├── README ├── VeraMono.ttf ├── crashRun.py ├── help.txt ├── keys.txt ├── license.txt ├── rumours.txt ├── src ├── Agent.py ├── BaseTile.py ├── Behaviour.py ├── CharacterGenerator.py ├── CombatResolver.py ├── CommandContext.py ├── Cyberspace.py ├── DisjointSet.py ├── DisplayGuts.py ├── DungeonMaster.py ├── DungeonUI.py ├── FieldOfView.py ├── FinalComplex.py ├── GameLevel.py ├── GamePersistence.py ├── Inventory.py ├── Items.py ├── Keys.py ├── Maze.py ├── MessageResolver.py ├── Mines.py ├── MiniBoss1.py ├── MonsterFactory.py ├── NewComplexFactory.py ├── OldComplex.py ├── Player.py ├── PriorityQueue.py ├── Prologue.py ├── ProvingGrounds.py ├── RLDungeonGenerator.py ├── Robots.py ├── Rooms.py ├── RumourFactory.py ├── ScienceComplex.py ├── Skills.py ├── Software.py ├── SubnetNode.py ├── Terrain.py ├── TowerFactory.py ├── Util.py ├── __init__.py └── ca_cave.py └── ttd.txt /README: -------------------------------------------------------------------------------- 1 | crashRun 0.4.0 Release Notes and Read Me 2 | 3 | ----------------------------------------------------------- 4 | What is crashRun? 5 | ----------------------------------------------------------- 6 | 7 | crashRun is a cyberpunk/sci-fi roguelike game where you 8 | play a "crash-runner", a combination of hacker and 9 | saboteur. In the world of crashRun, it's quite common for 10 | corporations to hire crash-runners to attack their 11 | competitors; trashing their facilities or stealing 12 | sensitive data. 13 | 14 | Unfamiliar with roguelike games? The wiki article may be 15 | useful: http://en.wikipedia.org/wiki/Roguelike 16 | 17 | You are a newbie crash-runner, still trying to make your 18 | reputation in the business. It's rough being a newbie, 19 | though, with jobs few and far between. With your rent past 20 | due and your stash of speed dwindling, you decide to answer 21 | that job posting that has been floating around the 22 | usual message boards for a couple of weeks now. For some 23 | reason, the experienced runners haven't touched it. 24 | 25 | It turned out the gig was a government one. A few years 26 | ago, they shutdown a military complex run by the Pensky 27 | Corporation that was investigating AI and other military 28 | technologies. But they didn't shut everything down; one of 29 | the AI systems took control of the complex after the 30 | scientists cleared out, and now it's starting to make it's 31 | presence known on the 'net. The Feds want you to go in and 32 | disable the AI's mainframe. So, you grabbed your 33 | sunglasses, your favourite shotgun and the last of your 34 | amphetamines and hopped in their helicopter. 35 | 36 | There was one catch they didn't tell you about until you 37 | were on the ground: if you haven't completed your mission 38 | in 48 hours, the Department of Defense is going to go with 39 | Plan B: nuking the complex from orbit, to stop the AI... 40 | 41 | ----------------------------------------------------------- 42 | Some Notes About Version 0.4.0 43 | ----------------------------------------------------------- 44 | 45 | - this software must be considered an early, incomplete 46 | version of the game. Bugs and crashes likely abound and 47 | there is a paucity of items and monsters in the game. 48 | 49 | - 0.4.0 allows you the option of going on a shopping trip 50 | before your adventure begins, if you want to customize your 51 | equipment. (You can also begin with a standard set of 52 | equipment) 53 | 54 | - You know have primary and secondary hands for wielding 55 | weapons. You can swap between primary and secondary and 56 | if you are wielding two melee weapons you'll automatically 57 | engage in two weapon combat. If you are wielding a firearm 58 | in your secondary hand, you won't use it in melee. The fire 59 | weapon command will automatically select a wielded firearm. 60 | Finally, you can save weapon configurations for easy swapping. 61 | 62 | - Some monsters are generated inactive and if your stealth 63 | checks are successful you may be able to sneak up on them. 64 | Attacking an inactive monster has an increased chance to hit 65 | and deals double damage. Loud noises may wake up inactive 66 | monsters. 67 | 68 | - the game isn't complete! The levels of the Penksy 69 | Complex which have been implemented so far amount to (I'd 70 | guess) about 40% of the game's eventual size. But you can 71 | go many levels down and battle the first mini-boss in the 72 | game. 73 | 74 | ----------------------------------------------------------- 75 | Setting Up crashRun 76 | ----------------------------------------------------------- 77 | 78 | If you have Python (2.4 or higher) and PyGame 1.8 installed, 79 | you should be good to go. I've tested crashRun on both 80 | MacOS X and Windows and I hope it'll run on Linux, too. 81 | 82 | If you need Python, you can download it from: 83 | http://python.org/download/ 84 | 85 | ** Vista users note: when I tried the python installer on a 86 | Vista machine, the installer didn't modify the PATH to 87 | include Python (it appeared to on XP). You can set this 88 | variable by right-clicking My Computer in the Start menu, 89 | selecting Properties, Advanced, and then pressing the 90 | Environment Variables button. You'll need to append 91 | ";C:\Python25\" to the end of the PATH variable. 92 | 93 | And PyGame is available here: 94 | http://pygame.org/download.shtml 95 | 96 | ** OS X Leopard users note: you will likely get a warning 97 | reading like this: 98 | 99 | "Python[4101:613] Warning once: This application, or a 100 | library it uses, is using NSQuickDrawView, which has been 101 | deprecated. Apps should cease use of QuickDraw and move to 102 | Quartz." 103 | 104 | This is a PyGame warning, not a crashRun warning; it 105 | doesn't affect gameplay and hopefully the PyGame gang will 106 | fix it sometime soon. You may also upgrade the version of 107 | SDL installed on your system which will make it go away. 108 | 109 | I intend, once crashRun is closer to a 1.0 release, to look 110 | for alternatives that wouldn't require users to have Python 111 | and PyGame preinstalled (and there are a number: Py2Exe, 112 | Py2App, or providing interfaces not built in PyGame) but 113 | for the immediate future I'm more interested in working on 114 | gameplay improvements. 115 | 116 | Once you are setup with Python and PyGame, all you should 117 | need to do is execute crashRun.py. 118 | 119 | This can be done from the commandline: 120 | 121 | python crashRun.py 122 | 123 | And depending on how your system is configured, you may be 124 | able to simply double-click on crashRun.py 125 | 126 | ----------------------------------------------------------- 127 | Playing 128 | ----------------------------------------------------------- 129 | 130 | When you start crashRun, you will be prompted for the name 131 | of your character. If it's the name of one of your saved 132 | games, it will be loaded up, otherwise you'll begin a new 133 | character. 134 | 135 | Character creation options are limited in this early 136 | version. The game will roll your stats, you'll spend a 137 | few starting skill points (not all of the skills have in 138 | game effects yet! Try to guess which ones!), and then 139 | you're good to go. 140 | 141 | Typing '?' will bring up the help file, which lists the 142 | various commands. You'll probably want to load your 143 | shotgun using the 'r' reload command. 144 | 145 | 'E' will let you look at the squares on the screen, which 146 | might be useful. 147 | 148 | Movement can be done with either the numeric keybad 149 | (numlock should be off) or vi-style movement keys (the 150 | developer's preference). 151 | 152 | When you encounter monsters, you can attack them in melee 153 | in the usual roguelike way, by bumping into them, or you 154 | can fire your shotfun ('f' to fire). 155 | 156 | 'i' will show your inventory and '@' will display your 157 | character's information. 158 | 159 | The action command ('a') allows you to interact with 160 | features on the map such as doors and computer terminals. 161 | 162 | ----------------------------------------------------------- 163 | License 164 | ----------------------------------------------------------- 165 | 166 | crashRun is released to you under the terms of the Free 167 | Software Foundation's General Public License, version 3. 168 | 169 | For full details, please see license.txt or visit the FSF's 170 | website: http://www.fsf.org/ 171 | 172 | PyGame is released under the LGPL and Python is distributed 173 | under the Python License (which has been deemed compatible 174 | with the GPL by the Free Software Foundation). 175 | 176 | Finally, I've bundled a font with crashRun, to avoid 177 | having to worry about which fonts users had installed on 178 | their systems. This is Bistream Vera Sans Mono and comes 179 | from the Gnome project (see http://www.gnome.org/fonts/). 180 | 181 | If you do end up working on the source code for crashRun, 182 | I'd love to hear about it! 183 | 184 | ----------------------------------------------------------- 185 | Contributors 186 | ----------------------------------------------------------- 187 | 188 | - Kiprianas Spiridonovas contributed a tweak to the display code for 0.5.0. 189 | 190 | ----------------------------------------------------------- 191 | Contacting Me 192 | ----------------------------------------------------------- 193 | 194 | The latest version of crashRun is available on its website: 195 | http://crashrun.org 196 | 197 | In addition, I've occasionally kept a development blog up 198 | to date: 199 | http://crashrun.blogspot.com/ 200 | 201 | If you have any questions, or need to report a bug, you can 202 | also reach me at: dana@pixelenvy.ca 203 | 204 | Thanks, and enjoy crashRun! 205 | 206 | Dana Larose 207 | 208 | -------------------------------------------------------------------------------- /VeraMono.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanaL/crashRun/9b95cc7dd2b8227a219769a95fcaee48e3371bec/VeraMono.ttf -------------------------------------------------------------------------------- /crashRun.py: -------------------------------------------------------------------------------- 1 | # Copyright 2010 by Dana Larose 2 | 3 | # This file is part of crashRun. 4 | 5 | # crashRun is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | 10 | # crashRun is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | 15 | # You should have received a copy of the GNU General Public License 16 | # along with crashRun. If not, see . 17 | 18 | from src.DungeonMaster import DungeonMaster 19 | from src.DungeonUI import DungeonUI 20 | from src.Keys import KeyConfigReader 21 | 22 | from sys import setcheckinterval 23 | #import profile 24 | 25 | VERSION = '0.5.0' 26 | 27 | keys = KeyConfigReader(VERSION) 28 | keymap = keys.read_keys() 29 | if keys.error_count > 0: 30 | print('There were errors found in the keys.txt. You must correct them\n') 31 | print('before crashRun can start. If you wish, you may simply delete the\n') 32 | print('keys.txt file and crashrun will generate a new, default one.\n') 33 | print('\n') 34 | print('The errors found were:\n') 35 | for e in keys.errors: 36 | print(e + '\n') 37 | exit() 38 | 39 | dui = DungeonUI(VERSION, keymap) 40 | dm = DungeonMaster(VERSION) 41 | dm.start_game(dui) 42 | #profile.run('dm.start_game()') 43 | -------------------------------------------------------------------------------- /help.txt: -------------------------------------------------------------------------------- 1 | crashRun 0.5.0 help file 2 | 3 | Basic commands: 4 | 5 | Movement is done via the numeric keypad (numlock must be off) 6 | or vi/rogue-style using these keys: 7 | 8 | yu 9 | hjkl 10 | bn 11 | 12 | Corresponding to the cardinal points: 13 | 14 | NW (y) N (j) NE (u) 15 | \ | / 16 | W (h) - @ - E (l) 17 | / | \ 18 | SW (n) S (k) SE (m) 19 | 20 | Other commands (the keys listed are the game's defaults): 21 | 22 | A - use Ability - If you had a special ability, this is how you would use it. 23 | B - Bash - Bash into something. Sometimes opens doors. 24 | a - Action - Use a computer terminal, open/close a door. May required a 25 | direction. (Use > to access a computer terminal, if necessary) 26 | c - Configure Weapons - Save what weapons you are currently wielding for easy 27 | switching. (You have up to 9 slots) 28 | C - Centre View - redraw the screen with you in the centre of the view. 29 | d - Drop - Drop item from inventory. 30 | f - Fire - Shoot a readied firearm. 31 | H - Hack - Hack an object in cyberspace (use to disarm logic bombs). 32 | i - Inventory - Display your stuff. 33 | @ - Info - Information about your character. 34 | P - Practice - improve your skills if you have points to spend 35 | Q - Quit - Quit game. Give up your mission. Coward. 36 | r - Reload - Reload firearm. 37 | S - Save - Save game and exit. 38 | s - Search - Search the area surrounding you for hidden things. 39 | t - Throw - Throw an object. 40 | R - Remove - Remove something you are wearing. 41 | U - Use - Use an item or software in your inventory. 42 | V - View Hall of Fame - Display high score list 43 | w - Wield - Select what you'll smack enemies with. 44 | (To use a saved config, hit the # of the slot to use.) 45 | W - Wear - Put on clothes, armour or accessories. 46 | E - Examine - Look at the squares on the map. 47 | x - Swap - Swap weapons between your primary and secondary hands. 48 | X - Exit - forcibly sever your link to cyberspace. (Can be hazardous) 49 | > - Go Down - Take a lift down to the next floor down. 50 | < - Go Up - Take a lift to the next floor up. 51 | , - Pick up - Pick up objects on the floor. 52 | * - Message memory - Display last 100 messages. 53 | . - Wait - Stand still for a moment and catch your breath. 54 | ? - Help - Access online help. 55 | 56 | Commands may be repeated by typing in a number before the command. 57 | Ie., 10dn means "Drop 10 of item 'n' in your inventory". 58 | 59 | When targeting a square, move the cursor around with the standard 60 | movement commands. Select target with a space. If you loose your 61 | cursor, hit Enter to bring it back to you. 62 | 63 | Keybindings may be changed by editing the keys.txt file. If you 64 | really mess up your key bindings, delete keys.txt and the game with 65 | generate a new file with default values. 66 | 67 | Symbols: 68 | 69 | What do all the characters mean? 70 | 71 | - @ is you, or another human 72 | - any letter is a bad guy 73 | 74 | Terrain: 75 | # - Wall (or tree, you can move onto a square with a tree) 76 | . - Ground, Floor, or Puddle 77 | + - Closed door 78 | - - Open door (also Firearms) 79 | ? - Computer terminal 80 | " - Security camera 81 | ~ - Water 82 | > - Elevator down 83 | < - Elevator up 84 | ^ - A trap (if you've spotted it) 85 | { - A pool of some sort 86 | 87 | Items: 88 | | - Blade of some kind 89 | / - Club or other bashing weapon 90 | ! - Pharmaceutical 91 | : - Software or file 92 | [ - Armour or clothes 93 | ) - Tool 94 | * - Ammunition 95 | ( - A box 96 | 97 | 98 | -------------------------------------------------------------------------------- /keys.txt: -------------------------------------------------------------------------------- 1 | VERSION: 0.5.0 2 | 'f': SHOOT 3 | 'j': MOVE_SOUTH 4 | 'k': MOVE_NORTH 5 | 'c': SAVE_WPN_CONFIG 6 | 'C': CENTRE_VIEW 7 | '*': SHOW_RECENT_MESSAGES 8 | 't': THROW_ITEM 9 | 'n': MOVE_SOUTHEAST 10 | 'h': MOVE_WEST 11 | 'P': PRACTICE_SKILLS 12 | 'U': USE_ITEM 13 | '@': CHAR_INFO 14 | 'u': MOVE_NORTHEAST 15 | 'd': DROP 16 | 'l': MOVE_EAST 17 | 'E': EXAMINE 18 | 'R': REMOVE_ARMOUR 19 | 'B': BASH 20 | 'a': ACTION 21 | 'A': SPECIAL_ABILITY 22 | ',': PICK_UP 23 | '#': DEBUG_COMMAND 24 | 'x': SWAP_WEAPONS 25 | 'b': MOVE_SOUTHWEST 26 | 's': SEARCH 27 | 'i': INVENTORY 28 | '.': PASS 29 | 'V': VIEW_SCORES 30 | 'H': HACKING 31 | 'W': WEAR_ARMOUR 32 | '<': MOVE_UP 33 | 'Q': QUIT 34 | 'y': MOVE_NORTHWEST 35 | 'w': WIELD_WEAPON 36 | 'r': RELOAD 37 | 'X': FORCE_QUIT_CYBERSPACE 38 | '>': MOVE_DOWN 39 | '?': SHOW_HELP 40 | 'S': SAVE_AND_EXIT 41 | 'O': TOGGLE_OPTIONS 42 | 'M': SHOW_MAP -------------------------------------------------------------------------------- /rumours.txt: -------------------------------------------------------------------------------- 1 | 0 2 | ...One key to understanding is to realize exactly why it is that the 3 | kind of bug report non-source-aware users normally turn in tends not 4 | to be very useful... 5 | ###################################################################### 6 | 0 7 | ...Non-source-aware users tend to report only surface symptoms; they 8 | take their environment for granted, so they (a) omit critical 9 | background data... 10 | ###################################################################### 11 | 0 12 | ...We have tried to retain the brevity of the first edition. C is not 13 | a big language, and it is not well served by a big book... 14 | ###################################################################### 15 | 1 16 | ...Recent security updates include seismic readers designed to 17 | initiate lock-down in the presence of an explosion... 18 | ###################################################################### 19 | 1 20 | Security path 45a87b: 21 | Admin-level access required in order to override lockdown... 22 | ###################################################################### 23 | 2 24 | ...Software team has been unable to tune the Docbot expert system to 25 | eliminate propensity for over-prescribing amputations. 26 |
27 | Military tech division increasingly questioning whether this is a bug 28 | or a feature... 29 | ###################################################################### 30 | 2 31 | ...New security expert system now in beta. Security Section requires 32 | redundant system in place before we go beta. They aren't the ones 33 | setting the budget 0.o... 34 | ###################################################################### 35 | 2 36 | /* I think the spec. for the surveillance drone is borked. If 37 | a security alarm is already sounding, and there's another security 38 | incident, the system won't re-initiate lockdown. 39 |
40 | Whatever. I'm writing to spec. Not my problem. 41 | */ 42 | ###################################################################### 43 | 3 44 | ...Recent security audits have identified concerns over the robustness 45 | of medical systems HCI systems. Several root exploits have been 46 | flagged... 47 | ###################################################################### 48 | 3 49 | ...Introducing the iCannon: an mp3 player, a phone, an internet mobile 50 | communicator, a relentless engine of destruction...these are NOT four 51 | separate devices... 52 | ###################################################################### 53 | 3 54 | ...Bio-tech systems is finding a useful supply of raw materials on 55 | the upper floors and the surface -- even the occasional stray human. 56 | More collector bots to be dispatched to the surface to collect more 57 | subjects for research... 58 | ###################################################################### 59 | 3 60 | ...possible flaw uncovered in ninja S.E.P. field. May be detectable 61 | in the infrared spectrum. Further testing required; will continue to 62 | developed; no plans to halt production of prototype models... 63 | ###################################################################### 64 | 3 65 | ...unionized staff sticking militantly to coffee breaks. At 10:00 am 66 | on the button, the lab is a ghost town. I can't wait until the new 67 | robot cleaners are online... 68 | ###################################################################### 69 | 4 70 | ...human subjects harvested from surface and upper levels display 71 | frequent instances of chemical dependencies. Such dependencies cause 72 | complications in experiments; science division tasked with developing 73 | a technique to flush systems... 74 | ###################################################################### 75 | 4 76 | ...Invoice #17894... 77 | - 174 rolls two-ply toilet tissue 78 | - 200 lbs burbank russet potatoes 79 | - 100 experimental taser upgrades for security bot model 200e 80 | - 4000 four-pack nine volt cell batteries 81 | - 500 single pack instant ramen noodles... 82 | ###################################################################### 83 | 4 84 | ...And then he had a visitor, a visitor unannounced, one who walked in 85 | through the elaborate maze of Smith's security as though it didn't 86 | exist. A small man, Japanese, enormously polite, who bore all the 87 | marks of a vatgrown ninja assassin... 88 | ###################################################################### 89 | 5 90 | ...Arstechnica has criticized Apple's new iCannon as pandering to 91 | style over substance, noting that its blasting power is weaker than 92 | initial expectations. The blog communities have been abuzz over its 93 | short battery life, and lack of user-changeable battery. Arstechnica 94 | did, however, give high praise to the targeting interface and its easy 95 | synchronization via iTunes... 96 | ###################################################################### -------------------------------------------------------------------------------- /src/BaseTile.py: -------------------------------------------------------------------------------- 1 | # Copyright 2010 by Dana Larose 2 | 3 | # This file is part of crashRun. 4 | 5 | # crashRun is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | 10 | # crashRun is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | 15 | # You should have received a copy of the GNU General Public License 16 | # along with crashRun. If not, see . 17 | 18 | from .Util import get_correct_article 19 | 20 | class BaseTile(object): 21 | def __init__(self, ch, fg, bg, lit, name): 22 | self.ch = ch 23 | self.fg_colour = fg 24 | self.bg_colour = bg 25 | self.lit_colour = lit 26 | self.name = name 27 | 28 | def get_ch(self): 29 | return self.ch 30 | 31 | def get_name(self, article=0): 32 | if article == 0: 33 | return 'the ' + self.name 34 | elif article == 1: 35 | return self.name 36 | 37 | _name = get_correct_article(self.name) + ' ' + self.name 38 | return _name.strip() 39 | -------------------------------------------------------------------------------- /src/Behaviour.py: -------------------------------------------------------------------------------- 1 | # Copyright 2010 by Dana Larose 2 | 3 | # This file is part of crashRun. 4 | 5 | # crashRun is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | 10 | # crashRun is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | 15 | # You should have received a copy of the GNU General Public License 16 | # along with crashRun. If not, see . 17 | 18 | from . import Items as I 19 | from .Util import get_correct_article 20 | 21 | def has_ammo_for(agent, gun): 22 | _a = ord('a') 23 | for _num in range(26): 24 | _letter = chr(_a + _num) 25 | _item = agent.inventory.get_item(_letter) 26 | if _item != '' and gun.is_ammo_compatible(_item): 27 | return _letter 28 | return '' 29 | 30 | def check_inventory_for_guns(agent): 31 | _guns = [] 32 | _a = ord('a') 33 | for _num in range(26): 34 | _letter = chr(_a + _num) 35 | _item = agent.inventory.get_item(_letter) 36 | if isinstance(_item, I.Firearm): 37 | if _item.current_ammo > 0 or has_ammo_for(agent, _item) != '': 38 | _guns.append((_item, _letter)) 39 | 40 | return _guns 41 | 42 | def pick_gun(agent): 43 | _pick = '' 44 | _guns = check_inventory_for_guns(agent) 45 | _max_dmg = 0 46 | for _gun in _guns: 47 | _dmg = _gun[0].shooting_roll * _gun[0].shooting_damage 48 | if _dmg > _max_dmg: 49 | _max_dmg = _dmg 50 | _pick = _gun[1] 51 | return _pick 52 | 53 | def pick_melee_weapon(agent): 54 | _inv = agent.inventory 55 | _a = ord('a') 56 | _max_dmg = 0 57 | _pick = '-' 58 | for _num in range(26): 59 | _letter = chr(_a + _num) 60 | _item = _inv.get_item(_letter) 61 | if isinstance(_item, I.Weapon): 62 | _dmg = _item.d_roll * _item.d_dice 63 | if _dmg > _max_dmg: 64 | _pick = _letter 65 | _max_dmg = _dmg 66 | 67 | return _pick 68 | 69 | # A monster who doesn't use guns. 70 | def select_weapon_for_brawler(agent): 71 | _pick = pick_melee_weapon(agent) 72 | _msg = agent.get_articled_name() 73 | 74 | if _pick == '-': 75 | _msg += " cracks his knuckles." 76 | else: 77 | _item = agent.inventory.get_item(_pick) 78 | if agent.get_max_h_to_h_dmg() > _item.d_roll * _item.d_dice: 79 | _msg += " cracks his knuckles." 80 | else: 81 | _name = _item.get_name(1) 82 | _msg += " readies " + get_correct_article(_name) + " " + _name 83 | agent.dm.alert_player(agent.row, agent.col, _msg) 84 | agent.inventory.ready_weapon(_pick) 85 | 86 | def select_weapon_for_shooter(agent): 87 | _gun = pick_gun(agent) 88 | if _gun != '': 89 | agent.inventory.ready_weapon(_gun) 90 | else: 91 | select_weapon_for_brawler(agent) 92 | 93 | # Return a list of armour to equip, ordered from most to least effective. 94 | def pick_armour(agent): 95 | _inv = agent.inventory 96 | _best_pieces = {'suit' : None, 'helmet' : None, 'gloves' : None, 'cloak' : None, 97 | 'boots' : None, 'glasses' : None, 'watch' : None} 98 | 99 | for j in range(0, 26): 100 | _slot = chr(ord('a') + j) 101 | _item = _inv.get_item(_slot) 102 | if isinstance(_item, I.Armour): 103 | _area = _item.get_area() 104 | _ac_mod = _item.get_ac_modifier() 105 | _curr = _inv.get_armour_in_location(_area) 106 | if _curr == '' or _ac_mod > _curr.get_ac_modifier(): 107 | if _best_pieces[_area] == None or _ac_mod > _best_pieces[_area][0]: 108 | _best_pieces[_area] = (_ac_mod, _slot) 109 | 110 | _pieces = [p for p in list(_best_pieces.values()) if p != None] 111 | 112 | if _pieces != None: 113 | _pieces.sort() 114 | _pieces.reverse() 115 | 116 | return [_p[1] for _p in _pieces if _p != None] 117 | -------------------------------------------------------------------------------- /src/CharacterGenerator.py: -------------------------------------------------------------------------------- 1 | # Copyright 2010 by Dana Larose 2 | 3 | # This file is part of crashRun. 4 | 5 | # crashRun is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | 10 | # crashRun is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | 15 | # You should have received a copy of the GNU General Public License 16 | # along with crashRun. If not, see . 17 | 18 | from copy import deepcopy 19 | from random import choice 20 | from random import randrange 21 | 22 | from .Items import ItemFactory 23 | from .Player import Player 24 | from .Player import PlayerStats 25 | from .Skills import SkillTable 26 | from .Software import get_software_by_name 27 | 28 | class CharacterGenerator: 29 | def __init__(self,dl,dm): 30 | self.dui = dl 31 | self.dm = dm 32 | 33 | def new_character(self, player_name): 34 | self.__gen_new_character(player_name) 35 | self.__show_initial_stats() 36 | 37 | msg = [self.__player.background,' ', 'Next you will select your skills.'] 38 | self.dui.write_screen(msg, True) 39 | 40 | self.__select_skills() 41 | self.__display_player_skills() 42 | self.__get_starting_equipment() 43 | 44 | self.__player.calc_ac() 45 | 46 | return self.__player 47 | 48 | def __get_starting_equipment(self): 49 | header = ['You can start your mission with a standard kit, or go shopping beforehand...'] 50 | menu = [('s','Standard kit','standard'),('v','Visit the mall','mall')] 51 | 52 | _choice = '' 53 | while _choice == '': 54 | _choice = self.dui.ask_menued_question(header,menu) 55 | 56 | if _choice == 'standard': 57 | self.__set_standard_kit() 58 | else: 59 | self.__fighting_is_hard_lets_go_shopping() 60 | 61 | def __fighting_is_hard_lets_go_shopping(self): 62 | _if = ItemFactory() 63 | _cash = 150 64 | 65 | # This stuff the player gets for free 66 | self.__player.inventory.add_item(_if.gen_item('stylish leather jacket', 1), 1) 67 | self.__player.inventory.add_item(_if.gen_item('stylish sunglasses', 1), 1) 68 | self.__player.inventory.add_item(_if.gen_item('wristwatch', 1), 1) 69 | for j in range(randrange(12, 25)): 70 | self.__player.inventory.add_item(_if.gen_item('amphetamine', 1), 0) 71 | 72 | _menu = [] 73 | _menu.append(('a', 'Shotgun - $50', ('shotgun', 50))) 74 | _menu.append(('b', 'P90 Assault Rifle - $80', ('p90 assault rifle', 80))) 75 | _menu.append(('c', 'M1911A1 - $75', ('m1911a1', 75))) 76 | _menu.append(('d', 'Shotgun Shells - $10/dozen', ('shell', 10))) 77 | _menu.append(('e', 'Machine Gun Clip - $15', ('machine gun clip', 15))) 78 | _menu.append(('f', '9mm Clip - $15', ('9mm clip', 15))) 79 | _menu.append(('g', 'Grenade - $17', ('grenade', 17))) 80 | _menu.append(('h', 'Truncheon - $15', ('truncheon', 15))) 81 | _menu.append(('i', 'Combat Knife - $10', ('combat knife', 10))) 82 | _menu.append(('j', 'Army Helmet - $25', ('army helmet', 25))) 83 | _menu.append(('k', 'Combat Boots - $25', ('combat boots', 25))) 84 | _menu.append(('l', 'High-Tech Sandals - $15', ('high-tech sandals', 15))) 85 | _menu.append(('m', 'C4 Charge - $10', ('C4 Charge', 10))) 86 | _menu.append(('n', 'Medkit - $10', ('medkit', 10))) 87 | _menu.append(('o', 'Lockpick - $10', ('lockpick', 10))) 88 | _menu.append(('p', 'Flare - $5', ('flare', 5))) 89 | _menu.append(('r', 'Flashlight - $10', ('flashlight', 10))) 90 | _menu.append(('s', 'Spare Battery - $5', ('battery', 5))) 91 | _menu.append(('q', 'Quit and begin your mission', 'quit')) 92 | 93 | while True: 94 | _header = ['You have $%d remaining.' % (_cash)] 95 | _choice = self.dui.ask_menued_question(_header, _menu) 96 | 97 | if _choice == '': 98 | continue 99 | 100 | if _choice == 'quit': 101 | break 102 | 103 | if _cash < _choice[1]: 104 | self.dui.display_message(" You can't afford that!", True) 105 | else: 106 | _cash -= _choice[1] 107 | self.dui.clear_msg_line() 108 | 109 | if _choice[0] == 'shell': 110 | for j in range(12): 111 | self.__player.inventory.add_item(_if.gen_item('shotgun shell', 1), 0) 112 | self.dui.display_message(" You buy some shotgun shells.", True) 113 | elif _choice[0] == 'flashlight': 114 | _fl = _if.gen_item('flashlight', 1) 115 | _fl.charge = _fl.maximum_charge # seemed mean not to 116 | self.dui.display_message(" You buy a flashlight.", True) 117 | self.__player.inventory.add_item(_fl, 0) 118 | elif _choice[0] in ('m1911a1', 'p90 assault rifle'): 119 | _gun = _if.gen_item(_choice[0], 1) 120 | _gun.current_ammo = 0 121 | self.__player.inventory.add_item(_gun, 0) 122 | _name = _gun.get_name(2) 123 | self.dui.display_message(" You buy " + _name + ".", True) 124 | else: 125 | _item = _if.gen_item(_choice[0], 1) 126 | self.__player.inventory.add_item(_item, 0) 127 | _name = _item.get_name(2) 128 | self.dui.display_message(" You buy " + _name + ".", True) 129 | 130 | def __set_standard_kit(self): 131 | _if = ItemFactory() 132 | 133 | self.__player.inventory.add_item(_if.gen_item('shotgun', 1), 0) 134 | 135 | for j in range(randrange(24, 37)): 136 | self.__player.inventory.add_item(_if.gen_item('shotgun shell',1), 1) 137 | 138 | self.__player.inventory.add_item(_if.gen_item('stylish leather jacket', 1), 1) 139 | self.__player.inventory.add_item(_if.gen_item('stylish sunglasses', 1), 1) 140 | self.__player.inventory.add_item(_if.gen_item('wristwatch', 1), 1) 141 | self.__player.inventory.add_item(_if.gen_item('high-tech sandals', 1), 1) 142 | self.__player.inventory.add_item(_if.gen_item('C4 Charge', 1), 0) 143 | self.__player.inventory.add_item(_if.gen_item('C4 Charge', 1), 0) 144 | self.__player.inventory.add_item(_if.gen_item('C4 Charge', 1), 0) 145 | self.__player.inventory.add_item(_if.gen_item('truncheon', 1), 1) 146 | self.__player.inventory.add_item(_if.gen_item('lockpick', 1), 0) 147 | 148 | def __display_player_skills(self): 149 | msg = ['You are trained in the following skills:'] 150 | 151 | for category in self.__player.skills.get_categories(): 152 | msg.append(category + ':') 153 | [msg.append(' ' + skill.get_name() + ' - ' + skill.get_rank_name()) for skill in self.__player.skills.get_category(category)] 154 | 155 | self.dui.write_screen(msg, True) 156 | 157 | def __generate_background(self): 158 | _roll = randrange(7) 159 | _st = SkillTable() 160 | _st.set_skill('Wetware Admin', 1) 161 | 162 | if _roll == 0: 163 | if randrange(2) == 0: 164 | _background = "You were a two-bit high school dropout. You learned to hack looking over your\nboyfriend's shoulder." 165 | else: 166 | _background = "You were a two-bit high school dropout. You learned to hack looking over your\ngirlfiend's shoulder." 167 | _st.set_skill('Hacking', 1) 168 | _st.set_skill(choice(['Lock Picking', 'Stealth']), 1) 169 | elif _roll == 1: 170 | _background = "You were a punk street kid. You joined a gang and they handed you a deck\nbecause you sucked at car jacking." 171 | _st.set_skill('Melee', 1) 172 | _st.set_skill(choice(['Lock Picking', 'Stealth']), 1) 173 | elif _roll == 2: 174 | _st.set_skill('Hardware Tech', 1) 175 | _st.set_skill('Hacking', 1) 176 | _background = "You were a suburban middle-class kid who became a script kiddie and are now\ntrying to make a name for yourself." 177 | elif _roll == 3: 178 | _st.set_skill('Melee', 1) 179 | _st.set_skill('Guns', 1) 180 | _background = "You were a soldier recruited into a saboteur unit. You were eventually\ndischarged after being injured in the line of duty. (Carpal tunnel syndrome)" 181 | elif _roll == 4: 182 | _st.set_skill('Hacking', 1) 183 | _st.set_skill('Crypto', 1) 184 | _background = "You were a university prof who was laid off and subsequently fell into the\ncrash runner underworld." 185 | elif _roll == 5: 186 | _st.set_skill('Hacking', 1) 187 | _st.set_skill(choice(['Crypto', 'Hardware Tech', 'Electronics']), 1) 188 | _background = "A recent university graduate, you turned to crash running to pay off your \nstudent loans after your Web 11.0 start-up tanked." 189 | elif _roll == 6: 190 | _background = "You were a corporate programmer until you finally got sick of maintaining\n200 year old Visual Basic code and quit your job." 191 | for j in range(2): 192 | _skill_name = choice(['Crypto', 'Electronics', 'Hacking', 'Hardware Tech', 'Robot Psychology', 'Wetware Admin']) 193 | _skill = _st.get_skill(_skill_name) 194 | _st.set_skill(_skill_name, _skill.get_rank()+1) 195 | 196 | return _background, _st 197 | 198 | def __gen_new_character(self, name): 199 | _background, _skills = self.__generate_background() 200 | 201 | self.__player = Player(PlayerStats(), _background, name, 0, 0, self.dm) 202 | 203 | 204 | self.__player.skills = _skills 205 | self.__set_starting_software() 206 | 207 | def __set_starting_software(self): 208 | _p = self.__player 209 | 210 | _sw = get_software_by_name('Norton Anti-Virus 27.4', 0) 211 | _sw.executing = True 212 | _p.software.upload(_sw) 213 | 214 | _sw = get_software_by_name('ipfw', 0) 215 | _sw.executing = True 216 | _p.software.upload(_sw) 217 | 218 | _sw = get_software_by_name('ACME ICE Breaker, Home Edition', 0) 219 | _sw.executing = True 220 | _p.software.upload(_sw) 221 | 222 | def __select_skills(self): 223 | self.__player.skill_points = 6 224 | while self.__player.skill_points > 0: 225 | self.dui.cc.practice_skills(self.__player) 226 | 227 | def __show_initial_stats(self): 228 | while True: 229 | msg = ['Your initial stats are:'] 230 | msg.append(' Strength: ' + str(self.__player.stats.get_strength())) 231 | msg.append(' Co-ordination: ' + str(self.__player.stats.get_coordination())) 232 | msg.append(' Toughness: ' + str(self.__player.stats.get_toughness())) 233 | msg.append(' Intuition: ' + str(self.__player.stats.get_intuition())) 234 | msg.append(' Chutzpah: ' + str(self.__player.stats.get_chutzpah())) 235 | msg.append(' ') 236 | msg.append('(r)eroll or any other key to continue.') 237 | 238 | self.dui.write_screen(msg, False) 239 | ch = self.dui.wait_for_input() 240 | if ch == 'r': 241 | self.__player.stats.roll_stats() 242 | else: 243 | break 244 | -------------------------------------------------------------------------------- /src/CombatResolver.py: -------------------------------------------------------------------------------- 1 | # Copyright 2010 by Dana Larose 2 | 3 | # This file is part of crashRun. 4 | 5 | # crashRun is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | 10 | # crashRun is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | 15 | # You should have received a copy of the GNU General Public License 16 | # along with crashRun. If not, see . 17 | 18 | from random import randrange 19 | 20 | from . import Items 21 | from .Items import BatteryPowered 22 | from .Items import Weapon 23 | from .MessageResolver import MessageResolver 24 | from .Util import do_d10_roll 25 | from .Util import get_rnd_direction_tuple 26 | 27 | class CombatResolver(object): 28 | def __init__(self, dm, dui): 29 | self.dm = dm 30 | self.dui = dui 31 | 32 | def get_total_uke_ac(self, uke): 33 | _uke_ac = uke.get_curr_ac() 34 | try: 35 | _uke_ac += uke.skills.get_skill("Dodge").get_rank() 36 | _uke_ac += int(round(uke.skills.get_skill("Dancing").get_rank() / 2)) 37 | except AttributeError: 38 | pass 39 | except KeyError: 40 | pass 41 | 42 | return _uke_ac 43 | 44 | class CyberspaceCombatResolver(CombatResolver): 45 | def attack(self, tori, uke): 46 | _base_roll = randrange(20) + 1 47 | _roll = _base_roll + tori.level / 2 48 | _roll += tori.get_cyberspace_attack_modifier() 49 | 50 | if _base_roll == 20 or _roll > self.get_total_uke_ac(uke): 51 | self.dm.mr.show_hit_message(tori, uke, 'hit') 52 | _dmg = tori.get_cyberspace_damage_roll() 53 | uke.damaged(self.dm, _dmg, tori) 54 | else: 55 | self.dm.mr.show_miss_message(tori, uke) 56 | 57 | def get_total_uke_ac(self, uke): 58 | _uke_ac = uke.get_curr_ac() 59 | try: 60 | _uke_ac += uke.get_intuition_bonus() * 2 61 | except AttributeError: 62 | pass 63 | 64 | return _uke_ac 65 | 66 | class MeleeResolver(CombatResolver): 67 | def __attack_uke(self, tori, uke, weapon, attack_modifiers): 68 | _dmg_types = [] 69 | if isinstance(weapon, Weapon): 70 | _dmg_types = weapon.get_damage_types() 71 | _base_roll = randrange(20) + 1 72 | _roll = _base_roll + tori.level / 2 73 | _roll += tori.get_melee_attack_modifier(weapon) 74 | _roll += attack_modifiers 75 | _success = False 76 | 77 | if _base_roll == 20 or _roll > self.get_total_uke_ac(uke): 78 | if weapon == '': 79 | _dmg = tori.get_hand_to_hand_dmg_roll() 80 | else: 81 | _dmg = weapon.dmg_roll(tori) 82 | 83 | try: 84 | if uke.attitude == 'inactive': 85 | _dmg *= 2 86 | except AttributeError: 87 | pass 88 | 89 | _verb = 'hit' 90 | if tori.melee_type == 'fire': 91 | _verb = 'burn' 92 | _dmg_types = ['burn'] 93 | elif tori.melee_type == 'shock': 94 | _verb = 'shock' 95 | _dmg_types = ['shock'] 96 | elif isinstance(weapon, Items.HandGun): 97 | _verb = 'pistol whip' 98 | 99 | self.dm.mr.show_hit_message(tori, uke, _verb) 100 | uke.damaged(self.dm, _dmg, tori, _dmg_types) 101 | _success = True 102 | else: 103 | self.dm.mr.show_miss_message(tori, uke) 104 | 105 | if isinstance(weapon, BatteryPowered) and weapon.charge > 0: 106 | # taser should only use a charge if it hit 107 | if _success or weapon.get_name(1) != "taser": 108 | weapon.charge -= 1 109 | if weapon.charge == 0: self.dm.items_discharged(tori, [weapon]) 110 | 111 | def attack(self, tori, uke): 112 | if tori.has_condition('dazed'): 113 | _dt = get_rnd_direction_tuple() 114 | r = tori.row + _dt[0] 115 | c = tori.col + _dt[1] 116 | _lvl = self.dm.dungeon_levels[uke.curr_level] 117 | uke = _lvl.dungeon_loc[r][c].occupant 118 | 119 | if uke == '': 120 | self.dm.mr.simple_verb_action(tori, ' %s wildly and %s.',['swing','miss']) 121 | return 122 | 123 | _attack_modifiers = 0 124 | try: 125 | if uke.attitude == 'inactive': 126 | _attack_modifiers = 10 127 | except AttributeError: 128 | pass 129 | 130 | _primary = tori.inventory.get_primary_weapon() 131 | _secondary = tori.inventory.get_secondary_weapon() 132 | 133 | if _primary != '' and _secondary != '' and not isinstance(_secondary, Items.Firearm): 134 | # two weapon fighting 135 | _tw_modifier = tori.get_two_weapon_modifier() + _attack_modifiers 136 | self.__attack_uke(tori, uke, _primary, _tw_modifier) 137 | if not uke.dead: # he may have been killed by the first blow 138 | self.__attack_uke(tori, uke, _primary, _tw_modifier - 2) 139 | else: 140 | self.__attack_uke(tori, uke, _primary, _attack_modifiers) 141 | 142 | class ShootingResolver(CombatResolver): 143 | def attack(self, tori, uke, gun): 144 | _base_roll = randrange(20) + 1 145 | _roll = _base_roll + tori.level / 2 146 | _roll += tori.get_shooting_attack_modifier() 147 | _roll += gun.to_hit_bonus 148 | 149 | if _base_roll == 20 or _roll > self.get_total_uke_ac(uke): 150 | self.dm.mr.shot_message(uke) 151 | _dmg = gun.shooting_dmg_roll() 152 | uke.damaged(self.dm, _dmg, tori) 153 | return True 154 | return False 155 | 156 | class ThrowingResolver(CombatResolver): 157 | def attack(self, tori, uke, item): 158 | _base_roll = randrange(20) + 1 159 | _roll = _base_roll + tori.level / 2 160 | _roll += tori.get_thrown_attack_modifier() 161 | 162 | if _base_roll == 20 or _roll > self.get_total_uke_ac(uke): 163 | self.dm.mr.thrown_message(item, uke) 164 | _dmg = item.dmg_roll(tori) 165 | uke.damaged(self.dm, _dmg, tori) 166 | return True 167 | return False 168 | 169 | 170 | -------------------------------------------------------------------------------- /src/Cyberspace.py: -------------------------------------------------------------------------------- 1 | # Copyright 2010 by Dana Larose 2 | 3 | # This file is part of crashRun. 4 | 5 | # crashRun is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | 10 | # crashRun is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | 15 | # You should have received a copy of the GNU General Public License 16 | # along with crashRun. If not, see . 17 | 18 | from random import choice 19 | from random import random 20 | from random import randrange 21 | 22 | from .Agent import DaemonicProcess 23 | from .Agent import SecurityControlProgram 24 | from .CombatResolver import CyberspaceCombatResolver 25 | from .FieldOfView import get_lit_list 26 | from .GameLevel import GameLevel 27 | from .Maze import Maze 28 | from . import MonsterFactory 29 | from .Software import get_software_by_name 30 | from . import Terrain as T 31 | from .Util import do_d10_roll 32 | 33 | class TrapSetOff(Exception): 34 | pass 35 | 36 | class CyberspaceLevel(GameLevel): 37 | def __init__(self, dm, level_num, length, width): 38 | GameLevel.__init__(self, dm, level_num, length, width, 'cyberspace') 39 | self.melee = CyberspaceCombatResolver(dm, dm.dui) 40 | 41 | def access_cameras(self): 42 | _meat_lvl = self.dm.dungeon_levels[self.dm.player.meatspace_level] 43 | _msg = "You %s the security camera system." % ("disable" if _meat_lvl.cameras_active else "activate") 44 | _meat_lvl.cameras_active = not _meat_lvl.cameras_active 45 | 46 | self.dm.dui.display_message(_msg) 47 | 48 | def activate_security_program(self): 49 | _scp = SecurityControlProgram(self.dm, 0, 0, self.dm.player.meatspace_level) 50 | GameLevel.add_monster(self, _scp) 51 | 52 | def add_monster(self): 53 | GameLevel.add_monster(self, self.__get_monster()) 54 | 55 | def __add_daemon_fortress(self): 56 | _width = randrange(3, 7) 57 | _tf = T.TerrainFactory() 58 | 59 | # place the fortress 60 | _start_r = randrange(1, self.lvl_length - _width - 2) 61 | _start_c = randrange(1, self.lvl_width - _width - 2) 62 | for col in range(_width + 1): 63 | self.map[_start_r][_start_c + col] = _tf.get_terrain_tile(T.FIREWALL) 64 | self.map[_start_r + _width][_start_c + col] = _tf.get_terrain_tile(T.FIREWALL) 65 | 66 | for row in range(1, _width): 67 | self.map[_start_r + row][_start_c] = _tf.get_terrain_tile(T.FIREWALL) 68 | self.map[_start_r + row][_start_c + _width] = _tf.get_terrain_tile(T.FIREWALL) 69 | for col in range(1, _width - 1): 70 | self.map[_start_r + row][_start_c + col] = _tf.get_terrain_tile(T.CYBERSPACE_FLOOR) 71 | 72 | _r_delta = randrange(1, _width) 73 | _c_delta = randrange(1, _width) 74 | _daemon_r = _start_r + _r_delta 75 | _daemon_c = _start_c + _c_delta 76 | _daemon = DaemonicProcess(self.dm, _daemon_r, _daemon_c, self.level_num) 77 | self.add_monster_to_dungeon(_daemon, _daemon_r, _daemon_c) 78 | 79 | # Eventually traps should have a difficulty level, for now 80 | # we'll require a roll of 15 to disarm the trap 81 | def attempt_to_hack_trap(self, player, tile, row, col): 82 | _hack = player.skills.get_skill("Hacking").get_rank() + 1 83 | _roll = do_d10_roll(_hack, 0) + player.get_intuition_bonus() 84 | 85 | if _roll < 3: 86 | _msg = 'You set off ' + tile.get_name() + '!' 87 | self.dm.alert_player(player.row, player.col, _msg) 88 | raise TrapSetOff() 89 | elif _roll > 15: 90 | _tf = T.TerrainFactory() 91 | self.map[row][col] = _tf.get_terrain_tile(T.CYBERSPACE_FLOOR) 92 | _msg = "You delete " + tile.get_name() + "." 93 | self.dm.alert_player(player.row, player.col, _msg) 94 | else: 95 | _msg = "You aren't able to alter the code fabric." 96 | self.dm.alert_player(player.row, player.col, _msg) 97 | 98 | def end_of_turn(self): 99 | if self.dm.virtual_turn % 50 == 0: 100 | for m in self.monsters: 101 | m.add_hp(1) 102 | if random() < 0.5: 103 | self.add_monster() 104 | 105 | def generate_level(self, meatspace_level): 106 | self.__generate_map() 107 | self.__add_daemon_fortress() 108 | self.__add_traps() 109 | self.__add_exit_nodes() 110 | #self.__add_monsters() 111 | self.__add_files() 112 | self.__set_entry_spot() 113 | 114 | self.place_sqr(T.SecurityCamera(0, True), T.CYBERSPACE_FLOOR) 115 | 116 | _tf = T.TerrainFactory() 117 | self.place_sqr(_tf.get_terrain_tile(T.UP_STAIRS), T.CYBERSPACE_FLOOR) 118 | self.place_sqr(_tf.get_terrain_tile(T.DOWN_STAIRS), T.CYBERSPACE_FLOOR) 119 | 120 | _meat_lvl = self.dm.dungeon_levels[meatspace_level] 121 | for _node in _meat_lvl.subnet_nodes: 122 | self.subnet_nodes.append(_node) 123 | self.place_sqr(_node, T.CYBERSPACE_FLOOR) 124 | 125 | def is_cyberspace(self): 126 | return True 127 | 128 | def lift_access(self, stairs): 129 | _meat_lvl = self.dm.dungeon_levels[self.dm.player.meatspace_level] 130 | if stairs.get_type() == T.UP_STAIRS: 131 | _stairs_loc = _meat_lvl.find_up_stairs_loc() 132 | else: 133 | _stairs_loc = _meat_lvl.find_down_stairs_loc() 134 | _meat_stairs = _meat_lvl.map[_stairs_loc[0]][_stairs_loc[1]] 135 | _meat_stairs.activated = not _meat_stairs.activated 136 | if _meat_stairs.activated: 137 | self.dm.dui.display_message('You activate the lift.') 138 | else: 139 | self.dm.dui.display_message('You deactivate the lift.') 140 | 141 | def mark_initially_known_sqrs(self, radius): 142 | for _sqr in get_lit_list(radius): 143 | _s = (self.entrance[0] + _sqr[0], self.entrance[1] + _sqr[1]) 144 | if self.in_bounds(_s[0], _s[1]): 145 | self.dungeon_loc[_s[0]][_s[1]].visited = True 146 | 147 | def __add_exit_nodes(self): 148 | _tf = T.TerrainFactory() 149 | 150 | for j in range(3): 151 | self.place_sqr(_tf.get_terrain_tile(T.EXIT_NODE), T.CYBERSPACE_FLOOR) 152 | 153 | def __get_low_level_file(self): 154 | _r = random() 155 | 156 | if _r < 0.50: 157 | _s = get_software_by_name('mp3', 1) 158 | elif _r < 0.75: 159 | _s = get_software_by_name('data file', self.level_num // 2) 160 | else: 161 | _s = get_software_by_name('Portable Search Engine', 1) 162 | 163 | return _s 164 | 165 | def __get_mid_level_file(self): 166 | _r = random() 167 | 168 | if _r < 0.20: 169 | _s = get_software_by_name('mp3', 1) 170 | elif _r < 0.70: 171 | _s = get_software_by_name('data file', self.level_num // 2) 172 | elif _r < 0.80: 173 | _s = get_software_by_name('Portable Search Engine', 1) 174 | elif _r < 0.90: 175 | _s = get_software_by_name('Camel Eye', 1) 176 | else: 177 | _s = get_software_by_name('Ono-Sendai ICE Breaker Pro 1.0', 1) 178 | 179 | return _s 180 | 181 | def __get_sc_level_file(self): 182 | _r = random() 183 | 184 | if _r < 0.50: 185 | _s = get_software_by_name('data file', self.level_num // 2) 186 | elif _r < 0.60: 187 | _s = get_software_by_name('Portable Search Engine', 1) 188 | elif _r < 0.70: 189 | _s = get_software_by_name('Camel Eye', 1) 190 | elif _r < 0.80: 191 | _s = get_software_by_name('Ono-Sendai ICE Breaker Pro 1.0', 1) 192 | elif _r < 0.90: 193 | _s = get_software_by_name('GNU Emacs (ICE mode) 17.4', 1) 194 | else: 195 | _s = get_software_by_name('Zone Alarm 57.3', 1) 196 | 197 | return _s 198 | 199 | def __add_file(self): 200 | if self.level_num < 4: 201 | _s = self.__get_low_level_file() 202 | elif self.level_num < 7: 203 | _s = self.__get_mid_level_file() 204 | else: 205 | _s = self.__get_sc_level_file() 206 | 207 | while True: 208 | r = randrange(self.lvl_length) 209 | c = randrange(self.lvl_width) 210 | 211 | if self.map[r][c].get_type() == T.CYBERSPACE_FLOOR: 212 | self.add_item_to_sqr(r, c, _s) 213 | break 214 | 215 | def __add_files(self): 216 | [self.__add_file() for j in range(randrange(1, 7))] 217 | 218 | def __add_monsters(self): 219 | [self.add_monster() for j in range(randrange(15, 26))] 220 | 221 | def __add_traps(self): 222 | # will probably adjust this for level_num at some point 223 | if self.level_num < 3: 224 | _traps = randrange(4) 225 | elif self.level_num < 7: 226 | _traps = randrange(6) 227 | else: 228 | _traps = randrange(8) 229 | 230 | for j in range(_traps): 231 | self.place_sqr(T.LogicBomb(), T.CYBERSPACE_FLOOR) 232 | 233 | def __generate_map(self): 234 | _maze = Maze(self.lvl_length, self.lvl_width) 235 | self.map = _maze.gen_map() 236 | self.lvl_length = _maze.length 237 | self.lvl_width = _maze.width 238 | 239 | _tf = T.TerrainFactory() 240 | for r in range(1, self.lvl_length-1): 241 | for c in range(1, self.lvl_width-1): 242 | self.map[r][c] = _tf.get_terrain_tile(T.CYBERSPACE_FLOOR) 243 | 244 | # Add a few open spaces 245 | for j in range(randrange(3, 7)): 246 | _row = randrange(4, self.lvl_length - 4) 247 | _col = randrange(4, self.lvl_width - 4) 248 | for _r in (-1, 0, 1): 249 | for _c in (-1, 0 , 1): 250 | self.map[_row + _r][_col + _c] = _tf.get_terrain_tile(T.CYBERSPACE_FLOOR) 251 | 252 | def __get_monster(self): 253 | if self.level_num == 1: 254 | _monster = choice([0, 0, 1, 1, 2, 2, 3]) 255 | elif self.level_num < 3: 256 | _monster = choice([0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5]) 257 | elif self.level_num < 7: 258 | _monster = choice ([1, 2, 3, 3, 4, 4, 5, 5]) 259 | elif self.level_num < 9: 260 | _monster = choice([3, 4, 4, 5, 5, 6, 6, 7, 7, 8]) 261 | elif self.level_num <= 10: 262 | _monster = choice([4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 10]) 263 | elif self.level_num >= 11: 264 | _monster = choice([4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 10]) 265 | 266 | if _monster == 0: 267 | _name = 'grid bug' 268 | elif _monster == 1: 269 | _name = 'belligerent process' 270 | elif _monster == 2: 271 | _name = 'script kiddie' 272 | elif _monster == 3: 273 | _name = 'two bit hacker' 274 | elif _monster == 4: 275 | _name = 'pensky antiviral mark I' 276 | elif _monster == 5: 277 | _name = 'troll' 278 | elif _monster == 6: 279 | _name = 'lolcat' 280 | elif _monster == 7: 281 | _name = 'ceiling cat' 282 | elif _monster == 8: 283 | _name = 'console cowboy' 284 | elif _monster == 9: 285 | _name = 'silk warrior' 286 | elif _monster == 10: 287 | _name = 'naive garbage collector' 288 | 289 | _m = MonsterFactory.get_monster_by_name(self.dm, _name, 0, 0) 290 | _m.curr_level = self.level_num 291 | return _m 292 | 293 | def __set_entry_spot(self): 294 | while True: 295 | r = randrange(1, self.lvl_length-1) 296 | c = randrange(1, self.lvl_width-1) 297 | 298 | if self.is_clear(r, c): 299 | break 300 | self.entrance = (r, c) 301 | -------------------------------------------------------------------------------- /src/DisjointSet.py: -------------------------------------------------------------------------------- 1 | # Copyright 2010 by Dana Larose 2 | 3 | # This file is part of crashRun. 4 | 5 | # crashRun is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | 10 | # crashRun is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | 15 | # You should have received a copy of the GNU General Public License 16 | # along with crashRun. If not, see . 17 | 18 | # A simple disjoint set ADT which uses path compression on finds 19 | # to speed things up 20 | 21 | class DSNode: 22 | def __init__(self, value): 23 | self.parent = self 24 | self.rank = 0 25 | self.value = value 26 | 27 | def _find(node): 28 | if node.parent == node: 29 | return node 30 | else: 31 | node.parent = find(node.parent) 32 | return node.parent 33 | 34 | def find(node): 35 | if node.parent == node: 36 | return node 37 | else: 38 | _n = node.parent 39 | while _n.parent != _n: 40 | _n = _n.parent 41 | node.parent = _n 42 | return _n 43 | 44 | def union(n1, n2): 45 | _root1 = find(n1) 46 | _root2 = find(n2) 47 | if _root1.rank > _root2.rank: 48 | _root2.parent = _root1 49 | elif _root1.rank < _root2.rank: 50 | _root1.parent = _root2 51 | else: 52 | _root2.parent = _root1 53 | _root1.rank += 1 54 | 55 | def split_sets(_nodes): 56 | _sets = {} 57 | for _n in _nodes: 58 | _keys = list(_sets.keys()) 59 | if _n.parent.value in _keys: 60 | _sets[_n.parent.value].append(_n) 61 | else: 62 | _p = find(_n) 63 | 64 | if not _p.value in list(_sets.keys()): 65 | _sets[_p.value] = [_p] 66 | _sets[_p.value].append(_n) 67 | 68 | return _sets 69 | -------------------------------------------------------------------------------- /src/FinalComplex.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014 by Dana Larose 2 | 3 | # This file is part of crashRun. 4 | 5 | # crashRun is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | 10 | # crashRun is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | 15 | # You should have received a copy of the GNU General Public License 16 | # along with crashRun. If not, see . 17 | 18 | from random import randrange 19 | 20 | from .GameLevel import GameLevel 21 | from .RLDungeonGenerator import RLDungeonGenerator 22 | from .Terrain import BossTerminal 23 | from .Terrain import TerrainFactory 24 | from .Terrain import DOWN_STAIRS, UP_STAIRS 25 | from .Terrain import FLOOR 26 | 27 | class FinalComplexLevel(GameLevel): 28 | def __init__(self, dm, level_num, length, width): 29 | GameLevel.__init__(self, dm, level_num, length, width, 'final complex') 30 | 31 | def generate_level(self): 32 | self.map = [] 33 | self.generate_map() 34 | 35 | def generate_map(self): 36 | tf = TerrainFactory() 37 | dg = RLDungeonGenerator(self.lvl_width, self.lvl_length) 38 | dg.generate_map() 39 | self.map = dg.map 40 | 41 | # Add location of the down stairs 42 | p = self.place_sqr(tf.get_terrain_tile(DOWN_STAIRS), FLOOR) 43 | self.entrance = p 44 | 45 | if self.level_num < 18: 46 | p = self.place_sqr(tf.get_terrain_tile(UP_STAIRS), FLOOR) 47 | self.exit = p 48 | else: 49 | self.place_sqr(BossTerminal(), FLOOR) 50 | 51 | def add_monster(self, monster=''): 52 | pass 53 | -------------------------------------------------------------------------------- /src/GamePersistence.py: -------------------------------------------------------------------------------- 1 | # Copyright 2010 by Dana Larose 2 | 3 | # This file is part of crashRun. 4 | 5 | # crashRun is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | 10 | # crashRun is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | 15 | # You should have received a copy of the GNU General Public License 16 | # along with crashRun. If not, see . 17 | 18 | from pickle import Pickler 19 | from pickle import Unpickler 20 | import datetime 21 | from os import listdir 22 | from os import path 23 | from os import remove 24 | import tarfile 25 | 26 | class NoSaveFileFound(Exception): 27 | pass 28 | 29 | def save_preferences(prefs): 30 | _file = open("prefs.txt", "w") 31 | for _key in prefs: 32 | _file.write("%s : %s\n" % (_key, str(prefs[_key]))) 33 | _file.close() 34 | 35 | def get_preferences(): 36 | if not path.exists('prefs.txt'): 37 | _prefs = {"auto unlock doors" : True, "bump to open doors" : True, "enter to clear pause" : True} 38 | save_preferences(_prefs) 39 | else: 40 | _prefs = {} 41 | _file = open("prefs.txt", "r") 42 | for _line in _file.readlines(): 43 | _parts = _line.split(":") 44 | _prefs[_parts[0].strip()] = _parts[1].strip().lower() == "true" 45 | 46 | return _prefs 47 | 48 | def get_level_from_save_obj(level, obj): 49 | _map = obj[0] 50 | _locations = obj[1] 51 | _light_sources = obj[2] 52 | _monsters = obj[3] 53 | _category = obj[4] 54 | _lvl_num = obj[5] 55 | _player_loc = obj[6] 56 | _cameras = obj[7] 57 | _length = len(_map) 58 | _width = len(_map[0]) 59 | _sec_lock = obj[8] 60 | _subnet_nodes = obj[9] 61 | _cameras_active = obj[10] 62 | _security = obj[11] 63 | 64 | level.map = _map 65 | level.dungeon_loc = _locations 66 | level.light_sources = _light_sources 67 | level.cameras = _cameras 68 | level.security_lockdown = _sec_lock 69 | level.subnet_nodes = _subnet_nodes 70 | level.cameras_active = _cameras_active 71 | level.security_active = _security 72 | level.player_loc = _player_loc 73 | level.level_num = _lvl_num 74 | 75 | for _m in _monsters: 76 | _m.dm = level.dm 77 | level.add_monster_to_dungeon(_m, _m.row, _m.col) 78 | 79 | def get_save_file_name(username): 80 | _filename = username + '.crsf' 81 | 82 | return _filename 83 | 84 | def clean_up_files(username, save_file): 85 | try: 86 | remove(save_file) 87 | except OSError: 88 | pass 89 | # Don't really need to do anything, this happens 90 | # when the player is killed before he saves 91 | [remove(_file) for _file in listdir('.') if _file.startswith(username + '_')] 92 | 93 | def load_level(username, level_num): 94 | try: 95 | f = open(username + '_' + str(level_num), 'rb') 96 | up = Unpickler(f) 97 | lvl_obj = up.load() 98 | f.close() 99 | except IOError: 100 | raise NoSaveFileFound() 101 | 102 | return lvl_obj 103 | 104 | def load_saved_game(username): 105 | try: 106 | unpack_files(username) 107 | 108 | f = open(username + '.crsf','rb') 109 | up = Unpickler(f) 110 | stuff = up.load() 111 | f.close() 112 | except IOError: 113 | raise NoSaveFileFound() 114 | 115 | return stuff 116 | 117 | def pack_files(username): 118 | _tf = tarfile.open(username + '.crsg','w:gz') 119 | _filename = get_save_file_name(username) 120 | _tf.add(_filename) 121 | 122 | for _file in listdir('.'): 123 | if _file.startswith(username + '_'): 124 | _tf.add(_file) 125 | 126 | _tf.close() 127 | clean_up_files(username, _filename) 128 | 129 | def split_score_file_line(line): 130 | line = line.strip() 131 | _start = line.find(' ') 132 | _items = [int(line[:_start])] 133 | _next = line.find(' ', _start+1) 134 | _items.append(line[_start+1:_next]) 135 | _start = _next + 1 136 | _next = line.find(' ', _start) 137 | _items.append(line[_start:_next]) 138 | _items.append(line[_next+1:]) 139 | 140 | return _items 141 | 142 | def read_scores(): 143 | _lines = [] 144 | try: 145 | f = open('scores','r') 146 | _lines = [split_score_file_line(_line) for _line in f.readlines()] 147 | f.close() 148 | except IOError: 149 | pass 150 | 151 | return _lines 152 | 153 | def write_score(version, score, message): 154 | _dt = datetime.date.today() 155 | _date = "%s-%s-%s" % (str(_dt.year), str(_dt.month), str(_dt.day)) 156 | _lines = read_scores() 157 | _new = [] 158 | 159 | _count = 0 160 | for _line in _lines: 161 | if _line[0] >= score: 162 | _new.append(_line) 163 | _count += 1 164 | else: 165 | break 166 | 167 | _new.append([score, version, _date, message]) 168 | if _count <= 100: 169 | _score = [_count+1, [score, version, _date, message]] 170 | else: 171 | _score = [] 172 | 173 | _new += _lines[_count:] 174 | _new = _new[:100] 175 | 176 | f = open('scores','w') 177 | for _line in _new: 178 | f.write("%d %s %s %s\n" % (_line[0], _line[1], _line[2], _line[3])) 179 | f.close() 180 | 181 | return _score 182 | 183 | def save_game(username, save_obj): 184 | f = open(username + '.crsf','wb') 185 | p = Pickler(f) 186 | p.dump(save_obj) 187 | f.close() 188 | 189 | pack_files(username) 190 | 191 | def unpack_files(username): 192 | _filename = username + '.crsg' 193 | 194 | _tf = tarfile.open(_filename,'r:gz') 195 | _tf.extractall() 196 | _tf.close() 197 | 198 | #remove(_filename) 199 | -------------------------------------------------------------------------------- /src/Keys.py: -------------------------------------------------------------------------------- 1 | # Copyright 2010 by Dana Larose 2 | # 3 | # This file is part of crashRun. 4 | # 5 | # crashRun is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # crashRun is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with crashRun. If not, see . 17 | 18 | from os import path 19 | 20 | _commands = {'PASS':'.', 'SPECIAL_ABILITY':'A', 'BASH':'B', 'DROP':'d', 'SHOOT':'f', 'HACKING':'H', 21 | 'INVENTORY':'i', 'CHAR_INFO':'@', 'PRACTICE_SKILLS':'P', 'QUIT':'Q', 'RELOAD':'r', 'SEARCH':'s', 22 | 'SAVE_AND_EXIT':'S', 'THROW_ITEM':'t', 'USE_ITEM':'U', 'VIEW_SCORES':'V', 'WIELD_WEAPON':'w', 23 | 'EXAMINE':'E', 'FORCE_QUIT_CYBERSPACE':'X','MOVE_DOWN':'>', 'MOVE_UP':'<', 'PICK_UP':',', 24 | 'SHOW_RECENT_MESSAGES':'*', 'SHOW_HELP':'?', 'DEBUG_COMMAND':'#', 'MOVE_WEST':'h', 'MOVE_SOUTH':'j', 25 | 'MOVE_NORTH':'k', 'MOVE_EAST':'l', 'MOVE_NORTHWEST':'y', 'MOVE_NORTHEAST':'u', 'MOVE_SOUTHWEST':'b', 26 | 'MOVE_SOUTHEAST':'n', 'WEAR_ARMOUR':'W', 'ACTION':'a', 'SWAP_WEAPONS':'x', 27 | 'SAVE_WPN_CONFIG':'c', 'REMOVE_ARMOUR':'R', 'TOGGLE_OPTIONS':'O', 'CENTRE_VIEW':'C', 'SHOW_MAP':'M'} 28 | 29 | class KeysSyntaxError(Exception): 30 | pass 31 | 32 | class KeyConfigReader(object): 33 | def __init__(self, version): 34 | self.version = version 35 | self._keys = {} 36 | self.error_count = 0 37 | self.errors = [] 38 | 39 | def check_for_keymap_file(self): 40 | if not path.exists('keys.txt'): 41 | print('key.txt not found, generating default file.') 42 | self.generate_file() 43 | 44 | def check_line(self, token, cmd, line_count): 45 | if cmd not in _commands: 46 | self.flag_error('Unknown command: ' + cmd, line_count) 47 | return False 48 | 49 | if token in self._keys: 50 | self.flag_error('Duplicate key', line_count) 51 | return False 52 | 53 | if cmd in list(self._keys.values()): 54 | self.flag_error('Duplicate command', line_count) 55 | return False 56 | 57 | return True 58 | 59 | def flag_error(self, msg, line_number): 60 | self.errors.append(msg + ', line ' + str(line_number)) 61 | self.error_count += 1 62 | 63 | def generate_file(self): 64 | _file = open('keys.txt', 'w') 65 | _file.write('VERSION: ' + self.version + "\n") 66 | _l = ["'" + _commands[_c] + "': " + _c + "\n" for _c in _commands] 67 | map(_file.write, _l) 68 | _file.close() 69 | 70 | def parse_line(self, line, line_count): 71 | if line == '' or line[0] == '#': 72 | return '', '' 73 | _token, _pos = self.read_token(line) 74 | _value = self.read_value(line[_pos+1:].strip()) 75 | 76 | return _token, _value 77 | 78 | def read_numeric_key(self, line): 79 | _colon = line.find(':') 80 | if _colon == -1: 81 | raise KeysSyntaxError 82 | 83 | try: 84 | return int(line[:_colon]), _colon 85 | except ValueError: 86 | raise KeysSyntaxError 87 | 88 | def read_str_key(self, line): 89 | try: 90 | _key = line[1] 91 | if line[2] != "'": 92 | raise KeysSyntaxError 93 | return _key, line.find(':', 2) 94 | except: 95 | raise KeysSyntaxError 96 | 97 | def read_token(self, line): 98 | if line[0] == "'": 99 | _token, _pos = self.read_str_key(line) 100 | if _pos == -1: 101 | raise KeysSyntaxError 102 | return ord(_token), _pos 103 | elif line[0:7] == 'VERSION': 104 | return 'VERSION', line.find(':', 7) 105 | else: 106 | return self.read_numeric_key(line) 107 | 108 | def read_value(self, line): 109 | _comment = line.find('#') 110 | if _comment > -1: 111 | line = line[:_comment] 112 | return line.strip() 113 | 114 | def read_keys(self): 115 | self.check_for_keymap_file() 116 | 117 | # need to generate file if it doesn't exist 118 | _line_count = 0 119 | _version = '' 120 | with open('keys.txt', 'r') as _lines: 121 | for _line in [l.strip() for l in _lines]: 122 | _line_count += 1 123 | if _line == '' or _line[0] == '#': 124 | continue 125 | try: 126 | _token, _value = self.parse_line(_line.strip(), _line_count) 127 | if _value == '': 128 | self.flag_error('Missing value.', _line_count) 129 | elif _version == '': 130 | if _token != 'VERSION': 131 | self.flag_error('Version not specified.', _line_count) 132 | break 133 | _version = _value 134 | elif self.check_line(_token, _value, _line_count): 135 | self._keys[_token] = _value 136 | except KeysSyntaxError: 137 | self.flag_error('File format error', _line_count) 138 | continue 139 | 140 | return self._keys 141 | -------------------------------------------------------------------------------- /src/Maze.py: -------------------------------------------------------------------------------- 1 | # Copyright 2010 by Dana Larose 2 | 3 | # This file is part of crashRun. 4 | 5 | # crashRun is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | 10 | # crashRun is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | 15 | # You should have received a copy of the GNU General Public License 16 | # along with crashRun. If not, see . 17 | 18 | from random import choice 19 | 20 | from .DisjointSet import DSNode 21 | from .DisjointSet import union 22 | from .DisjointSet import find 23 | from .DisjointSet import split_sets 24 | from .Terrain import TerrainFactory 25 | from .Terrain import CYBERSPACE_WALL 26 | from .Terrain import CYBERSPACE_FLOOR 27 | 28 | class Maze(object): 29 | def __init__(self, length, width): 30 | self.length = length 31 | self.width = width 32 | if self.width % 2 == 0: self.width -= 1 33 | if self.length % 2 == 0: self.length -= 1 34 | self.map = [] 35 | self.__tf = TerrainFactory() 36 | self.__ds_nodes = [] 37 | self.__wall = self.__tf.get_terrain_tile(CYBERSPACE_WALL) 38 | self.__floor = self.__tf.get_terrain_tile(CYBERSPACE_FLOOR) 39 | 40 | self.__gen_initial_map() 41 | 42 | def __gen_initial_map(self): 43 | for r in range(self.length): 44 | if r % 2 == 0: 45 | self.map.append([self.__wall] * self.width) 46 | else: 47 | _row = [] 48 | _ds_row = [] 49 | for c in range(self.width): 50 | if c % 2 == 0: 51 | _row.append(self.__wall) 52 | else: 53 | _row.append(self.__floor) 54 | _ds_row.append(DSNode((r,c))) 55 | self.__ds_nodes.append(_ds_row) 56 | self.map.append(_row) 57 | 58 | def in_bounds(self, row, col): 59 | return row >= 0 and row < self.length and col >= 0 and col < self.width 60 | 61 | def __get_candidate(self, node): 62 | _candidates = [] 63 | _nr = node.value[0] 64 | _nc = node.value[1] 65 | 66 | if self.in_bounds(_nr - 2, _nc) and self.map[_nr-1][_nc].get_type() == CYBERSPACE_WALL: 67 | _c_node = self.__ds_nodes[_nr//2-1][_nc//2] 68 | if find(_c_node) != find(node): 69 | _candidates.append((_c_node, _nr-1, _nc)) 70 | if self.in_bounds(_nr + 2, _nc) and self.map[_nr+1][_nc].get_type() == CYBERSPACE_WALL: 71 | _c_node = self.__ds_nodes[_nr//2+1][_nc//2] 72 | if find(_c_node) != find(node): 73 | _candidates.append((_c_node, _nr+1, _nc)) 74 | if self.in_bounds(_nr, _nc - 2) and self.map[_nr][_nc-1].get_type() == CYBERSPACE_WALL: 75 | _c_node = self.__ds_nodes[_nr//2][_nc//2-1] 76 | if find(_c_node) != find(node): 77 | _candidates.append((_c_node, _nr, _nc-1)) 78 | if self.in_bounds(_nr, _nc + 2) and self.map[_nr][_nc+1].get_type() == CYBERSPACE_WALL: 79 | _c_node = self.__ds_nodes[_nr//2][_nc//2+1] 80 | if find(_c_node) != find(node): 81 | _candidates.append((_c_node, _nr, _nc+1)) 82 | 83 | if len(_candidates) > 0: 84 | return choice(_candidates) 85 | else: 86 | return None 87 | 88 | def gen_map(self): 89 | for _row in self.__ds_nodes: 90 | for _node in _row: 91 | _merge = self.__get_candidate(_node) 92 | if _merge != None: 93 | union(_node, _merge[0]) 94 | self.map[_merge[1]][_merge[2]] = self.__floor 95 | return self.map 96 | 97 | def print_map(self): 98 | for r in range(self.length): 99 | row = '' 100 | for c in range(self.width): 101 | ch = self.map[r][c].get_ch() 102 | row += ' ' if ch == '.' else ch 103 | print(row) 104 | 105 | -------------------------------------------------------------------------------- /src/MessageResolver.py: -------------------------------------------------------------------------------- 1 | # Copyright 2010 by Dana Larose 2 | 3 | # This file is part of crashRun. 4 | 5 | # crashRun is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | 10 | # crashRun is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | 15 | # You should have received a copy of the GNU General Public License 16 | # along with crashRun. If not, see . 17 | 18 | from random import choice 19 | from random import randrange 20 | 21 | from .Util import get_correct_article 22 | from .Util import VisualAlert 23 | 24 | _verbs = { 25 | 'miss': {True:'miss', False:'misses'}, 26 | 'slash' : {True:'slash', False:'slashes'}, 27 | 'etre' : {True:'are', False:'is'} 28 | } 29 | 30 | class MessageResolver(object): 31 | def __init__(self, dm, dui): 32 | self.dm = dm 33 | self.dui = dui 34 | self.true_player = self.dm.get_true_player() 35 | 36 | def monster_killed(self, monster, by_player): 37 | _name = self.resolve_name(monster) 38 | _level = self.dm.dungeon_levels[monster.curr_level] 39 | if by_player: 40 | if _level.is_cyberspace(): 41 | options = ['delete', 'expunge'] 42 | else: 43 | options = ['waste', 'dust', 'kill'] 44 | 45 | if randrange(2) == 0: 46 | _mess = _name + ' is toast.' 47 | else: 48 | _mess = 'You %s %s' % (choice(options), _name) 49 | else: 50 | _mess = _name + ' is killed.' 51 | 52 | alert = VisualAlert(monster.row, monster.col, _mess, '') 53 | alert.show_alert(self.dm, False) 54 | 55 | def parse(self, agent, verb): 56 | if verb not in _verbs: 57 | if agent == self.dm.player: 58 | return verb 59 | else: 60 | return verb + 's' 61 | else: 62 | return _verbs[verb][agent == self.dm.player] 63 | 64 | def pick_up_message(self, agent, item, slot): 65 | _msg = self.resolve_name(agent) + ' ' + self.parse(agent, agent.pick_up_verb()) 66 | _item = item.get_name(1) 67 | 68 | _art = get_correct_article(_item) 69 | _msg += ' up ' 70 | if _art != '': 71 | _msg += _art + ' ' 72 | if slot == '': 73 | _msg += _item + '.' 74 | else: 75 | _msg += _item + ' (' + slot + ').' 76 | 77 | alert = VisualAlert(agent.row, agent.col, _msg, '') 78 | alert.show_alert(self.dm, False) 79 | 80 | def resolve_name(self, agent): 81 | _level = self.dm.dungeon_levels[agent.curr_level] 82 | if agent == self.dm.player: 83 | return 'you' 84 | elif self.dm.is_occupant_visible_to_agent(self.dm.player, agent): 85 | return agent.get_name() 86 | elif self.dm.is_occupant_visible_to_agent(self.dm.get_true_player(), agent): 87 | return agent.get_name() 88 | else: 89 | return 'it' 90 | 91 | def put_on_item(self, agent, item): 92 | _msg = self.resolve_name(agent) + ' ' + self.parse(agent, 'put') 93 | _item = item.get_full_name() 94 | _msg += ' on the ' + item.get_full_name() + '.' 95 | 96 | alert = VisualAlert(agent.row, agent.col, _msg, '') 97 | alert.show_alert(self.dm, False) 98 | 99 | def simple_verb_action(self, subject, text, verbs, pause_for_more=False): 100 | verbs = tuple([self.parse(subject, v) for v in verbs]) 101 | _name = self.resolve_name(subject) 102 | _mess = _name + (text % verbs) 103 | 104 | self.dm.alert_player(subject.row, subject.col, _mess, pause_for_more) 105 | 106 | def shot_message(self, victim): 107 | _verb = self.parse(victim, 'etre') 108 | _mess = self.resolve_name(victim) + ' ' + _verb + ' hit.' 109 | self.dm.alert_player(victim.row, victim.col, _mess) 110 | 111 | def show_hit_message(self, tori, uke, verb): 112 | if tori == self.dm.player: 113 | _mess = 'You ' + self.parse(tori, verb) + ' ' + \ 114 | self.resolve_name(uke) + '!' 115 | elif tori.get_name() in ('the lolcat', 'the ceiling cat'): 116 | _mess = self.resolve_name(tori) + ' has bited you.' 117 | elif uke == self.dm.player: 118 | _mess = self.resolve_name(tori) + ' hits you!' 119 | elif uke == self.true_player: 120 | _mess = self.resolve_name(tori) + ' hits your meatsack!' 121 | else: 122 | _mess = self.resolve_name(tori) + ' hits ' + self.resolve_name(uke) 123 | 124 | self.dm.alert_player(tori.row, tori.col, _mess) 125 | 126 | def show_miss_message(self, tori, uke): 127 | if uke == self.dm.player or uke == self.true_player: 128 | _mess = self.resolve_name(tori) + ' misses you!' 129 | else: 130 | _mess = "%s %s %s!" % (self.resolve_name(tori), self.parse(tori, 'miss'), self.resolve_name(uke)) 131 | 132 | self.dm.alert_player(uke.row, uke.col, _mess) 133 | 134 | def thrown_message(self, item, target): 135 | _mess = item.get_name() + ' hits ' + self.resolve_name(target) + '.' 136 | self.dm.alert_player(target.row, target.col, _mess) 137 | 138 | -------------------------------------------------------------------------------- /src/Mines.py: -------------------------------------------------------------------------------- 1 | # Copyright 2010 by Dana Larose 2 | 3 | # This file is part of crashRun. 4 | 5 | # crashRun is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | 10 | # crashRun is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | 15 | # You should have received a copy of the GNU General Public License 16 | # along with crashRun. If not, see . 17 | 18 | from random import random 19 | from random import randrange 20 | 21 | from .Agent import ExperimentalHoboInfiltrationDroid41K 22 | from .ca_cave import CA_CaveFactory 23 | from .GameLevel import GameLevel 24 | from .GameLevel import ItemChart 25 | from . import MonsterFactory 26 | from . import Terrain 27 | 28 | class MinesLevel(GameLevel): 29 | def __init__(self, dm, level_num, length, width): 30 | GameLevel.__init__(self, dm, level_num, length, width, 'mines') 31 | 32 | def __add_items_to_level(self): 33 | _chart = ItemChart() 34 | _chart.common_items[0] = ('shotgun shell', 7) 35 | _chart.common_items[1] = ('medkit', 0) 36 | _chart.common_items[2] = ('old fatigues', 0) 37 | _chart.common_items[3] = ('flare', 0) 38 | _chart.common_items[4] = ('baseball bat', 0) 39 | _chart.common_items[5] = ('shotgun shell', 7) 40 | _chart.common_items[6] = ('medkit', 0) 41 | _chart.common_items[7] = ('amphetamine', 5) 42 | _chart.common_items[8] = ('combat boots', 0) 43 | 44 | _chart.uncommon_items[0] = ('army helmet', 0) 45 | _chart.uncommon_items[1] = ('C4 Charge', 0) 46 | _chart.uncommon_items[2] = ('flak jacket', 0) 47 | _chart.uncommon_items[3] = ('riot helmet', 0) 48 | _chart.uncommon_items[4] = ('stimpak', 0) 49 | _chart.uncommon_items[5] = ('battery', 3) 50 | _chart.uncommon_items[6] = ('long leather coat', 0) 51 | _chart.uncommon_items[7] = ('flashlight', 0) 52 | _chart.uncommon_items[8] = ('rubber boots', 0) 53 | _chart.uncommon_items[9] = ('throwing knife', 2) 54 | _chart.uncommon_items[10] = ('9mm clip', 0) 55 | _chart.uncommon_items[11] = ('m1911a1', 0) 56 | _chart.uncommon_items[12] = ('instant coffee', 0) 57 | _chart.uncommon_items[13] = ('leather gloves', 0) 58 | 59 | _chart.rare_items[0] = ('grenade', 3) 60 | _chart.rare_items[1] = ('kevlar vest', 0) 61 | _chart.rare_items[2] = ('riot gear', 0) 62 | _chart.rare_items[3] = ('chainsaw', 0) 63 | _chart.rare_items[4] = ('infra-red goggles', 0) 64 | _chart.rare_items[5] = ('targeting wizard', 0) 65 | _chart.rare_items[6] = ('flash bomb',2) 66 | _chart.rare_items[7] = ('machine gun clip', 0) 67 | _chart.rare_items[8] = ('m16 assault rifle', 0) 68 | _chart.rare_items[9] = ('taser', 0) 69 | 70 | [self.add_item(_chart) for k in range(randrange(5,10))] 71 | 72 | def __get_monster(self): 73 | rnd = randrange(0,32) 74 | if rnd in range(0,5): 75 | return MonsterFactory.get_monster_by_name(self.dm,'extra large cockroach',0,0) 76 | elif rnd in range(5,9): 77 | return MonsterFactory.get_monster_by_name(self.dm,'dust head',0,0) 78 | elif rnd in range(9,13): 79 | return MonsterFactory.get_monster_by_name(self.dm,'enhanced mole',0,0) 80 | elif rnd in range(13,15): 81 | return MonsterFactory.get_monster_by_name(self.dm,'giant bat',0,0) 82 | elif rnd in range(15,17): 83 | return MonsterFactory.get_monster_by_name(self.dm,'roomba',0,0) 84 | elif rnd in range(18,21): 85 | return MonsterFactory.get_monster_by_name(self.dm,'damaged security bot',0,0) 86 | elif rnd in range(22,24): 87 | return MonsterFactory.get_monster_by_name(self.dm,'reanimated maintenance worker',0,0) 88 | elif rnd in range(24,26): 89 | return MonsterFactory.get_monster_by_name(self.dm,'mutant',0,0) 90 | elif rnd in range(26,28): 91 | return MonsterFactory.get_monster_by_name(self.dm,'reanimated unionized maintenance worker',0,0) 92 | elif rnd in range(28,30): 93 | return MonsterFactory.get_monster_by_name(self.dm,'reanimated scientist',0,0) 94 | else: 95 | return MonsterFactory.get_monster_by_name(self.dm,'incinerator',0,0) 96 | 97 | def add_monster(self): 98 | GameLevel.add_monster(self, self.__get_monster()) 99 | 100 | def __add_monsters(self): 101 | for j in range(randrange(15,31)): 102 | self.add_monster() 103 | 104 | def __generate_map(self): 105 | _cf = CA_CaveFactory(self.lvl_length, self.lvl_width) 106 | self.map = _cf.gen_map() 107 | self.entrance = _cf.upStairs 108 | self.exit = _cf.downStairs 109 | 110 | # Copied from OldComplex. I didn't think it was worth creating a super 111 | # class for OldComplex and Mines just to share this one method. 112 | def add_EHID41K(self): 113 | _odds = float(self.level_num - 2) / 4 114 | _r = random() 115 | if _r < _odds: 116 | self.dm.player.remember('EHID41K') 117 | _droid = ExperimentalHoboInfiltrationDroid41K(self.dm, 0, 0) 118 | GameLevel.add_monster(self, _droid) 119 | 120 | def generate_level(self): 121 | self.__generate_map() 122 | for j in range(3): 123 | if randrange(4) == 0: 124 | self.place_sqr(Terrain.ConcussionMine(), Terrain.FLOOR) 125 | self.__add_items_to_level() 126 | self.__add_monsters() 127 | 128 | if not self.dm.player.has_memory('EHID41K'): 129 | self.add_EHID41K() 130 | -------------------------------------------------------------------------------- /src/MiniBoss1.py: -------------------------------------------------------------------------------- 1 | # Copyright 2010 by Dana Larose 2 | 3 | # This file is part of crashRun. 4 | 5 | # crashRun is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | 10 | # crashRun is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | 15 | # You should have received a copy of the GNU General Public License 16 | # along with crashRun. If not, see . 17 | 18 | from random import choice 19 | from random import randrange 20 | 21 | from .ca_cave import CA_CaveFactory 22 | from .GameLevel import GameLevel 23 | from .GameLevel import ItemChart 24 | from . import MonsterFactory 25 | from .NewComplexFactory import NewComplexFactory 26 | from .Robots import Roomba3000 27 | from .Rooms import add_science_complex_rooms 28 | from .Terrain import SpecialDoor 29 | from .Terrain import SecurityCamera 30 | from .Terrain import Terminal 31 | from .Terrain import TerrainFactory 32 | from .Terrain import DOOR 33 | from .Terrain import FLOOR 34 | from .Terrain import OCEAN 35 | from .Terrain import PERM_WALL 36 | from .Terrain import UP_STAIRS 37 | from .Terrain import WALL 38 | 39 | class MiniBoss1Level(GameLevel): 40 | def __init__(self, dm, level_num, length, width): 41 | GameLevel.__init__(self, dm, level_num, length, width, 'mini-boss 1') 42 | 43 | def __vet_door(self, _dir, r, c): 44 | if _dir == 'w': 45 | if c < 2: return False 46 | return self.map[r][c-1].get_type() == FLOOR 47 | if _dir == 'e': 48 | if c > self.width-2: return False 49 | return self.map[r][c+1].get_type() == FLOOR 50 | return False 51 | 52 | def __set_complex_exits(self,e_walls, w_walls): 53 | # set west door 54 | while True: 55 | _w = choice(w_walls) 56 | 57 | if self.__vet_door('w', _w[0]+5, _w[1]+5): 58 | self.map[_w[0]+5][_w[1]+5] = self.__tf.get_terrain_tile(DOOR) 59 | break 60 | 61 | # set east door 62 | while True: 63 | _w = choice(e_walls) 64 | 65 | if self.__vet_door('e', _w[0]+5, _w[1]+5): 66 | self.map[_w[0]+5][_w[1]+5] = self.__tf.get_terrain_tile(DOOR) 67 | break 68 | 69 | def __translate_rooms(self, _ncf): 70 | self.rooms = {} 71 | for _key in list(_ncf.rooms.keys()): 72 | self.rooms.setdefault(_key,[]) 73 | for _floor in _ncf.rooms[_key]: 74 | self.rooms[_key].append((_floor[0]+5, _floor[1]+5)) 75 | 76 | def __generate_complex(self): 77 | _east_walls = [] 78 | _west_walls = [] 79 | 80 | _ncf = NewComplexFactory(50,70,False,True) 81 | _ncf.remove_up_stairs_from_rooms() 82 | _cmap = _ncf.gen_map() 83 | for r in range(self.complex_length): 84 | for c in range(self.complex_width): 85 | if _cmap[r][c].get_type() != OCEAN: 86 | self.map[r+5][c+5] = _cmap[r][c] 87 | if c > 0 and _cmap[r][c-1].get_type() == OCEAN: 88 | _west_walls.append((r,c)) 89 | if c < self.complex_width-1 and _cmap[r][c+1].get_type() == OCEAN: 90 | _east_walls.append((r,c)) 91 | self.__set_complex_exits(_east_walls, _west_walls) 92 | _stairs = _ncf.upStairs 93 | self.entrance = (_stairs[0] + 5, _stairs[1] + 5) 94 | self.__translate_rooms(_ncf) 95 | 96 | def __set_east_wall(self): 97 | _sr = self.length // 2 98 | _sc = self.width - 3 99 | 100 | for r in range(self.length): 101 | for c in range(_sc,self.width): 102 | self.map[r][c] = self.__tf.get_terrain_tile(PERM_WALL) 103 | 104 | self.map[_sr][_sc] = SpecialDoor() 105 | self.map[_sr+1][_sc] = SpecialDoor() 106 | 107 | if self.map[_sr][_sc].get_type() == WALL: 108 | self.map[_sr][_sc] = self.__tf.get_terrain_tile(FLOOR) 109 | if self.map[_sr][_sc-1].get_type() == WALL: 110 | self.map[_sr][_sc-1] = self.__tf.get_terrain_tile(FLOOR) 111 | if self.map[_sr-1][_sc].get_type() == WALL: 112 | self.map[_sr-1][_sc] = self.__tf.get_terrain_tile(FLOOR) 113 | if self.map[_sr-1][_sc-1].get_type() == WALL: 114 | self.map[_sr-1][_sc-1] = self.__tf.get_terrain_tile(FLOOR) 115 | 116 | # For now, add_monster, __get_monster, _add_items_to_level and __add_monsters 117 | # are lifted from ScienceComplex.py. I could have MiniBoss1Level 118 | # inhereit from ScienceComplexLevel, but I'm not sure if this is 119 | # the finalized monster set anyhow. 120 | def __add_items_to_level(self): 121 | _chart = ItemChart() 122 | _chart.common_items[0] = ('shotgun shell', 7) 123 | _chart.common_items[1] = ('medkit', 0) 124 | _chart.common_items[2] = ('flare', 0) 125 | _chart.common_items[3] = ('shotgun shell', 7) 126 | _chart.common_items[4] = ('medkit', 0) 127 | _chart.common_items[5] = ('amphetamine', 5) 128 | _chart.common_items[6] = ('combat boots', 0) 129 | 130 | _chart.uncommon_items[0] = ('army helmet', 0) 131 | _chart.uncommon_items[1] = ('C4 Charge', 0) 132 | _chart.uncommon_items[2] = ('flak jacket', 0) 133 | _chart.uncommon_items[3] = ('riot helmet', 0) 134 | _chart.uncommon_items[4] = ('stimpak', 0) 135 | _chart.uncommon_items[5] = ('battery', 3) 136 | _chart.uncommon_items[6] = ('grenade', 3) 137 | _chart.uncommon_items[7] = ('long leather coat', 0) 138 | _chart.uncommon_items[8] = ('flashlight', 0) 139 | 140 | _chart.rare_items[0] = ('kevlar vest', 0) 141 | _chart.rare_items[1] = ('riot gear', 0) 142 | _chart.rare_items[2] = ('infra-red goggles', 0) 143 | _chart.rare_items[3] = ('targeting wizard', 0) 144 | 145 | [self.add_item(_chart) for k in range(randrange(8,16))] 146 | 147 | def add_monster(self): 148 | _monster = self.__get_monster() 149 | GameLevel.add_monster(self, _monster) 150 | if _monster.get_name(True).startswith('pigoon'): 151 | self.add_pack('pigoon', 2, 4, _monster.row, _monster.col) 152 | 153 | def get_entrance(self): 154 | if not self.entrance: 155 | self.entrance = self.find_up_stairs_loc() 156 | return self.entrance 157 | 158 | def get_exit(self): 159 | if not self.exit: 160 | for r in range(self.lvl_length): 161 | for c in range(self.lvl_width): 162 | _sqr = self.map[r][c] 163 | if isinstance(_sqr, SpecialDoor): 164 | return (r, c) 165 | 166 | def __get_monster(self): 167 | _rnd = randrange(0,23) 168 | if _rnd in range(0,3): 169 | return MonsterFactory.get_monster_by_name(self.dm,'reanimated maintenance worker',0,0) 170 | elif _rnd in range(3,6): 171 | return MonsterFactory.get_monster_by_name(self.dm,'reanimated unionized maintenance worker',0,0) 172 | elif _rnd in range(6,7): 173 | return MonsterFactory.get_monster_by_name(self.dm,'roomba',0,0) 174 | elif _rnd in range(7,10): 175 | return MonsterFactory.get_monster_by_name(self.dm,'wolvog',0,0) 176 | elif _rnd in range(10,13): 177 | return MonsterFactory.get_monster_by_name(self.dm,'pigoon',0,0) 178 | elif _rnd in range(13,16): 179 | return MonsterFactory.get_monster_by_name(self.dm,'beastman',0,0) 180 | elif _rnd in range(16,18): 181 | return MonsterFactory.get_monster_by_name(self.dm,'security bot',0,0) 182 | elif _rnd in range(18,20): 183 | return MonsterFactory.get_monster_by_name(self.dm,'incinerator',0,0) 184 | elif _rnd in range(20,22): 185 | return MonsterFactory.get_monster_by_name(self.dm,'mq1 predator',0,0) 186 | else: 187 | return MonsterFactory.get_monster_by_name(self.dm,'ninja',0,0) 188 | 189 | def __add_monsters(self): 190 | for j in range(randrange(15,31)): 191 | self.add_monster() 192 | 193 | def __add_pools(self): 194 | _start_r = randrange(self.length-15,self.length-5) 195 | _start_c = randrange(4,10) 196 | 197 | for _c in range(_start_c, _start_c + randrange(3,6)): 198 | self.map[_start_r][_c] = self.__tf.get_terrain_tile(OCEAN) 199 | for _c in range(_start_c + randrange(-1,2),_start_c + randrange(3,6)): 200 | self.map[_start_r-1][_c] = self.__tf.get_terrain_tile(OCEAN) 201 | for _c in range(_start_c + randrange(-1,2),_start_c + randrange(3,6)): 202 | self.map[_start_r-2][_c] = self.__tf.get_terrain_tile(OCEAN) 203 | 204 | def check_special_door(self, tile): 205 | _player = self.dm.player 206 | if _player.has_memory("the Roomba 3000 killed"): 207 | tile.unlock() 208 | else: 209 | tile.lock() 210 | 211 | def generate_level(self): 212 | self.map = [] 213 | self.length = 60 214 | self.width = 80 215 | self.complex_length = 50 216 | self.complex_width = 70 217 | 218 | self.__tf = TerrainFactory() 219 | _ca = CA_CaveFactory(self.length, self.width, 0.50) 220 | self.map = _ca.gen_map([False,False]) 221 | self.__generate_complex() 222 | 223 | self.downStairs = '' 224 | add_science_complex_rooms(self.dm, self, self) 225 | self.__set_east_wall() 226 | self.__add_pools() 227 | self.__add_monsters() 228 | self.__add_items_to_level() 229 | 230 | GameLevel.add_monster(self, Roomba3000(self.dm, 0, 0)) 231 | 232 | -------------------------------------------------------------------------------- /src/OldComplex.py: -------------------------------------------------------------------------------- 1 | # Copyright 2010 by Dana Larose 2 | 3 | # This file is part of crashRun. 4 | 5 | # crashRun is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | 10 | # crashRun is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | 15 | # You should have received a copy of the GNU General Public License 16 | # along with crashRun. If not, see . 17 | 18 | from random import random 19 | from random import randrange 20 | 21 | from .Agent import ExperimentalHoboInfiltrationDroid41K 22 | from .GameLevel import GameLevel 23 | from .GameLevel import ItemChart 24 | from . import MonsterFactory 25 | from . import SubnetNode 26 | from . import Terrain 27 | from .Terrain import TerrainFactory 28 | from .Terrain import DOWN_STAIRS 29 | from .Terrain import FLOOR 30 | from .Terrain import SECURITY_CAMERA 31 | from .Terrain import TERMINAL 32 | from .Terrain import UP_STAIRS 33 | from .TowerFactory import TowerFactory 34 | from .Util import AudioAlert 35 | 36 | class OldComplexLevel(GameLevel): 37 | def __init__(self, dm, level_num, length, width): 38 | GameLevel.__init__(self, dm, level_num, length, width, 'old complex') 39 | 40 | def __add_items_to_level(self): 41 | _chart = ItemChart() 42 | _chart.common_items[0] = ('shotgun shell', 7) 43 | _chart.common_items[1] = ('medkit', 0) 44 | _chart.common_items[2] = ('old fatigues', 0) 45 | _chart.common_items[3] = ('flare', 0) 46 | _chart.common_items[4] = ('baseball bat', 0) 47 | _chart.common_items[5] = ('shotgun shell', 7) 48 | _chart.common_items[6] = ('medkit', 0) 49 | 50 | _chart.uncommon_items[0] = ('army helmet', 0) 51 | _chart.uncommon_items[1] = ('amphetamine', 5) 52 | _chart.uncommon_items[2] = ('combat boots', 0) 53 | _chart.uncommon_items[3] = ('lockpick', 0) 54 | _chart.uncommon_items[4] = ('stimpak', 0) 55 | _chart.uncommon_items[5] = ('long leather coat', 0) 56 | _chart.uncommon_items[6] = ('flashlight', 0) 57 | _chart.uncommon_items[7] = ('throwing knife', 2) 58 | _chart.uncommon_items[8] = ('instant coffee', 0) 59 | _chart.uncommon_items[9] = ('leather gloves', 0) 60 | 61 | _chart.rare_items[0] = ('grenade', 3) 62 | _chart.rare_items[1] = ('C4 Charge', 0) 63 | _chart.rare_items[2] = ('flak jacket', 0) 64 | _chart.rare_items[3] = ('chainsaw', 0) 65 | _chart.rare_items[4] = ('battery', 3) 66 | _chart.rare_items[5] = ('grenade', 2) 67 | _chart.rare_items[6] = ('battery', 2) 68 | _chart.rare_items[7] = ('rubber boots', 0) 69 | _chart.rare_items[8] = ('flash bomb', 2) 70 | _chart.rare_items[8] = ('Addidas sneakers', 0) 71 | _chart.rare_items[9] = ('machine gun clip', 0) 72 | _chart.rare_items[10] = ('9mm clip', 0) 73 | _chart.rare_items[11] = ('m1911a1', 0) 74 | _chart.rare_items[12] = ('taser', 0) 75 | 76 | [self.add_item(_chart) for k in range(randrange(5,10))] 77 | 78 | def __add_subnet_nodes(self): 79 | _rnd = randrange(0,6) 80 | 81 | if _rnd < 3: 82 | self.subnet_nodes.append(SubnetNode.LameSubnetNode()) 83 | elif _rnd == 3: 84 | self.subnet_nodes.append(SubnetNode.get_skill_node('Dance')) 85 | elif _rnd == 4: 86 | self.subnet_nodes.append(SubnetNode.StatBuilderNode()) 87 | else: 88 | self.subnet_nodes.append(SubnetNode.get_skill_node()) 89 | 90 | self.subnet_nodes.append(SubnetNode.RobotGrandCentral()) 91 | 92 | def __get_monster(self, _monster_level): 93 | if _monster_level < 4: 94 | rnd = randrange(0, 8) 95 | else: 96 | rnd = randrange(4, 20) 97 | 98 | if rnd in range(0,2): 99 | return MonsterFactory.get_monster_by_name(self.dm,'feral dog', 0, 0) 100 | elif rnd in range(2,4): 101 | return MonsterFactory.get_monster_by_name(self.dm,'junkie', 0, 0) 102 | elif rnd in range(4,6): 103 | return MonsterFactory.get_monster_by_name(self.dm,'extra large cockroach', 0, 0) 104 | elif rnd in range(6,8): 105 | return MonsterFactory.get_monster_by_name(self.dm,'mutant rat', 0, 0) 106 | elif rnd in range(8,10): 107 | return MonsterFactory.get_monster_by_name(self.dm,'dust head', 0, 0) 108 | elif rnd in range(10,12): 109 | return MonsterFactory.get_monster_by_name(self.dm,'mutant mutt', 0, 0) 110 | elif rnd in range(12,14): 111 | return MonsterFactory.get_monster_by_name(self.dm,'damaged security bot', 0, 0) 112 | elif rnd in range(14,17): 113 | return MonsterFactory.get_monster_by_name(self.dm,'mutant', 0, 0) 114 | elif rnd in range(17,19): 115 | return MonsterFactory.get_monster_by_name(self.dm,'reanimated mailroom clerk', 0, 0) 116 | else: 117 | return MonsterFactory.get_monster_by_name(self.dm,'surveillance drone', 0, 0) 118 | 119 | def add_monster(self): 120 | _monster_level = self.level_num 121 | if _monster_level > 2: 122 | rnd = random() 123 | if rnd < 0.05: 124 | _monster_level += 3 125 | elif rnd < 0.10: 126 | _monster_level += 2 127 | elif rnd < 0.20: 128 | _monster_level += 1 129 | elif rnd > 0.95: 130 | _monster_level -= 1 131 | 132 | GameLevel.add_monster(self, self.__get_monster(_monster_level)) 133 | 134 | def __add_monsters(self): 135 | for j in range(randrange(15,31)): 136 | self.add_monster() 137 | 138 | def __bust_up_level(self): 139 | maxDestruction = (500 - self.level_num) // 2 140 | minDestruction = maxDestruction // 2 141 | 142 | l = self.lvl_length - 1 143 | w = self.lvl_width - 1 144 | _tf = Terrain.TerrainFactory() 145 | 146 | for x in range(randrange(minDestruction, maxDestruction)): 147 | r = randrange(1,l) 148 | c = randrange(1,w) 149 | if self.map[r][c].get_type() not in (UP_STAIRS,DOWN_STAIRS,SECURITY_CAMERA,TERMINAL): 150 | self.map[r][c] = _tf.get_terrain_tile(FLOOR) 151 | 152 | def __generate_map(self): 153 | _tower = TowerFactory(self.lvl_length, self.lvl_width, False, False) 154 | self.map = _tower.gen_map() 155 | self.entrance = _tower.upStairs 156 | self.exit = _tower.downStairs 157 | 158 | self.__bust_up_level() 159 | 160 | def add_EHID41K(self): 161 | _odds = float(self.level_num - 2) / 4 162 | _r = random() 163 | if _r < _odds: 164 | self.dm.player.remember('EHID41K') 165 | _droid = ExperimentalHoboInfiltrationDroid41K(self.dm, 0, 0) 166 | GameLevel.add_monster(self, _droid) 167 | 168 | def generate_level(self): 169 | self.__generate_map() 170 | 171 | for j in range(randrange(3,7)): 172 | self.add_feature_to_map(Terrain.Terminal()) 173 | 174 | for j in range(randrange(3,7)): 175 | _cam = Terrain.SecurityCamera(5, True) 176 | self.cameras[j] = _cam 177 | self.add_feature_to_map(_cam) 178 | 179 | # add a few traps, maybe 180 | if self.level_num > 2: 181 | for j in range(3): 182 | if randrange(4) == 0: 183 | self.place_sqr(Terrain.ConcussionMine(), FLOOR) 184 | 185 | self.__add_items_to_level() 186 | self.__add_monsters() 187 | self.__add_subnet_nodes() 188 | 189 | if random() < 0.25: 190 | self.map[self.exit[0]][self.exit[1]].activated = False 191 | 192 | if not self.dm.player.has_memory('EHID41K'): 193 | self.add_EHID41K() 194 | 195 | def dispatch_security_bots(self): 196 | for x in range(randrange(1,6)): 197 | GameLevel.add_monster(self, MonsterFactory.get_monster_by_name(self.dm,'damaged security bot',0,0)) 198 | 199 | def begin_security_lockdown(self): 200 | if self.security_active and not self.security_lockdown: 201 | self.security_lockdown = True 202 | self.disable_lifts() 203 | self.dispatch_security_bots() 204 | if self.dm.player.curr_level == self.level_num: 205 | alert = AudioAlert(self.dm.player.row, self.dm.player.col, 'An alarm begins to sound.', '') 206 | alert.show_alert(self.dm, False) 207 | 208 | for _m in self.monsters: 209 | _m.attitude = 'hostile' 210 | 211 | 212 | -------------------------------------------------------------------------------- /src/PriorityQueue.py: -------------------------------------------------------------------------------- 1 | # Copyright 2010 by Dana Larose 2 | 3 | # This file is part of crashRun. 4 | 5 | # crashRun is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | 10 | # crashRun is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | 15 | # You should have received a copy of the GNU General Public License 16 | # along with crashRun. If not, see . 17 | 18 | class PriorityQueue(object): 19 | def __init__(self): 20 | self.__queue = [] 21 | 22 | def __len__(self): 23 | return len(self.__queue) 24 | 25 | def dump(self): 26 | print(self.__queue) 27 | 28 | def is_empty(self): 29 | if len(self.__queue) == 0: 30 | return True 31 | else: 32 | return False 33 | 34 | def peekAtNextPriority(self): 35 | if len(self.__queue) > 0: 36 | return self.__queue[0][1] 37 | else: 38 | return 0 39 | 40 | def push(self,data,priority): 41 | self.__queue.append((data,priority)) 42 | hole = len(self.__queue) 43 | 44 | while hole > 1 and priority < self.__queue[hole//2-1][1]: 45 | self.__queue[hole-1] = self.__queue[hole//2-1] 46 | hole //= 2 47 | 48 | self.__queue[hole-1] = (data,priority) 49 | 50 | # Unfortunately, this operation is O(n), since the tree isn't a binary search tree, 51 | # it merely maintains the Heap property (for any given node, both of it's children 52 | # must be larger than it). 53 | # 54 | # Not that it doesn't return a value, it only deletes the item with no indication of 55 | # success or failure. 56 | def pluck(self,data): 57 | for j in range(0,len(self.__queue)): 58 | if self.__queue[j][0] == data: 59 | self.pop(j) 60 | break 61 | # O(n) :( 62 | def exists(self,data): 63 | for j in range(len(self.__queue)): 64 | if self.__queue[j][0] == data: 65 | return 1 66 | 67 | return 0 68 | 69 | def pop(self,target=0): 70 | head = self.__queue[target] 71 | 72 | self.__queue[target] = self.__queue[len(self.__queue)-1] 73 | self.__percolate_down(target+1) 74 | 75 | return head[0] 76 | 77 | def __percolate_down(self,hole): 78 | tmp = self.__queue[hole-1] 79 | 80 | while hole * 2 < len(self.__queue): 81 | child = hole * 2 82 | 83 | if child != len(self.__queue) and self.__queue[child][1] < self.__queue[child-1][1]: 84 | child += 1 85 | 86 | if self.__queue[child-1][1] < tmp[1]: 87 | self.__queue[hole-1] = self.__queue[child-1] 88 | else: 89 | break 90 | 91 | hole = child 92 | 93 | self.__queue[hole-1] = tmp 94 | self.__queue.pop() 95 | 96 | -------------------------------------------------------------------------------- /src/Prologue.py: -------------------------------------------------------------------------------- 1 | # Copyright 2010 by Dana Larose 2 | 3 | # This file is part of crashRun. 4 | 5 | # crashRun is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | 10 | # crashRun is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | 15 | # You should have received a copy of the GNU General Public License 16 | # along with crashRun. If not, see . 17 | 18 | from random import random 19 | from random import randrange 20 | 21 | from . import Items 22 | from .Items import ItemFactory 23 | from .GameLevel import GameLevel 24 | from . import MonsterFactory 25 | from . import Terrain 26 | from .Terrain import TerrainFactory 27 | from .Terrain import DOOR 28 | from .Terrain import FLOOR 29 | from .Terrain import WALL 30 | from .Terrain import PERM_WALL 31 | from .Terrain import TREE 32 | from .Terrain import GRASS 33 | from .Terrain import ROAD 34 | from .Terrain import DOWN_STAIRS 35 | from .TowerFactory import TowerFactory 36 | 37 | class Prologue(GameLevel): 38 | def __init__(self, dm): 39 | GameLevel.__init__(self, dm, 0, 30, 70, 'prologue') 40 | 41 | def add_monster(self, monster=''): 42 | rnd = randrange(0,3) 43 | if rnd == 0: 44 | _m = MonsterFactory.get_monster_by_name(self.dm,'turkey vulture',0, 0) 45 | elif rnd == 1: 46 | _m = MonsterFactory.get_monster_by_name(self.dm,'junkie', 0, 0) 47 | else: 48 | _m = MonsterFactory.get_monster_by_name(self.dm,'feral dog', 0, 0) 49 | GameLevel.add_monster(self, _m) 50 | 51 | def set_start_loc_for_player(self): 52 | _row = 15 53 | _col = 4 54 | 55 | if self.dungeon_loc[_row][_col].occupant == '': 56 | return (_row, _col) 57 | else: 58 | # The game placed a monster where we prefer to start the player. 59 | # So put him somewhere else. 60 | for r in (-1, 0, 1): 61 | for c in (-1, 0, 1): 62 | if self.is_clear(_row + r, _col + c): 63 | return (_row+r, _col+c) 64 | 65 | # If we get to this point, we're in a totally improbable configuraiton where 66 | # the usual starting location and all adjacent squares are surrounded. So just 67 | # pick random ones until we find a clear loc 68 | while True: 69 | _row = randrange(1, self.lvl_length - 1) 70 | _col = randrange(1, self.lvl_width - 1) 71 | if self.is_clear(_row, _col): 72 | return (_row, _col) 73 | 74 | def generate_level(self): 75 | self.map = self.__generate_map() 76 | for x in range(1,11): 77 | self.add_monster() 78 | 79 | self.entrance = self.set_start_loc_for_player() 80 | 81 | def __generate_map(self): 82 | _map = [] 83 | 84 | _if = ItemFactory() 85 | _tf = TerrainFactory() 86 | 87 | # make all border squares walls 88 | # This could be moved to a superclass 89 | row = [] 90 | for j in range(0, self.lvl_width): 91 | row.append(_tf.get_terrain_tile(PERM_WALL)) 92 | _map.append(row) 93 | 94 | for r in range(1, self.lvl_length-1): 95 | row = [] 96 | row.append(_tf.get_terrain_tile(PERM_WALL)) 97 | 98 | for c in range(1, self.lvl_width-1): 99 | rnd = random() 100 | 101 | if rnd < 0.50: 102 | row.append(_tf.get_terrain_tile(ROAD)) 103 | elif rnd < 0.90: 104 | row.append(_tf.get_terrain_tile(GRASS)) 105 | else: 106 | row.append(_tf.get_terrain_tile(TREE)) 107 | row.append(_tf.get_terrain_tile(PERM_WALL)) 108 | _map.append(row) 109 | row = [] 110 | for j in range(0, self.lvl_width): 111 | row.append(_tf.get_terrain_tile(PERM_WALL)) 112 | _map.append(row) 113 | 114 | # generate the tower section 115 | _tower = TowerFactory(length = 20, width = 30, top = True, bottom = False) 116 | _tower.gen_map() 117 | 118 | for r in range(0, 20): 119 | for c in range(0, 30): 120 | _row = 10 + r 121 | _col = self.lvl_width- 31 + c 122 | _map[_row][_col] = _tower.get_cell(r,c) 123 | if _map[_row][_col].get_type() == DOOR and random() > 0.6: 124 | _map[_row][_col].broken = True 125 | _map[_row][_col].open = True 126 | 127 | # beat up the tower a bit 128 | for x in range(randrange(100, 200)): 129 | r = 10 + randrange(0,20) 130 | c = self.lvl_width - 31 + randrange(0, 30) 131 | if _map[r][c].get_type() != DOWN_STAIRS: 132 | if random() < 0.75: 133 | _map[r][c] = _tf.get_terrain_tile(ROAD) 134 | else: 135 | _map[r][c] = _tf.get_terrain_tile(GRASS) 136 | 137 | # Add double door main entrance 138 | for r in range(15,25): 139 | if _map[r][self.lvl_width-30].get_type() == FLOOR and _map[r+1][self.lvl_width-30].get_type() == FLOOR: 140 | break 141 | _map[r][self.lvl_width-31] = _tf.get_terrain_tile(DOOR) 142 | _map[r+1][self.lvl_width-31] = _tf.get_terrain_tile(DOOR) 143 | 144 | for c in range(0, 30): 145 | _map[29][self.lvl_width-31+c] = _tf.get_terrain_tile(WALL) 146 | 147 | _box = Items.Box() 148 | _box_placed = False 149 | while not _box_placed: 150 | _col = randrange(self.lvl_width-30, self.lvl_width) 151 | _row = randrange(self.lvl_length-20, self.lvl_length) 152 | if _map[_row][_col].get_type() not in (DOOR, WALL, PERM_WALL, DOWN_STAIRS): 153 | self.add_item_to_sqr(_row, _col, _box) 154 | _box_placed = True 155 | 156 | for x in range(randrange(4)): 157 | _box.add_item(_if.gen_item('amphetamine', 1)) 158 | for x in range(randrange(19)): 159 | _box.add_item(_if.gen_item('shotgun shell', 1)) 160 | for x in range(randrange(4)): 161 | _box.add_item(_if.gen_item('flare', 1)) 162 | if randrange(4) > 2: 163 | _box.add_item(_if.gen_item('medkit', 1)) 164 | 165 | self.exists = [(_tower.downStairs, None)] 166 | 167 | return _map 168 | -------------------------------------------------------------------------------- /src/ProvingGrounds.py: -------------------------------------------------------------------------------- 1 | # Copyright 2010 by Dana Larose 2 | 3 | # This file is part of crashRun. 4 | 5 | # crashRun is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | 10 | # crashRun is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | 15 | # You should have received a copy of the GNU General Public License 16 | # along with crashRun. If not, see . 17 | 18 | from random import choice 19 | from random import randrange 20 | 21 | from .ca_cave import CA_CaveFactory 22 | from .GameLevel import GameLevel 23 | from . import Items 24 | from .Items import ItemFactory 25 | from . import MonsterFactory 26 | from .Rooms import place_item 27 | from .Rooms import place_monster 28 | from . import Terrain 29 | from .Terrain import SpecialFloor 30 | from .Terrain import TerrainFactory 31 | from .Terrain import ACID_POOL 32 | from .Terrain import DOOR 33 | from .Terrain import FLOOR 34 | from .Terrain import PERM_WALL 35 | from .Terrain import TOXIC_WASTE 36 | from .Terrain import UP_STAIRS 37 | from .Terrain import WALL 38 | 39 | class ProvingGroundsLevel(GameLevel): 40 | def __init__(self, dm, level_num, length, width): 41 | GameLevel.__init__(self, dm, level_num, length, width, 'proving grounds') 42 | 43 | def generate_level(self): 44 | self.map = [] 45 | self.generate_map() 46 | 47 | for j in range(randrange(5, 16)): 48 | self.add_monster() 49 | 50 | def find_special_floor_loc(self, direction): 51 | for r in range(self.lvl_length): 52 | for c in range(self.lvl_width): 53 | _sqr = self.map[r][c] 54 | if isinstance(_sqr, Terrain.SpecialFloor) and _sqr.direction == direction: 55 | return (r, c) 56 | 57 | def get_entrance(self): 58 | if not self.entrance: 59 | if self.level_num == 13: 60 | self.entrance = self.find_special_floor_loc('up') 61 | else: 62 | self.entrance = self.find_special_floor_loc('down') 63 | 64 | return self.entrance 65 | 66 | def get_exit(self): 67 | if not self.exit: 68 | if self.level_num == 13: 69 | self.exit = self.find_special_floor_loc('down') 70 | else: 71 | self.exit = self.find_up_stairs_loc() 72 | 73 | return self.exit 74 | 75 | def generate_map(self): 76 | self.tf = TerrainFactory() 77 | 78 | _map = [] 79 | 80 | # initialize map 81 | _ca = CA_CaveFactory(self.lvl_length, self.lvl_width - 10, 0.45) 82 | _cave = _ca.gen_map([False,False]) 83 | 84 | for _row in _cave: 85 | _line = [self.tf.get_terrain_tile(PERM_WALL)] 86 | _line += [self.tf.get_terrain_tile(PERM_WALL) for j in range(4)] 87 | _line += _row 88 | _line += [self.tf.get_terrain_tile(PERM_WALL) for j in range(4)] 89 | _line.append(self.tf.get_terrain_tile(PERM_WALL)) 90 | _map.append(_line) 91 | 92 | self.map = _map 93 | 94 | # clear out middle section of map 95 | for _row in range(8, self.lvl_length - 9): 96 | for _col in range(7, self.lvl_width - 10): 97 | self.map[_row][_col] = self.tf.get_terrain_tile(FLOOR) 98 | 99 | # Now draw the tunnel entrance tunnel 100 | self.draw_entrance_tunnel() 101 | if self.level_num == 13: 102 | self.draw_exit_tunnel() 103 | self.add_buildings() 104 | self.add_toxic_pools() 105 | for j in range(2, 5): 106 | self.place_sqr(Terrain.ConcussionMine(), FLOOR) 107 | 108 | def draw_entrance_tunnel(self): 109 | _row = randrange(5, self.lvl_length-5) 110 | _col = 3 111 | 112 | # direction in the dungeon switches after level 13 113 | entrance_dir = 'up' if self.level_num == 13 else 'down' 114 | self.map[_row][1] = SpecialFloor(entrance_dir) 115 | self.entrance = (_row, 1) 116 | self.map[_row][2] = self.tf.get_terrain_tile(FLOOR) 117 | self.player_start_loc = (_row, 2) 118 | 119 | while self.map[_row][_col].get_type() != FLOOR: 120 | self.map[_row][_col] = self.tf.get_terrain_tile(FLOOR) 121 | if randrange(4) == 0 and _row < self.lvl_width - 5: 122 | _row += 1 123 | self.map[_row][_col] = self.tf.get_terrain_tile(FLOOR) 124 | _col += 1 125 | 126 | def draw_exit_tunnel(self): 127 | _row = randrange(5, self.lvl_length-5) 128 | _col = self.lvl_width - 2 129 | self.map[_row][_col] = SpecialFloor('down') 130 | _col -= 1 131 | 132 | while self.map[_row][_col].get_type() != FLOOR: 133 | self.map[_row][_col] = self.tf.get_terrain_tile(FLOOR) 134 | if randrange(4) == 0 and _row < self.lvl_width - 5: 135 | _row += 1 136 | self.map[_row][_col] = self.tf.get_terrain_tile(FLOOR) 137 | _col -= 1 138 | 139 | def add_pool(self, pool_type): 140 | # get starting point 141 | while True: 142 | _row = randrange(2, self.lvl_length - 2) 143 | _col = randrange(2, self.lvl_width - 2) 144 | if self.map[_row][_col].get_type() == FLOOR: 145 | break 146 | 147 | self.map[_row][_col] = self.tf.get_terrain_tile(pool_type) 148 | for _dr in (-1, 0, 1): 149 | for _dc in (-1, 0, 1): 150 | if self.map[_row + _dr][_col + _dc].get_type() == FLOOR and randrange(5) == 0: 151 | self.map[_row + _dr][_col + _dc] = self.tf.get_terrain_tile(pool_type) 152 | 153 | def add_toxic_pools(self): 154 | for j in range(randrange(1,4)): 155 | self.add_pool(TOXIC_WASTE) 156 | for j in range(randrange(1,4)): 157 | self.add_pool(ACID_POOL) 158 | 159 | def get_rectangular_building(self): 160 | _sqrs = [] 161 | _start_r = randrange(5, self.lvl_length - 10) 162 | _start_c = randrange(15, self.lvl_width - 15) 163 | _length = randrange(4, 8) 164 | _width = randrange(4, 8) 165 | 166 | for c in range(_width): 167 | _sqrs.append([_start_r, _start_c + c, self.tf.get_terrain_tile(WALL)]) 168 | for r in range(1, _length): 169 | _sqrs.append([_start_r+r, _start_c, self.tf.get_terrain_tile(WALL)]) 170 | _sqrs.append([_start_r+r, _start_c + _width - 1, self.tf.get_terrain_tile(WALL)]) 171 | for c in range(1, _width - 1): 172 | _sqrs.append([_start_r+r, _start_c + c, self.tf.get_terrain_tile(FLOOR)]) 173 | for c in range(_width): 174 | _sqrs.append([_start_r + _length, _start_c + c, self.tf.get_terrain_tile(WALL)]) 175 | 176 | # place door 177 | _walls = [_sqr for _sqr in _sqrs if _sqr[2].get_type() == WALL] 178 | while len(_walls) > 0: 179 | _w = choice(_walls) 180 | _walls.remove(_w) 181 | 182 | if _w[0] == _start_r: 183 | if _w[1] == _start_c or _w[1] == _start_c + _width - 1: 184 | continue 185 | if self.map[_w[0]-1][_w[1]].get_type() not in (WALL, PERM_WALL, DOOR): 186 | _w[2] = self.tf.get_terrain_tile(DOOR) 187 | break 188 | if _w[0] == _start_r + _length: 189 | if _w[1] == _start_c or _w[1] == _start_c + _width - 1: 190 | continue 191 | if self.map[_w[0]+1][_w[1]].get_type() not in (WALL, PERM_WALL, DOOR): 192 | _w[2] = self.tf.get_terrain_tile(DOOR) 193 | break 194 | if _w[1] == _start_c: 195 | if _w[0] == _start_r or _w[0] == _start_r + _length: 196 | continue 197 | if self.map[_w[0]][_w[1]-1].get_type() not in (WALL, PERM_WALL, DOOR): 198 | _w[2] = self.tf.get_terrain_tile(DOOR) 199 | break 200 | if _w[1] == _start_c + _width - 1: 201 | if _w[0] == _start_r or _w[0] == _start_r + _length: 202 | continue 203 | if self.map[_w[0]][_w[1]+1].get_type() not in (WALL, PERM_WALL, DOOR): 204 | _w[2] = self.tf.get_terrain_tile(DOOR) 205 | break 206 | 207 | return _sqrs 208 | 209 | # Not time efficient, but developer brain efficient... 210 | def will_overlap(self, buildings, new_building): 211 | _new_sqrs = set([(_p[0],_p[1]) for _p in new_building]) 212 | for _b in buildings: 213 | _sqrs = set([(_p[0],_p[1]) for _p in _b]) 214 | if len(_new_sqrs.intersection(_sqrs)): 215 | return True 216 | 217 | return False 218 | 219 | def make_ambush_building(self, building): 220 | _top_wall = self.lvl_length 221 | _bottom_wall = 0 222 | _left_wall = self.lvl_width 223 | _right_wall = 0 224 | 225 | for _sqr in building: 226 | if _sqr[0] < _top_wall: 227 | _top_wall = _sqr[0] 228 | if _sqr[0] > _bottom_wall: 229 | _bottom_wall = _sqr[0] 230 | if _sqr[1] < _left_wall: 231 | _left_wall = _sqr[1] 232 | if _sqr[1] > _right_wall: 233 | _right_wall = _sqr[1] 234 | if _sqr[2].get_type() == DOOR: 235 | _door = (_sqr[0], _sqr[1]) 236 | 237 | _gt = MonsterFactory.get_monster_by_name(self.dm, "gun turret", 0, 0) 238 | # We want to make the gun turret either straight across from the 239 | # door or at right angles. 240 | if _door[0] == _top_wall: 241 | self.add_monster_to_dungeon(_gt, _bottom_wall - 1, _door[1]) 242 | elif _door[0] == _bottom_wall: 243 | self.add_monster_to_dungeon(_gt, _top_wall + 1, _door[1]) 244 | elif _door[1] == _left_wall: 245 | self.add_monster_to_dungeon(_gt, _door[0], _right_wall - 1) 246 | elif _door[1] == _right_wall: 247 | self.add_monster_to_dungeon(_gt, _door[0], _left_wall + 1) 248 | 249 | def make_barracks(self, building): 250 | for j in range(randrange(2,4)): 251 | _cy = MonsterFactory.get_monster_by_name(self.dm, "cyborg soldier", 0, 0) 252 | place_monster(building, self, _cy) 253 | 254 | _if = ItemFactory() 255 | _box = Items.Box('footlocker') 256 | for j in range(randrange(3)): 257 | _roll = randrange(6) 258 | if _roll == 0: 259 | _box.add_item(_if.get_stack('shotgun shell', 6, True)) 260 | elif _roll == 1: 261 | _box.add_item(_if.get_stack('grenade', 4, True)) 262 | elif _roll == 2: 263 | _box.add_item(_if.get_stack('stimpak', 3, True)) 264 | elif _roll == 3: 265 | _box.add_item(_if.get_stack('machine gun clip', 3, True)) 266 | elif _roll == 4: 267 | _box.add_item(_if.get_stack('9mm clip', 3, True)) 268 | else: 269 | _box.add_item(_if.get_stack('medkit', 3, True)) 270 | place_item(building, self, _box) 271 | 272 | def make_repair_shop(self, building): 273 | _doc = MonsterFactory.get_monster_by_name(self.dm, "repair bot", 0, 0) 274 | place_monster(building, self, _doc) 275 | 276 | for j in range(randrange(2)): 277 | _ed = MonsterFactory.get_monster_by_name(self.dm, "ed-209", 0, 0) 278 | place_monster(building, self, _ed) 279 | for j in range(randrange(1,4)): 280 | _sb = MonsterFactory.get_monster_by_name(self.dm, "security bot", 0, 0) 281 | place_monster(building, self, _sb) 282 | 283 | _if = ItemFactory() 284 | for j in range(randrange(1,4)): 285 | _roll = randrange(10) 286 | if _roll < 7: 287 | _item = _if.get_stack('battery', 3, True) 288 | elif _roll < 9: 289 | _item = _if.gen_item('targeting wizard') 290 | else: 291 | _item = _if.gen_item('icannon') 292 | place_item(building, self, _item) 293 | 294 | def populate_building(self, building): 295 | _roll = randrange(5) 296 | if _roll < 2: 297 | self.make_repair_shop(building) 298 | elif _roll < 4: 299 | self.make_barracks(building) 300 | else: 301 | self.make_ambush_building(building) 302 | 303 | def add_buildings(self): 304 | _buildings = [] 305 | 306 | for j in range(randrange(3,7)): 307 | _building = self.get_rectangular_building() 308 | if not self.will_overlap(_buildings, _building): 309 | _buildings.append(_building) 310 | 311 | for _b in _buildings: 312 | for _s in _b: 313 | self.map[_s[0]][_s[1]] = _s[2] 314 | self.populate_building(_b) 315 | 316 | if self.level_num == 14: 317 | upstairs_loc = choice([sqr for sqr in choice(_buildings) if sqr[2].get_type() == FLOOR]) 318 | stairs = self.tf.get_terrain_tile(UP_STAIRS) 319 | self.map[upstairs_loc[0]][upstairs_loc[1]] = stairs 320 | 321 | def __get_monster(self): 322 | _rnd = randrange(0, 16) 323 | if _rnd in range(0, 2): 324 | _name = 'reanimated unionized maintenance worker' 325 | elif _rnd in range(2, 4): 326 | _name = 'wolvog' 327 | elif _rnd in range(4, 6): 328 | _name = 'security bot' 329 | elif _rnd in range(5, 6): 330 | _name = 'mq1 predator' 331 | elif _rnd in range(6,7): 332 | _name = 'ninja' 333 | elif _rnd in range(7,9): 334 | _name = 'beastman' 335 | elif _rnd in range(9, 12): 336 | _name = 'cyborg soldier' 337 | elif _rnd in range(12, 14): 338 | _name = 'cyborg sergeant' 339 | else: 340 | _name = 'ed-209' 341 | 342 | return MonsterFactory.get_monster_by_name(self.dm, _name, 0, 0) 343 | 344 | def add_monster(self): 345 | _monster = self.__get_monster() 346 | GameLevel.add_monster(self, _monster) 347 | -------------------------------------------------------------------------------- /src/RLDungeonGenerator.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014 by Dana Larose 2 | 3 | # This file is part of crashRun. 4 | 5 | # crashRun is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | 10 | # crashRun is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | 15 | # You should have received a copy of the GNU General Public License 16 | # along with crashRun. If not, see . 17 | 18 | from math import sqrt 19 | from random import random 20 | from random import randrange 21 | from random import choice 22 | 23 | from . import Terrain 24 | from .Terrain import TerrainFactory 25 | from .Terrain import DOOR 26 | from .Terrain import FLOOR 27 | from .Terrain import WALL 28 | 29 | class Room: 30 | def __init__(self, r, c, h, w): 31 | self.row = r 32 | self.col = c 33 | self.height = h 34 | self.width = w 35 | 36 | class RLDungeonGenerator: 37 | def __init__(self, w, h): 38 | self.MAX = 15 # Cutoff for when we want to stop dividing sections 39 | self.width = w 40 | self.height = h 41 | self.leaves = [] 42 | self.map = [] 43 | self.rooms = [] 44 | self.tf = TerrainFactory() 45 | self.floor = self.tf.get_terrain_tile(FLOOR) 46 | self.wall = self.tf.get_terrain_tile(WALL) 47 | 48 | self.generate_initial_map() 49 | 50 | def generate_initial_map(self): 51 | for h in range(self.height): 52 | row = [] 53 | for w in range(self.width): 54 | row.append(self.wall) 55 | 56 | self.map.append(row) 57 | 58 | def random_split(self, min_row, min_col, max_row, max_col): 59 | # We want to keep splitting until the sections get down to the threshold 60 | seg_height = max_row - min_row 61 | seg_width = max_col - min_col 62 | 63 | if seg_height < self.MAX and seg_width < self.MAX: 64 | self.leaves.append((min_row, min_col, max_row, max_col)) 65 | elif seg_height < self.MAX and seg_width >= self.MAX: 66 | self.split_on_vertical(min_row, min_col, max_row, max_col) 67 | elif seg_height >= self.MAX and seg_width < self.MAX: 68 | self.split_on_horizontal(min_row, min_col, max_row, max_col) 69 | else: 70 | if random() < 0.5: 71 | self.split_on_horizontal(min_row, min_col, max_row, max_col) 72 | else: 73 | self.split_on_vertical(min_row, min_col, max_row, max_col) 74 | 75 | def split_on_horizontal(self, min_row, min_col, max_row, max_col): 76 | split = (min_row + max_row) // 2 + choice((-2, -1, 0, 1, 2)) 77 | self.random_split(min_row, min_col, split, max_col) 78 | self.random_split(split + 1, min_col, max_row, max_col) 79 | 80 | def split_on_vertical(self, min_row, min_col, max_row, max_col): 81 | split = (min_col + max_col) // 2 + choice((-2, -1, 0, 1, 2)) 82 | self.random_split(min_row, min_col, max_row, split) 83 | self.random_split(min_row, split + 1, max_row, max_col) 84 | 85 | def carve_rooms(self): 86 | for leaf in self.leaves: 87 | # We don't want to fill in every possible room or the 88 | # dungeon looks too uniform 89 | if random() > 0.80: continue 90 | section_width = leaf[3] - leaf[1] 91 | section_height = leaf[2] - leaf[0] 92 | 93 | # The actual room's height and width will be 60-100% of the 94 | # available section. 95 | room_width = round(randrange(60, 100) / 100 * section_width) 96 | room_height = round(randrange(60, 100) / 100 * section_height) 97 | 98 | # If the room doesn't occupy the entire section we are carving it from, 99 | # 'jiggle' it a bit in the square 100 | if section_height > room_height: 101 | room_start_row = leaf[0] + randrange(section_height - room_height) 102 | else: 103 | room_start_row = leaf[0] 104 | 105 | if section_width > room_width: 106 | room_start_col = leaf[1] + randrange(section_width - room_width) 107 | else: 108 | room_start_col = leaf[1] 109 | 110 | self.rooms.append(Room(room_start_row, room_start_col, room_height, room_width)) 111 | for r in range(room_start_row, room_start_row + room_height): 112 | for c in range(room_start_col, room_start_col + room_width): 113 | self.map[r][c] = self.floor 114 | 115 | def are_rooms_adjacent(self, room1, room2): 116 | adj_rows = [] 117 | adj_cols = [] 118 | for r in range(room1.row, room1.row + room1.height): 119 | if r >= room2.row and r < room2.row + room2.height: 120 | adj_rows.append(r) 121 | 122 | for c in range(room1.col, room1.col + room1.width): 123 | if c >= room2.col and c < room2.col + room2.width: 124 | adj_cols.append(c) 125 | 126 | return (adj_rows, adj_cols) 127 | 128 | def distance_between_rooms(self, room1, room2): 129 | centre1 = (room1.row + room1.height // 2, room1.col + room1.width // 2) 130 | centre2 = (room2.row + room2.height // 2, room2.col + room2.width // 2) 131 | 132 | return sqrt((centre1[0] - centre2[0]) ** 2 + (centre1[1] - centre2[1]) ** 2) 133 | 134 | def carve_corridor_between_rooms(self, room1, room2): 135 | if room2[2] == 'rows': 136 | row = choice(room2[1]) 137 | # Figure out which room is to the left of the other 138 | if room1.col + room1.width < room2[0].col: 139 | start_col = room1.col + room1.width 140 | end_col = room2[0].col 141 | else: 142 | start_col = room2[0].col + room2[0].width 143 | end_col = room1.col 144 | for c in range(start_col, end_col): 145 | self.map[row][c] = self.floor 146 | 147 | if end_col - start_col >= 4: 148 | self.map[row][start_col] = self.tf.get_terrain_tile(DOOR) 149 | self.map[row][end_col - 1] = self.tf.get_terrain_tile(DOOR) 150 | elif start_col == end_col - 1: 151 | self.map[row][start_col] = self.tf.get_terrain_tile(DOOR) 152 | else: 153 | col = choice(room2[1]) 154 | # Figure out which room is above the other 155 | if room1.row + room1.height < room2[0].row: 156 | start_row = room1.row + room1.height 157 | end_row = room2[0].row 158 | else: 159 | start_row = room2[0].row + room2[0].height 160 | end_row = room1.row 161 | 162 | for r in range(start_row, end_row): 163 | self.map[r][col] = self.floor 164 | 165 | if end_row - start_row >= 4: 166 | self.map[start_row][col] = self.tf.get_terrain_tile(DOOR) 167 | self.map[end_row - 1][col] = self.tf.get_terrain_tile(DOOR) 168 | elif start_row == end_row - 1: 169 | self.map[start_row][col] = self.tf.get_terrain_tile(DOOR) 170 | 171 | # Find two nearby rooms that are in difference groups, draw 172 | # a corridor between them and merge the groups 173 | def find_closest_unconnect_groups(self, groups, room_dict): 174 | shortest_distance = 99999 175 | start = None 176 | start_group = None 177 | nearest = None 178 | 179 | for group in groups: 180 | for room in group: 181 | key = (room.row, room.col) 182 | for other in room_dict[key]: 183 | if not other[0] in group and other[3] < shortest_distance: 184 | shortest_distance = other[3] 185 | start = room 186 | nearest = other 187 | start_group = group 188 | 189 | self.carve_corridor_between_rooms(start, nearest) 190 | 191 | # Merge the groups 192 | other_group = None 193 | for group in groups: 194 | if nearest[0] in group: 195 | other_group = group 196 | break 197 | 198 | start_group += other_group 199 | groups.remove(other_group) 200 | 201 | def connect_rooms(self): 202 | # Build a dictionary containing an entry for each room. Each bucket will 203 | # hold a list of the adjacent rooms, weather they are adjacent along rows or 204 | # columns and the distance between them. 205 | # 206 | # Also build the initial groups (which start of as a list of individual rooms) 207 | groups = [] 208 | room_dict = {} 209 | for room in self.rooms: 210 | key = (room.row, room.col) 211 | room_dict[key] = [] 212 | for other in self.rooms: 213 | other_key = (other.row, other.col) 214 | if key == other_key: continue 215 | adj = self.are_rooms_adjacent(room, other) 216 | if len(adj[0]) > 0: 217 | room_dict[key].append((other, adj[0], 'rows', self.distance_between_rooms(room, other))) 218 | elif len(adj[1]) > 0: 219 | room_dict[key].append((other, adj[1], 'cols', self.distance_between_rooms(room, other))) 220 | 221 | groups.append([room]) 222 | 223 | while len(groups) > 1: 224 | self.find_closest_unconnect_groups(groups, room_dict) 225 | 226 | def generate_map(self): 227 | self.random_split(1, 1, self.height - 1, self.width - 1) 228 | self.carve_rooms() 229 | self.connect_rooms() 230 | 231 | def print_map(self): 232 | for r in range(self.height): 233 | row = '' 234 | for c in range(self.width): 235 | ch = self.map[r][c].get_ch() 236 | row += ' ' if ch == '.' else ch 237 | print(row) 238 | 239 | -------------------------------------------------------------------------------- /src/Rooms.py: -------------------------------------------------------------------------------- 1 | # Copyright 2010 by Dana Larose 2 | 3 | # This file is part of crashRun. 4 | 5 | # crashRun is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | 10 | # crashRun is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | 15 | # You should have received a copy of the GNU General Public License 16 | # along with crashRun. If not, see . 17 | 18 | from random import choice 19 | from random import random 20 | from random import randrange 21 | 22 | from . import Items 23 | from .Items import ItemFactory 24 | from . import MonsterFactory 25 | from .Robots import MoreauBot6000 26 | from .Terrain import Terminal 27 | 28 | def place_item(room, level, item): 29 | while True: 30 | _floor = choice(room) 31 | _loc = level.dungeon_loc[_floor[0]][_floor[1]] 32 | if not level.map[_floor[0]][_floor[1]].is_passable(): 33 | continue 34 | if level.size_of_item_stack(_floor[0], _floor[1]) == 0: 35 | level.add_item_to_sqr(_floor[0], _floor[1], item) 36 | break 37 | 38 | def place_monster(room, level, monster): 39 | while True: 40 | _sqr = choice(room) 41 | if level.is_clear(_sqr[0], _sqr[1]): 42 | monster.row = _sqr[0] 43 | monster.col = _sqr[1] 44 | level.add_monster_to_dungeon(monster, _sqr[0], _sqr[1]) 45 | break 46 | 47 | def place_terminal(room, level): 48 | _t = Terminal() 49 | _floor = choice(room) 50 | _t.row = _floor[0] 51 | _t.col = _floor[1] 52 | level.map[_floor[0]][_floor[1]] = _t 53 | 54 | def place_repair_shop_bots(dm, room, level): 55 | _num_of_bots = int(len(room) * 0.66) 56 | _bot = MonsterFactory.get_monster_by_name(dm,'repair bot', 0, 0) 57 | place_monster(room, level, _bot) 58 | 59 | for j in range(_num_of_bots): 60 | if not room: break 61 | 62 | _roll = randrange(12) 63 | if _roll < 5: 64 | _name = 'damaged security bot' 65 | elif _roll < 8: 66 | _name = 'security bot' 67 | elif _roll < 9: 68 | _name = 'roomba' 69 | elif _roll < 10: 70 | _name = 'incinerator' 71 | elif _roll < 11: 72 | _name = 'surveillance drone' 73 | else: 74 | _name = 'repair bot' 75 | 76 | _bot = MonsterFactory.get_monster_by_name(dm, _name, 0, 0) 77 | _bot.curr_hp = int(_bot.curr_hp * (0.5 + random()/4)) 78 | place_monster(room, level, _bot) 79 | 80 | def place_repair_shop_items(dm, room, level): 81 | _if = ItemFactory() 82 | for j in range(1,randrange(5)): 83 | _roll = randrange(10) 84 | if _roll < 8: 85 | _item = _if.get_stack('battery',5, True) 86 | elif _roll == 8: 87 | _item = _if.gen_item('infra-red goggles', True) 88 | else: 89 | _item = _if.gen_item('targeting wizard', True) 90 | 91 | _floor = choice(room) 92 | level.add_item_to_sqr(_floor[0], _floor[1], _item) 93 | 94 | def place_science_lab_monsters(dm, room, level): 95 | _count = 0 96 | for _floor in room: 97 | _roll = randrange(1,8) 98 | if _roll == 1: 99 | _name = 'mutant' 100 | elif _roll == 2: 101 | _name = 'reanimated maintenance worker' 102 | elif _roll == 3: 103 | _name = 'reanimated unionized maintenance worker' 104 | elif _roll == 4: 105 | _name = 'pigoon' 106 | elif _roll == 5: 107 | _name = 'wolvog' 108 | elif _roll == 6: 109 | _name = 'enhanced mole' 110 | elif _roll == 7: 111 | _name = 'reanimated scientist' 112 | 113 | _m = MonsterFactory.get_monster_by_name(dm, _name, _floor[0], _floor[1]) 114 | level.add_monster_to_dungeon(_m, _floor[0], _floor[1]) 115 | 116 | _count += 1 117 | if _count > 20: break 118 | 119 | def place_science_lab_items(dm, room, level): 120 | _floor = choice(room) 121 | _if = ItemFactory() 122 | for j in range(1,randrange(5)): 123 | _roll = randrange(10) 124 | if _roll < 3: 125 | _item = _if.get_stack('stimpak',3, True) 126 | elif _roll < 6: 127 | _item = _if.get_stack('amphetamine',7, True) 128 | elif _roll < 9: 129 | _item = _if.get_stack('shotgun shell',10, True) 130 | else: 131 | _item = _if.gen_item('infra-red goggles', True) 132 | 133 | _floor = choice(room) 134 | level.add_item_to_sqr(_floor[0], _floor[1], _item) 135 | 136 | def make_science_lab(dm, room, level): 137 | place_terminal(room, level) 138 | place_science_lab_monsters(dm, room, level) 139 | place_science_lab_items(dm, room, level) 140 | 141 | def make_repair_shop(dm, room, level): 142 | place_repair_shop_items(dm, room, level) 143 | place_repair_shop_bots(dm, room, level) 144 | 145 | # Make P-90s and other guns available here 146 | # (when I implement them) 147 | def get_locker_for_minor_armoury(): 148 | _if = ItemFactory() 149 | _box = Items.Box('footlocker') 150 | 151 | for j in range(randrange(4)): 152 | _roll = random() 153 | if _roll < 0.333: 154 | _box.add_item(_if.get_stack('shotgun shell', 8, True)) 155 | elif _roll < 0.666: 156 | _box.add_item(_if.get_stack('grenade', 4, True)) 157 | elif _roll < 0.9: 158 | _box.add_item(_if.get_stack('stimpak', 5, True)) 159 | else: 160 | _box.add_item(_if.gen_item('shotgun', True)) 161 | 162 | return _box 163 | 164 | def place_minor_armoury_monster(dm, room, level): 165 | _roll = randrange(7) 166 | if _roll < 5: 167 | _name = 'security bot' 168 | elif _roll < 6: 169 | _name = 'mq1 predator' 170 | else: 171 | _name = 'ninja' 172 | 173 | _m = MonsterFactory.get_monster_by_name(dm, _name, 0, 0) 174 | place_monster(room, level, _m) 175 | 176 | def make_minor_armoury(dm, room, level): 177 | for j in range(randrange(1,4)): 178 | _box = get_locker_for_minor_armoury() 179 | place_item(room, level, _box) 180 | 181 | for j in range(randrange(3,6)): 182 | place_minor_armoury_monster(dm, room, level) 183 | 184 | def place_medical_lab_monsters(dm, room, level): 185 | _bot = MonsterFactory.get_monster_by_name(dm, 'docbot', 0 ,0) 186 | place_monster(room, level, _bot) 187 | 188 | for j in range(randrange(1,4)): 189 | _bot = MonsterFactory.get_monster_by_name(dm, 'security bot', 0 ,0) 190 | place_monster(room, level, _bot) 191 | 192 | def get_medical_lab_box(): 193 | _if = ItemFactory() 194 | _box = Items.Box('box') 195 | 196 | for j in range(randrange(1,4)): 197 | _roll = random() 198 | if _roll < 0.25: 199 | _box.add_item(_if.get_stack('medkit', 4, True)) 200 | elif _roll < 0.50: 201 | _box.add_item(_if.get_stack('stimpak', 2, True)) 202 | elif _roll < 0.75: 203 | _box.add_item(_if.get_stack('amphetamine', 5, True)) 204 | else: 205 | _box.add_item(_if.get_stack('battery', 3, True)) 206 | 207 | return _box 208 | 209 | def make_medical_lab(dm, room, level): 210 | place_terminal(room, level) 211 | place_medical_lab_monsters(dm, room, level) 212 | for j in range(randrange(1,3)): 213 | _box = get_medical_lab_box() 214 | place_item(room, level, _box) 215 | 216 | def get_moreau_box(): 217 | _if = ItemFactory() 218 | _box = Items.Box('box') 219 | 220 | for j in range(randrange(1,4)): 221 | _roll = randrange(7) 222 | if _roll < 3: 223 | _box.add_item(_if.get_stack('stimpak', 4, True)) 224 | elif _roll < 6: 225 | _box.add_item(_if.get_stack('shotgun shell', 10, True)) 226 | else: 227 | _box.add_item(_if.gen_item('shotgun', True)) 228 | 229 | return _box 230 | 231 | def make_moreau_room(dm, room, level): 232 | place_terminal(room, level) 233 | place_monster(room, level, MoreauBot6000(dm,0,0)) 234 | 235 | for j in range(randrange(1,4)): 236 | place_monster(room, level, MonsterFactory.get_monster_by_name(dm,'beastman',0,0)) 237 | 238 | for j in range(randrange(1,4)): 239 | _box = get_moreau_box() 240 | place_item(room, level, _box) 241 | 242 | def check_for_moreau_room(dm,level): 243 | if dm.player.has_memory('Moreau'): 244 | return False 245 | 246 | _odds = (level.level_num - 7) * 25 247 | if randrange(100) < _odds: 248 | return True 249 | 250 | def make_science_complex_room(dm, rooms, level): 251 | for j in range(randrange(1,4)): 252 | _key = choice(list(rooms.keys())) 253 | _room = rooms[_key] 254 | del rooms[_key] # don't want to use the same room twice! 255 | 256 | if check_for_moreau_room(dm, level): 257 | dm.player.remember('Moreau') 258 | make_moreau_room(dm, _room, level) 259 | else: 260 | _roll = randrange(4) 261 | if _roll == 0: 262 | make_repair_shop(dm, _room, level) 263 | elif _roll == 1: 264 | make_science_lab(dm, _room, level) 265 | elif _roll == 2: 266 | make_minor_armoury(dm, _room, level) 267 | else: 268 | make_medical_lab(dm, _room, level) 269 | 270 | def add_science_complex_rooms(dm, factory, nextLvl): 271 | _num_of_rooms = randrange(1,4) 272 | _rooms = factory.rooms 273 | for _count in range(_num_of_rooms): 274 | make_science_complex_room(dm, _rooms, nextLvl) -------------------------------------------------------------------------------- /src/RumourFactory.py: -------------------------------------------------------------------------------- 1 | # Copyright 2010 by Dana Larose 2 | 3 | # This file is part of crashRun. 4 | 5 | # crashRun is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | 10 | # crashRun is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | 15 | # You should have received a copy of the GNU General Public License 16 | # along with crashRun. If not, see . 17 | 18 | from random import choice 19 | from random import randrange 20 | 21 | class RumourFactory: 22 | _breaker = '#' * 70 23 | 24 | def __read_rumour(self, rfile): 25 | try: 26 | _line = rfile.readline().strip() 27 | _level = int(_line) 28 | _lines = [] 29 | 30 | _line = rfile.readline().strip() 31 | while _line != self._breaker and _line != '': 32 | if _line == '
': _line = ' ' 33 | _lines.append(_line) 34 | _line = rfile.readline().strip() 35 | 36 | return (_level, _lines) 37 | except ValueError: 38 | return () 39 | 40 | def __read_rumours(self, rfile, max_level): 41 | _rumours = [] 42 | _rumour = self.__read_rumour(rfile) 43 | 44 | while _rumour != () and _rumour[0] <= max_level: 45 | _rumours.append(_rumour) 46 | _rumour = self.__read_rumour(rfile) 47 | 48 | return _rumours 49 | 50 | def fetch_rumour(self, max_level): 51 | _rumour_file = open('rumours.txt','r') 52 | _rumours = self.__read_rumours(_rumour_file, max_level) 53 | 54 | return choice(_rumours)[1] 55 | -------------------------------------------------------------------------------- /src/ScienceComplex.py: -------------------------------------------------------------------------------- 1 | # Copyright 2010 by Dana Larose 2 | 3 | # This file is part of crashRun. 4 | 5 | # crashRun is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | 10 | # crashRun is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | 15 | # You should have received a copy of the GNU General Public License 16 | # along with crashRun. If not, see . 17 | 18 | from random import random 19 | from random import randrange 20 | 21 | from .NewComplexFactory import NewComplexFactory 22 | from .GameLevel import GameLevel 23 | from .GameLevel import ItemChart 24 | from . import MonsterFactory 25 | from .Rooms import add_science_complex_rooms 26 | from . import SubnetNode 27 | from . import Terrain 28 | from .Util import AudioAlert 29 | 30 | class ScienceComplexLevel(GameLevel): 31 | def __init__(self, dm, level_num, length, width): 32 | GameLevel.__init__(self, dm, level_num, length, width, 'science complex') 33 | 34 | def __add_subnet_nodes(self): 35 | for j in range(randrange(1, 4)): 36 | _rnd = randrange(8) 37 | 38 | if _rnd == 0: 39 | self.subnet_nodes.append(SubnetNode.get_skill_node('Dance')) 40 | elif _rnd < 4: 41 | self.subnet_nodes.append(SubnetNode.StatBuilderNode()) 42 | else: 43 | self.subnet_nodes.append(SubnetNode.get_skill_node()) 44 | 45 | def __generate_map(self): 46 | self._ncf = NewComplexFactory(self.lvl_length, self.lvl_width, False, False) 47 | self.map = self._ncf.gen_map() 48 | self._ncf.remove_up_stairs_from_rooms() 49 | self.upStairs = self._ncf.upStairs 50 | self.downStairs = self._ncf.downStairs 51 | self.entrance = self.upStairs 52 | self.exit = self.downStairs 53 | 54 | def add_monster(self): 55 | _monster = self.__get_monster() 56 | GameLevel.add_monster(self, _monster) 57 | if _monster.get_name(True).startswith('pigoon'): 58 | self.add_pack('pigoon', 2, 4, _monster.row, _monster.col) 59 | 60 | def __get_monster(self): 61 | _rnd = randrange(0,28) 62 | if _rnd in range(0,3): 63 | _name = 'reanimated maintenance worker' 64 | elif _rnd in range(3,6): 65 | _name = 'reanimated unionized maintenance worker' 66 | elif _rnd in range(6,7): 67 | _name = 'roomba' 68 | elif _rnd in range(7,10): 69 | _name = 'wolvog' 70 | elif _rnd in range(10,13): 71 | _name = 'pigoon' 72 | elif _rnd in range(13,16): 73 | _name = 'beastman' 74 | elif _rnd in range(16,18): 75 | _name = 'security bot' 76 | elif _rnd in range(18,20): 77 | _name = 'incinerator' 78 | elif _rnd in range(20,22): 79 | _name = 'mq1 predator' 80 | elif _rnd in range(22,24): 81 | _name = 'reanimated scientist' 82 | elif _rnd in range(24,26): 83 | _name = 'reanimated mathematician' 84 | else: 85 | _name = 'ninja' 86 | 87 | return MonsterFactory.get_monster_by_name(self.dm, _name, 0, 0) 88 | 89 | def __add_monsters(self): 90 | for j in range(randrange(15,31)): 91 | self.add_monster() 92 | 93 | def __add_items_to_level(self): 94 | _chart = ItemChart() 95 | _chart.common_items[0] = ('shotgun shell', 7) 96 | _chart.common_items[1] = ('medkit', 0) 97 | _chart.common_items[2] = ('flare', 0) 98 | _chart.common_items[3] = ('shotgun shell', 7) 99 | _chart.common_items[4] = ('medkit', 0) 100 | _chart.common_items[5] = ('amphetamine', 5) 101 | _chart.common_items[6] = ('combat boots', 0) 102 | _chart.common_items[7] = ('instant coffee', 0) 103 | 104 | _chart.uncommon_items[0] = ('army helmet', 0) 105 | _chart.uncommon_items[1] = ('C4 Charge', 0) 106 | _chart.uncommon_items[2] = ('flak jacket', 0) 107 | _chart.uncommon_items[3] = ('riot helmet', 0) 108 | _chart.uncommon_items[4] = ('stimpak', 0) 109 | _chart.uncommon_items[5] = ('battery', 3) 110 | _chart.uncommon_items[6] = ('grenade', 3) 111 | _chart.uncommon_items[7] = ('long leather coat', 0) 112 | _chart.uncommon_items[8] = ('flashlight', 0) 113 | _chart.uncommon_items[9] = ('rubber boots', 0) 114 | _chart.uncommon_items[10] = ('throwing knife', 2) 115 | _chart.uncommon_items[11] = ('Addidas sneakers', 0) 116 | _chart.uncommon_items[12] = ('machine gun clip', 0) 117 | _chart.uncommon_items[13] = ('machine gun clip', 0) 118 | _chart.uncommon_items[14] = ('9mm clip', 0) 119 | _chart.uncommon_items[15] = ('m1911a1', 0) 120 | _chart.uncommon_items[16] = ('p90 assault rifle', 0) 121 | _chart.uncommon_items[17] = ('leather gloves', 0) 122 | 123 | _chart.rare_items[0] = ('kevlar vest', 0) 124 | _chart.rare_items[1] = ('riot gear', 0) 125 | _chart.rare_items[2] = ('infra-red goggles', 0) 126 | _chart.rare_items[3] = ('targeting wizard', 0) 127 | _chart.rare_items[4] = ('flash bomb', 2) 128 | _chart.rare_items[5] = ('Nike sneakers', 0) 129 | _chart.rare_items[6] = ('m16 assault rifle', 0) 130 | _chart.rare_items[7] = ('uzi', 0) 131 | _chart.rare_items[8] = ('taser', 0) 132 | 133 | [self.add_item(_chart) for k in range(randrange(5,10))] 134 | 135 | def generate_level(self): 136 | self.__generate_map() 137 | for j in range(3): 138 | if randrange(4) == 0: 139 | self.place_sqr(Terrain.ConcussionMine(), Terrain.FLOOR) 140 | self._ncf.remove_up_stairs_from_rooms() 141 | add_science_complex_rooms(self.dm, self._ncf, self) 142 | self.__add_monsters() 143 | self.__add_items_to_level() 144 | self.__add_subnet_nodes() 145 | 146 | for j in range(randrange(3,7)): 147 | self.add_feature_to_map(Terrain.Terminal()) 148 | 149 | for j in range(randrange(3,7)): 150 | _cam = Terrain.SecurityCamera(5, True) 151 | self.cameras[j] = _cam 152 | self.add_feature_to_map(_cam) 153 | 154 | if random() < 0.25: 155 | self.map[self.downStairs[0]][self.downStairs[1]].activated = False 156 | 157 | def dispatch_security_bots(self): 158 | for x in range(randrange(1,5)): 159 | GameLevel.add_monster(self, MonsterFactory.get_monster_by_name(self.dm,'security bot',0,0)) 160 | 161 | def begin_security_lockdown(self): 162 | if not self.security_lockdown: 163 | self.security_lockdown = True 164 | self.disable_lifts() 165 | self.dispatch_security_bots() 166 | if self.dm.player.curr_level == self.level_num: 167 | alert = AudioAlert(self.dm.player.row, self.dm.player.col, 'An alarm begins to sound.', '') 168 | alert.show_alert(self.dm, False) 169 | 170 | for _m in self.monsters: 171 | _m.attitude = 'hostile' 172 | 173 | -------------------------------------------------------------------------------- /src/Skills.py: -------------------------------------------------------------------------------- 1 | # Copyright 2010 by Dana Larose 2 | 3 | # This file is part of crashRun. 4 | 5 | # crashRun is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | 10 | # crashRun is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | 15 | # You should have received a copy of the GNU General Public License 16 | # along with crashRun. If not, see . 17 | 18 | from copy import copy 19 | 20 | class Skill(object): 21 | def __init__(self,name,category,prereqs = []): 22 | self.__name = name 23 | self.__category = category 24 | self.__prereqs = prereqs 25 | self.__rank = 0 26 | 27 | def get_pre_reqs(self): 28 | return self.__prereqs 29 | 30 | def get_name(self): 31 | return self.__name 32 | 33 | def get_category(self): 34 | return self.__category 35 | 36 | def get_rank(self): 37 | return self.__rank 38 | 39 | def get_rank_name(self): 40 | if self.__rank == 0: return 'unskilled' 41 | if self.__rank == 1: return 'n00b' 42 | if self.__rank == 2: return 'novice' 43 | if self.__rank == 3: return 'advanced' 44 | if self.__rank == 4: return 'guru' 45 | if self.__rank == 5: return 'l337' 46 | if self.__rank == 6: return 'wizard' 47 | 48 | def change_rank(self,rank): 49 | if rank >= 0 and rank <= 6: 50 | self.__rank = rank 51 | 52 | class SkillTable(object): 53 | def __init__(self): 54 | self.__categories = ['Combat','Tech','Subterfuge','Miscellaneous'] 55 | self.__skills = {} 56 | 57 | self.__skills['Guns'] = Skill('Guns','Combat') 58 | self.__skills['Hand-to-Hand'] = Skill('Hand-to-Hand','Combat') 59 | self.__skills['Melee'] = Skill('Melee','Combat') 60 | self.__skills['Thrown'] = Skill('Thrown','Combat') 61 | self.__skills['Two Weapon Fighting'] = Skill('Two Weapon Fighting','Combat') 62 | 63 | self.__skills['Crypto'] = Skill('Crypto','Tech') 64 | self.__skills['Electronics'] = Skill('Electronics','Tech') 65 | self.__skills['Hacking'] = Skill('Hacking','Tech') 66 | self.__skills['Hardware Tech'] = Skill('Hardware Tech','Tech') 67 | self.__skills['Robot Psychology'] = Skill('Robot Psychology','Tech') 68 | self.__skills['Wetware Admin'] = Skill('Wetware Admin','Tech') 69 | 70 | self.__skills['Bomb Defusing'] = Skill('Bomb Defusing','Subterfuge') 71 | self.__skills['Lock Picking'] = Skill('Lock Picking','Subterfuge') 72 | self.__skills['Stealth'] = Skill('Stealth','Subterfuge') 73 | 74 | self.__skills['Dodge'] = Skill('Dodge','Miscellaneous') 75 | self.__skills['First Aid'] = Skill('First Aid','Miscellaneous') 76 | 77 | def add_skill(self, name, category, rank): 78 | self.__skills[name]= Skill(name, category) 79 | self.__skills[name].change_rank(rank) 80 | 81 | def get_categories(self): 82 | for cat in self.__categories: 83 | yield cat 84 | 85 | def get_category(self,category): 86 | keys = list(self.__skills.keys()) 87 | keys.sort() 88 | cat_list = [] 89 | 90 | for k in keys: 91 | if self.__skills[k].get_category() == category: 92 | yield self.__skills[k] 93 | 94 | def get_skill(self, skill): 95 | return copy(self.__skills[skill]) 96 | 97 | def set_skill(self, name, skill): 98 | self.__skills[name].change_rank(skill) 99 | 100 | 101 | -------------------------------------------------------------------------------- /src/Software.py: -------------------------------------------------------------------------------- 1 | # Copyright 2010 by Dana Larose 2 | 3 | # This file is part of crashRun. 4 | 5 | # crashRun is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | 10 | # crashRun is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | 15 | # You should have received a copy of the GNU General Public License 16 | # along with crashRun. If not, see . 17 | 18 | from copy import deepcopy 19 | from random import choice 20 | from random import randrange 21 | 22 | from .Items import BaseItem 23 | from .RumourFactory import RumourFactory 24 | 25 | _artists = {0:'the Postal Service', 1:'ABBA', 2:'the Be Good Tanyas', 3:'R.E.M.', 26 | 4:'Me First & The Gimme Gimmes',5:'Social Distortion', 5:'the Rheostatics', 27 | 5:'Chumbawamba',6:'Green Day',7:'U2',8:'the Ramones',9:'Beck', 28 | 10:'the Weakterthans',11:'Roy Orbison',12:'Dire Straits',13:'Iron Maiden', 29 | 14:'Fatboy Slim',12:'Neko Case',13:'a-ha',14:'the Bangles',15:'Black Flag', 30 | 16:'the Decemberists',17:'Ministry',18:'Cake',19:'Urban Dance Squad', 31 | 17:'Soundgarden',18:'Death In Vegas',19:'Les Dales Hawerchuk', 32 | 20:'Led Zeppelin',21:'Billy joel',22:'Jane\'s Addition',23:'KOMPRESSOR', 33 | 24:'the Tragically Hip',25:'My Life With The Thrill Kill Kult', 34 | 26:'Men At Work',27:'David Bowie'} 35 | 36 | class Software(BaseItem): 37 | def __init__(self, name, level, decrypted, category): 38 | BaseItem.__init__(self, name, 'file', ':', 'grey', 'white', False) 39 | self.name = name 40 | self.level = level 41 | self.decrypted = decrypted 42 | self.effects = [] 43 | self.executing = False 44 | self.category = category 45 | 46 | def execute(self, dm, agent): 47 | self.decrypted = True 48 | self.executing = True 49 | agent.apply_effects_from_equipment() 50 | 51 | def get_name(self, article=False): 52 | return self.name 53 | 54 | def terminate(self, dm, agent): 55 | self.executing = False 56 | agent.remove_effects(self) 57 | 58 | class Antiviral(Software): 59 | def __init__(self, name, level, decrypted, defense): 60 | Software.__init__(self, name, level, decrypted,'antiviral') 61 | self.effects.append(('antiviral',defense,0)) 62 | 63 | class SearchEngine(Software): 64 | def __init__(self, name, level, decrypted): 65 | Software.__init__(self, name, level, decrypted,'search engine') 66 | self.effects.append(('search engine', level, 0)) 67 | 68 | class ICEBreaker(Software): 69 | def __init__(self, name, level, decrypted, attack, damage): 70 | Software.__init__(self, name, level, decrypted, 'ice breaker') 71 | self.effects.append(('cyberspace attack', attack, 0)) 72 | self.effects.append(('cyberspace damage', damage, 0)) 73 | 74 | class Firewall(Software): 75 | def __init__(self, name, level, decrypted, defense): 76 | Software.__init__(self, name, level, decrypted, 'firewall') 77 | self.effects.append(('cyberspace defense', defense, 0)) 78 | 79 | def execute(self, dm, agent): 80 | super(Firewall, self).execute(dm, agent) 81 | agent.calc_cyberspace_ac() 82 | 83 | def terminate(self, dm, agent): 84 | super(Firewall, self).terminate(dm, agent) 85 | agent.calc_cyberspace_ac() 86 | 87 | class MP3(Software): 88 | def __init__(self): 89 | Software.__init__(self, 'mp3 file', 0, True, 'mp3') 90 | self.artist = self.__get_artist() 91 | 92 | def execute(self, dm, agent): 93 | self.decrypted = True 94 | dm.alert_player(agent.row, agent.col, "It's just an mp3 of " + self.artist + ".") 95 | 96 | def __get_artist(self): 97 | return _artists[choice(list(_artists.keys()))] 98 | 99 | class DataFile(Software): 100 | def __init__(self, level): 101 | Software.__init__(self, 'data file', level, True, 'datafile') 102 | self.txt = '' 103 | self.level = level 104 | 105 | def execute(self, dm, agent): 106 | self.decrypted = True 107 | if not self.txt: 108 | _rf = RumourFactory() 109 | self.txt = _rf.fetch_rumour(self.level) 110 | 111 | dm.dui.display_message('You are able to decrypt part of the file:', True) 112 | dm.dui.write_screen(self.txt, False) 113 | dm.dui.wait_for_input() 114 | dm.dui.draw_screen() 115 | 116 | _software = {} 117 | _software['Norton Anti-Virus 27.4'] = Antiviral('Norton Anti-Virus 27.4', 1, True, 3) 118 | _software['ipfw'] = Firewall('ipfw', 1, True, 3) 119 | _software['Camel Eye'] = Firewall("Camel's Eye", 3, True, 5) 120 | _software['Zone Alarm 57.3'] = Firewall('Zone Alarm 57.3', 4, True, 7) 121 | _software['ACME ICE Breaker, Home Edition'] = ICEBreaker('ACME ICE Breaker, Home Edition', 1, True, 1, 1) 122 | _software['Ono-Sendai ICE Breaker Pro 1.0'] = ICEBreaker('Ono-Sendai ICE Breaker Pro 1.0', 2, True, 2, 2) 123 | _software['GNU Emacs (ICE mode) 17.4'] = ICEBreaker('GNU Emacs (ICE mode) 17.4', 4, True, 4, 4) 124 | _software['Portable Search Engine'] = SearchEngine('Portable Search Engine', 1, True) 125 | 126 | def get_software_by_name(name, level): 127 | if name in _software: 128 | return deepcopy(_software[name]) 129 | elif name == 'mp3': 130 | return MP3() 131 | elif name == 'data file': 132 | return DataFile(level) 133 | 134 | if __name__ == '__main__': 135 | _s = get_software_by_name('ipfw', 1) 136 | print(_s.get_name()) 137 | 138 | _s = get_software_by_name('Ono-Sendai ICE Breaker Pro 1.0', 1) 139 | print(_s.get_name()) 140 | 141 | _s = get_software_by_name('mp3', 1) 142 | print(_s.get_name()) 143 | -------------------------------------------------------------------------------- /src/SubnetNode.py: -------------------------------------------------------------------------------- 1 | # Copyright 2010 by Dana Larose 2 | 3 | # This file is part of crashRun. 4 | 5 | # crashRun is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | 10 | # crashRun is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | 15 | # You should have received a copy of the GNU General Public License 16 | # along with crashRun. If not, see . 17 | 18 | from random import choice 19 | from random import randrange 20 | 21 | from .Terrain import TerrainTile 22 | from .Terrain import SUBNET_NODE 23 | 24 | class SubnetNode(TerrainTile): 25 | def __init__(self): 26 | TerrainTile.__init__(self,"'",'orange','black','yellow',1,0,1,0,'subnet node',SUBNET_NODE) 27 | self.visited = False 28 | 29 | class LameSubnetNode(SubnetNode): 30 | def __init__(self): 31 | SubnetNode.__init__(self) 32 | self.__topic = self.__set_topic() 33 | 34 | def __set_topic(self): 35 | _r = randrange(4) 36 | if _r == 0: 37 | return "presentation on Pensky's health benefits program." 38 | elif _r == 1: 39 | return "course on standard filing procedures." 40 | elif _r == 2: 41 | return "session on human/robot sensitivity training." 42 | elif _r == 3: 43 | return "recording of a Pensky shareholder AGM." 44 | 45 | def visit(self, dm, agent): 46 | dm.dui.clear_msg_line() 47 | if not self.visited: 48 | self.visited = True 49 | dm.alert_player(agent.row, agent.col, "You suffer through a boring " + self.__topic) 50 | else: 51 | dm.alert_player(agent.row, agent.col, "You suffer through the same boring " + self.__topic) 52 | dm.alert_player(agent.row, agent.col, "You get even less out of it this time.") 53 | 54 | class RobotGrandCentral(SubnetNode): 55 | def __init__(self): 56 | TerrainTile.__init__(self,"'",'darkblue','black','blue',1,0,1,0,'subnet node',SUBNET_NODE) 57 | 58 | def attempt_to_remote(self, dm, robot): 59 | _hacking = dm.player.skills.get_skill('Hacking').get_rank() 60 | 61 | # For each previous time the robot has bene controlled and thrown off the control, we'll give it a 62 | # bonus to escape control again. 63 | _mod = robot.memory_count("remote controlled") - _hacking 64 | if not robot.saving_throw(_mod): 65 | dm.player_remotes_to_robot(robot) 66 | else: 67 | dm.alert_player(dm.player.row, dm.player.col, "Remote connection refused.") 68 | 69 | def visit(self, dm, agent): 70 | dm.alert_player(agent.row, agent.col, "Accessing directory of online robots.") 71 | robots = dm.dungeon_levels[agent.meatspace_level].get_list_of_robots() 72 | 73 | if len(robots) == 0: 74 | dm.alert_player(agent.row, agent.col, "There are no robots currently online.") 75 | else: 76 | header = ['To initiate remote robot access, please select from currently avaiable bots:'] 77 | menu = [] 78 | count = 0 79 | for r in robots: 80 | ch = chr(ord('a') + count) 81 | menu.append((ch, r.get_name(1) + " " + r.get_serial_number(), ch)) 82 | count += 1 83 | 84 | _continue = True 85 | while _continue: 86 | _choice = dm.dui.ask_menued_question(header, menu) 87 | if _choice == '': 88 | dm.dui.display_message("Nevermind.") 89 | return 90 | else: 91 | _continue = False 92 | _robot = robots[ord(_choice) - ord('a')] 93 | self.attempt_to_remote(dm, _robot) 94 | 95 | def get_dance_node(): 96 | _dn = SkillBuilderNode('Dancing', 'Miscellaneous') 97 | 98 | _r = randrange(3) 99 | if _r == 0: 100 | _dn.desc = "You watch returns of Dancing With The Stars for several virtual hours." 101 | elif _r == 1: 102 | _dn.desc = "Every single Cyd Charisse movie has been downloaded into your brain." 103 | else: 104 | _dn.desc = "You download dozens of DDR strategy guides." 105 | 106 | _dn.visit_msg = "You feel lighter on your toes." 107 | _dn.already_visited_msg = "But you learn no new moves." 108 | 109 | return _dn 110 | 111 | def get_skill_node(skill = ""): 112 | if skill == 'Dance': 113 | return get_dance_node() 114 | 115 | if skill == "": 116 | skill = choice(['Guns','Hand-to-Hand','Melee','Hacking','Crypto','Hardware Tech','Robot Psychology','Wetware Admin', 117 | 'Bomb Defusing','Lock Picking','First Aid','Stealth']) 118 | 119 | if skill == 'Guns': 120 | _sn = SkillBuilderNode(skill, 'Combat') 121 | _sn.desc = "You play a few hundred games of Doom." 122 | _sn.visit_msg = "You feel like a sharpshooter." 123 | _sn.already_visited_msg = "You frag a bunch of dudes and have some fun." 124 | elif skill == 'Hand-to-Hand': 125 | _sn = SkillBuilderNode(skill, 'Combat') 126 | _sn.desc = "You watch every Bruce Lee movie." 127 | if randrange(2) == 0: 128 | _sn.visit_msg = "'I know kung-fu.'" 129 | else: 130 | _sn.visit_msg = "Your new fighting technique is unstoppable." 131 | _sn.already_visited_msg = "You have a craving for dim sum." 132 | elif skill == 'Melee': 133 | _sn = SkillBuilderNode(skill, 'Combat') 134 | _sn.desc = "You play several hundred matches of Soul Calibur." 135 | _sn.visit_msg = "You feel like getting into a fight." 136 | _sn.already_visited_msg = "You don't pick up any new moves." 137 | elif skill == 'Hacking': 138 | _sn = SkillBuilderNode(skill, 'Tech') 139 | _sn.desc = "You find and download every back-issue of 2600." 140 | _sn.visit_msg = "You feel more 1337." 141 | _sn.already_visited_msg = "You mostly notice how poor the grammar is." 142 | elif skill == 'Crypto': 143 | _sn = SkillBuilderNode(skill, 'Tech') 144 | _sn.desc = "You read 'the Code Book' by Simon Singh." 145 | _sn.visit_msg = "You understand why Caesar cyphers suck." 146 | _sn.already_visited_msg = "You don't have any new insights." 147 | elif skill == 'Hardware Tech': 148 | _sn = SkillBuilderNode(skill, 'Tech') 149 | _sn.desc = "You download dozens of hardware tech manuals." 150 | _sn.visit_msg = "You suddenly realize why your toaster always burns your toast." 151 | _sn.already_visited_msg = "But they're mostly duplicates." 152 | elif skill == 'Robot Psychology': 153 | _sn = SkillBuilderNode(skill, 'Tech') 154 | _sn.desc = "You amuse yourself chatting with an Eliza program." 155 | _sn.visit_msg = "You think you understand robots a little better." 156 | _sn.already_visited_msg = "But are sick of talking about your mother." 157 | elif skill == 'Wetware Admin': 158 | _sn = SkillBuilderNode(skill, 'Tech') 159 | _sn.desc = "You access the knowledgebase for your brain OS." 160 | _sn.visit_msg = "You pick up some handy tips on defragging your brain." 161 | _sn.already_visited_msg = "But learn nothing new." 162 | elif skill == 'Bomb Defusing': 163 | _sn = SkillBuilderNode(skill, 'Subterfuge') 164 | _sn.desc = "You take a correspondence course in bomb disposal." 165 | _sn.visit_msg = "You learn some new techniques." 166 | _sn.already_visited_msg = "But you'ld still rather have a robot do it." 167 | elif skill == 'Lock Picking': 168 | _sn = SkillBuilderNode(skill, 'Subterfuge') 169 | _sn.desc = "You read a bunch of lock picking FAQs." 170 | _sn.visit_msg = "You learn some new techniques." 171 | _sn.already_visited_msg = "But learn nothing new." 172 | elif skill == 'Stealth': 173 | _sn = SkillBuilderNode(skill, 'Subterfuge') 174 | _sn.desc = "You play a bunch of Metal Gear Solid games." 175 | _sn.visit_msg = "You feel sneakier." 176 | _sn.already_visited_msg = "But the story completely confuses you." 177 | elif skill == 'First Aid': 178 | _sn = SkillBuilderNode(skill, 'Miscellaneous') 179 | _sn.desc = "You take a first aid course." 180 | _sn.visit_msg = "You understand how to use medkits better." 181 | _sn.already_visited_msg = "But don't feel CPR will be useful right now." 182 | 183 | return _sn 184 | 185 | class SkillBuilderNode(SubnetNode): 186 | def __init__(self, skill, cat): 187 | SubnetNode.__init__(self) 188 | self.skill = skill 189 | self.category = cat 190 | self.desc = "" 191 | self.visit_msg = "" 192 | self.already_visited_msg = "" 193 | 194 | def train(self, dm, agent): 195 | # Some skills (like dancing) may not yet exist in the player's list of skills 196 | try: 197 | _skill = agent.skills.get_skill(self.skill) 198 | agent.skills.set_skill(self.skill, _skill.get_rank()+1) 199 | except KeyError: 200 | agent.skills.add_skill(self.skill, self.category, 1) 201 | 202 | def visit(self, dm, agent): 203 | dm.dui.clear_msg_line() 204 | dm.alert_player(agent.row, agent.col, self.desc) 205 | 206 | if not self.visited: 207 | self.visited = True 208 | self.train(dm, agent) 209 | dm.alert_player(agent.row, agent.col, self.visit_msg) 210 | else: 211 | dm.alert_player(agent.row, agent.col, self.already_visited_msg) 212 | 213 | class StatBuilderNode(SubnetNode): 214 | def __init__(self, stat=''): 215 | SubnetNode.__init__(self) 216 | if stat == '': 217 | self.__stat = choice(('co-ordination','chutzpah','intuition')) 218 | 219 | self.__message = self.__get_message() 220 | 221 | def visit(self, dm, agent): 222 | dm.dui.clear_msg_line() 223 | dm.alert_player(agent.row, agent.col, self.__message) 224 | 225 | if not self.visited: 226 | self.visited = True 227 | self.__train(dm, agent) 228 | else: 229 | dm.alert_player(agent.row, agent.col, "But you don't pick up anything new.") 230 | 231 | def __get_message(self): 232 | if self.__stat == 'co-ordination': 233 | return "You spend a long time playing Wing Commander 18, and feel a bit more co-ordinated." 234 | elif self.__stat == 'chutzpah': 235 | return "You spend a long time practicing speeches in front of a bathroom mirror." 236 | elif self.__stat == 'intuition': 237 | return "You lose many virtual dollars in a poker simulation but hone your instincts a little." 238 | 239 | def __train(self, dm, agent): 240 | _score = agent.stats.get_stat(self.__stat) 241 | 242 | if _score > 19: 243 | dm.alert_player(agent.row, agent.col, "You don't feel you can improve any further in that area.") 244 | else: 245 | agent.stats.change_stat(self.__stat,1) 246 | -------------------------------------------------------------------------------- /src/TowerFactory.py: -------------------------------------------------------------------------------- 1 | # Copyright 2010 by Dana Larose 2 | 3 | # This file is part of crashRun. 4 | 5 | # crashRun is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | 10 | # crashRun is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | 15 | # You should have received a copy of the GNU General Public License 16 | # along with crashRun. If not, see . 17 | 18 | from random import randrange 19 | from . import Terrain 20 | from .Terrain import TerrainFactory 21 | 22 | class FatalSplittingError(Exception): 23 | pass 24 | 25 | # if I create a method called get_sqr, I can factor much code that is common into an abstract DungeonFactory 26 | # class that would be shared with CaveFactory and eventually the other factories I'll be creating 27 | 28 | # More intelligent door placing could perhaps be to track which rooms are connected 29 | # (DisjointSet!) and only place a door if they aren't already in the same set. This 30 | # will avoid situations like: 31 | # 32 | # ########## 33 | # # ## 34 | # # ## 35 | # # + 36 | # # ## 37 | # # ## 38 | # # + 39 | # # ## 40 | # # ## 41 | # ########## 42 | class TowerFactory(object): 43 | __clear = [Terrain.DOOR, Terrain.FLOOR,Terrain.UP_STAIRS,Terrain.DOWN_STAIRS] 44 | __open_hallway_odds = 25 # times (in a hundred) that the end of a hallway should be opened up 45 | __tf = TerrainFactory() 46 | 47 | def __init__(self,length,width,top,bottom): 48 | # subtract two because we generate the 'working area' of the tower, then draw the border around it 49 | self.__length = length - 2 50 | self.__width = width - 2 51 | self.__area = length * width 52 | self.__min_size = int(self.__area * 0.0325) # smallest room size allowed (when to end recursion) 53 | self.__wall = self.__tf.get_terrain_tile(Terrain.WALL) 54 | self.__floor = self.__tf.get_terrain_tile(Terrain.FLOOR) 55 | self.__top = top 56 | self.__bottom = bottom 57 | 58 | self.reset_map() 59 | 60 | self.upStairs = '' 61 | self.downStairs = '' 62 | 63 | def reset_map(self): 64 | self.map = [] 65 | # start the map off all floors 66 | for r in range(self.__length): 67 | row = [] 68 | for c in range(self.__width): 69 | row.append(self.__tf.get_terrain_tile(Terrain.FLOOR)) 70 | self.map.append(row) 71 | 72 | def gen_map(self): 73 | done = False 74 | while not done: 75 | try: 76 | self.__split_map(0,0,self.__length,self.__width) 77 | done = True 78 | except FatalSplittingError: 79 | done = False 80 | self.reset_map() 81 | 82 | self.__set_border() # could be refactored into an abstract DungeonFactory superclass 83 | self.__fix_useless_doors() 84 | 85 | self.lvl_length = self.__length 86 | self.lvl_width = self.__width 87 | 88 | self.__set_stairs() # could be refactored into an abstract DungeonFactory superclass 89 | 90 | return self.map 91 | 92 | def get_cell(self,row,col): 93 | return self.map[row][col] 94 | 95 | def print_grid(self): 96 | for r in range(self.__length): 97 | for c in range(self.__width): 98 | ch = self.map[r][c].get_ch() 99 | 100 | if ch == ' ': 101 | ch = '#' 102 | 103 | print(ch, end="") 104 | print('') 105 | 106 | # refactoring candidate 107 | def set_cell(self,r, c, sqr): 108 | self.map[r][c] = sqr 109 | 110 | def __check_col(self,col,start_r,length): 111 | good = 1 112 | 113 | if start_r > 0: 114 | if col > 0: 115 | if self.map[start_r-1][col-1].get_type() == Terrain.DOOR: 116 | good = 0 117 | if col < self.__width - 1: 118 | if self.map[start_r-1][col+1].get_type() == Terrain.DOOR: 119 | good = 0 120 | 121 | if start_r + length < self.__length - 1: 122 | if col > 0: 123 | if self.map[start_r+length][col-1].get_type() == Terrain.DOOR: 124 | good = 0 125 | if col < self.__width - 1: 126 | if self.map[start_r+length][col+1].get_type() == Terrain.DOOR: 127 | good = 0 128 | 129 | return good 130 | 131 | # A door is good if there is a clear path, straight through: 132 | # 133 | # #+# is good 134 | # 135 | # ### 136 | # +# is bad 137 | # # 138 | def __check_door(self,r,c): 139 | # check north and south 140 | if self.map[r-1][c].get_type() in self.__clear and self.map[r+1][c].get_type() in self.__clear: 141 | return 1 142 | 143 | # check east and west 144 | if self.map[r][c-1].get_type() in self.__clear and self.map[r][c+1].get_type() in self.__clear: 145 | return 1 146 | 147 | # both checks failed so... 148 | return 0 149 | 150 | def __count_adj_doors(self,row,col): 151 | count = -1 # start at -1 because we'll count ourself 152 | 153 | for r in range(-1,2): 154 | for c in range(-1,2): 155 | if self.map[row+r][col+c].get_type() == Terrain.DOOR: 156 | count += 1 157 | 158 | return count 159 | 160 | # The __check_row and __check_call functions ensure that hallway places are up to 161 | # my exacting standards. 162 | # 163 | # Basicly, I wanted to avoid situations like: 164 | # 165 | # # # 166 | # # # 167 | # ######+###+ # 168 | # # # 169 | # ###+####### # 170 | # # # 171 | # 172 | # So, make sure a door placement won't be at a perpendicular wall 173 | def __check_row(self,row,start_c,width): 174 | good = 1 175 | 176 | if start_c > 0: 177 | if row > 0: 178 | if self.map[row-1][start_c-1].get_type() == Terrain.DOOR: 179 | good = 0 180 | if row < self.__length - 1: 181 | if self.map[row+1][start_c-1].get_type() == Terrain.DOOR: 182 | good = 0 183 | 184 | if start_c + width < self.__width - 1: 185 | if row > 0: 186 | if self.map[row-1][start_c+width].get_type() == Terrain.DOOR: 187 | good = 0 188 | if row < self.__length - 1: 189 | if self.map[row+1][start_c+width].get_type() == Terrain.DOOR: 190 | good = 0 191 | 192 | return good 193 | 194 | def __do_h_split(self,start_r,start_c,length,width): 195 | # pick a row 196 | delta = int(0.2 * length) 197 | 198 | x = 0 199 | row = randrange(start_r + delta, start_r - delta + length) 200 | while not self.__check_row(row,start_c,width): 201 | x += 1 202 | row = randrange(start_r + delta, start_r - delta + length) 203 | if x > 100: 204 | raise FatalSplittingError() 205 | 206 | # draw walls along that row 207 | for c in range(width): 208 | self.set_cell(row-1,start_c+c,self.__wall) 209 | self.set_cell(row+1,start_c+c,self.__wall) 210 | 211 | # should we open up the end of the hallway? 212 | if randrange(100) < self.__open_hallway_odds: 213 | # open up left or right? 214 | if randrange(2): 215 | if start_c + c + 1 < self.__width and self.map[row][start_c+c+1].get_type() == Terrain.WALL: 216 | self.map[row][start_c+c+1] = self.__tf.get_terrain_tile(Terrain.FLOOR) 217 | else: 218 | if start_c - 1 > 0 and self.map[row][start_c-1].get_type() == Terrain.WALL: 219 | self.map[row][start_c-1] = self.__tf.get_terrain_tile(Terrain.FLOOR) 220 | 221 | # add doors randomly 222 | c = randrange(0,width) 223 | self.set_cell(row-1,start_c+c,self.__tf.get_terrain_tile(Terrain.DOOR)) 224 | c = randrange(0,width) 225 | self.set_cell(row+1,start_c+c,self.__tf.get_terrain_tile(Terrain.DOOR)) 226 | 227 | # split the new regions 228 | self.__split_map(start_r,start_c, row - 1 - start_r,width) 229 | self.__split_map(row+2,start_c, start_r + length - row - 2,width) 230 | 231 | def __do_v_split(self,start_r,start_c,length,width): 232 | # pick a column 233 | delta = int(0.2 * width) 234 | 235 | x = 0 236 | col = randrange(start_c + delta, start_c - delta + width) 237 | while not self.__check_col(col,start_r,length): 238 | x += 1 239 | col = randrange(start_c + delta, start_c - delta + width) 240 | if x > 100: 241 | raise FatalSplittingError() 242 | 243 | # draw walls along that row 244 | for r in range(length): 245 | self.set_cell(start_r + r,col-1,self.__wall) 246 | self.set_cell(start_r + r,col+1,self.__wall) 247 | 248 | # should we open up the end of the hallway? 249 | if randrange(100) < self.__open_hallway_odds: 250 | # open up top or bottom? 251 | if randrange(2): 252 | if start_r + r + 1 < self.__length and self.map[start_r+r+1][col].get_type() == Terrain.WALL: 253 | self.map[start_r+r+1][col] = self.__tf.get_terrain_tile(Terrain.FLOOR) 254 | else: 255 | if start_r - 1 > 0 and self.map[start_r-1][col].get_type() == Terrain.WALL: 256 | self.map[start_r-1][col] = self.__tf.get_terrain_tile(Terrain.FLOOR) 257 | 258 | # add doors randomly 259 | r = randrange(0,length) 260 | self.set_cell(start_r+r,col-1,self.__tf.get_terrain_tile(Terrain.DOOR)) 261 | r = randrange(0,length) 262 | self.set_cell(start_r+r,col+1,self.__tf.get_terrain_tile(Terrain.DOOR)) 263 | 264 | # split the new regions 265 | self.__split_map(start_r,start_c,length, col - 1 - start_c) 266 | self.__split_map(start_r,col+2,length, start_c + width - col - 2) 267 | 268 | def __fix_door(self,r,c): 269 | # overwrite any doors along the outside of the tower 270 | if c == 1 or c == self.__width - 1 or r == 0 or r == self.__width +1: 271 | self.set_cell(r,c,self.__tf.get_terrain_tile(Terrain.WALL)) 272 | return 273 | 274 | # if there are adjacent doors, overwrite this one 275 | # 276 | # avoids this: 277 | # 278 | # ## 279 | # + 280 | # + 281 | # ## 282 | if self.__count_adj_doors(r,c) > 0: 283 | self.set_cell(r,c,self.__tf.get_terrain_tile(Terrain.WALL)) 284 | return 285 | 286 | # can the west wall be turned into a passageway? 287 | if c > 1: 288 | if self.map[r][c-1].get_type() == Terrain.WALL and self.map[r][c-2].get_type() in self.__clear: 289 | # ensure we are making a passageway that actually goes somewhere 290 | if self.map[r][c+1].get_type() in self.__clear: 291 | self.set_cell(r,c-1,self.__floor) 292 | return 293 | 294 | # can the east wall be turned into a passageway? 295 | if c < self.__width-1: 296 | if self.map[r][c+1].get_type() == Terrain.WALL and self.map[r][c+2].get_type() in self.__clear: 297 | # ensure we are making a passageway that actually goes somewhere 298 | if self.map[r][c-1].get_type() in self.__clear: 299 | self.set_cell(r,c+1,self.__floor) 300 | return 301 | 302 | # can the north wall be turned into a passageway? 303 | if r > 1: 304 | if self.map[r-1][c].get_type() == Terrain.WALL and self.map[r-2][c].get_type() in self.__clear: 305 | # ensure we are making a passageway that actually goes somewhere 306 | if self.map[r+1][c].get_type() in self.__clear: 307 | self.set_cell(r-1,c,self.__floor) 308 | return 309 | 310 | # can the south wall be turned into a passageway? 311 | if r < self.__length-1: 312 | if self.map[r+1][c].get_type() == Terrain.WALL and self.map[r+2][c].get_type() in self.__clear: 313 | # ensure we are making a passageway that actually goes somewhere 314 | if self.map[r-1][c].get_type() in self.__clear: 315 | self.set_cell(r+1,c,self.__floor) 316 | return 317 | 318 | # just in case the other checks fail, turn the door into a wall 319 | # (try changing it into a passageway and see how that looks?) 320 | self.set_cell(r,c,self.__tf.get_terrain_tile(Terrain.WALL)) 321 | 322 | # Check to see if the doors in the tower are useful, avoid situations like: 323 | # 324 | # ############ 325 | # #####+###### 326 | # # # 327 | # # # 328 | # 329 | def __fix_useless_doors(self): 330 | for r in range(1,self.__length-1): 331 | for c in range(1,self.__width-1): 332 | if self.map[r][c].get_type() == Terrain.DOOR and not self.__check_door(r,c): 333 | self.__fix_door(r,c) 334 | 335 | def __set_border(self): 336 | pwall = self.__tf.get_terrain_tile(Terrain.PERM_WALL) 337 | nmap = [] 338 | 339 | 340 | nmap.append( [pwall] * (self.__width + 2)) 341 | for r in range(self.__length): 342 | nmap.append( [pwall] + self.map[r] + [pwall]) 343 | nmap.append( [pwall] * (self.__width + 2)) 344 | 345 | self.__length += 2 346 | self.__width += 2 347 | self.map = nmap 348 | 349 | def __set_staircase(self,stairs): 350 | while 1: 351 | r = randrange(self.__length) 352 | c = randrange(self.__width) 353 | 354 | if self.map[r][c].get_type() == Terrain.FLOOR: 355 | self.set_cell(r,c,stairs) 356 | break 357 | 358 | return (r,c) 359 | 360 | def __set_stairs(self): 361 | if not self.__top: 362 | self.upStairs = self.__set_staircase(self.__tf.get_terrain_tile(Terrain.UP_STAIRS)) 363 | if not self.__bottom: 364 | self.downStairs = self.__set_staircase(self.__tf.get_terrain_tile(Terrain.DOWN_STAIRS)) 365 | 366 | def __split_map(self,start_r,start_c,length,width): 367 | if length * width <= self.__min_size: 368 | return 369 | 370 | # A more clever/interesting way would be to pick an H or V split with 371 | # probably based on relative size. 372 | # Ie., if region is length 80 and width 40, then do 373 | # an H split 66% of the time and V split 33% of the time 374 | if length > width: 375 | self.__do_h_split(start_r,start_c,length,width) 376 | elif width > length: 377 | self.__do_v_split(start_r,start_c,length,width) 378 | else: 379 | r = randrange(0,2) 380 | if r == 0: 381 | self.__do_h_split(start_r,start_c,length,width) 382 | else: 383 | self.__do_v_split(start_r,start_c,length,width) 384 | 385 | -------------------------------------------------------------------------------- /src/Util.py: -------------------------------------------------------------------------------- 1 | # Copyright 2010 by Dana Larose 2 | 3 | # This file is part of crashRun. 4 | 5 | # crashRun is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | 10 | # crashRun is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | 15 | # You should have received a copy of the GNU General Public License 16 | # along with crashRun. If not, see . 17 | 18 | # File for functions which are used in several places but don't have 19 | # an obvious place to belong 20 | 21 | import math 22 | from math import atan2 23 | from math import sqrt 24 | from random import choice 25 | from random import randrange 26 | 27 | # Record-type class for events the player needs to be alerted 28 | # to. Base class is for basic narration-type alerts. 29 | class Alert: 30 | def __init__(self, r, c, msg, alt): 31 | self.row = r 32 | self.col = c 33 | self.message = msg 34 | self.alternate = alt 35 | 36 | def display_message(self, msg, dm, refresh): 37 | _txt = msg[0].upper() + msg[1:] 38 | dm.dui.display_message(_txt) 39 | if refresh: 40 | dm.refresh_player_view() 41 | 42 | # This will eventually be deleted 43 | def show_alert(self, dm, refresh): 44 | self.display_message(self.message, dm, refresh) 45 | 46 | class VisualAlert(Alert): 47 | def show_alert(self, dm, refresh): 48 | _txt = '' 49 | if dm.player.has_condition("blind"): 50 | _txt = self.alternate 51 | elif not dm.can_player_see_location(self.row, self.col, dm.player.curr_level): 52 | _txt = self.alternate 53 | else: 54 | _txt = self.message 55 | 56 | if _txt != '': 57 | self.display_message(_txt, dm, refresh) 58 | 59 | class AudioAlert(Alert): 60 | def show_alert(self, dm, refresh): 61 | d = calc_distance(self.row, self.col, dm.player.row, dm.player.col) 62 | 63 | # picked arbitrarily 64 | if d < 6: 65 | self.display_message(self.message, dm, refresh) 66 | elif self.alternate != '': 67 | self.display_message(self.alternate, dm, refresh) 68 | 69 | class EmptyInventory(Exception): 70 | pass 71 | 72 | class NonePicked(Exception): 73 | pass 74 | 75 | class TurnInterrupted(Exception): 76 | pass 77 | 78 | # Pretty much directly the opitmized version 79 | # of the algorithm from Wikipedia 80 | def bresenham_line(x0, y0, x1, y1): 81 | _pts = [] 82 | _steep = abs(y1 - y0) > abs(x1 - x0) 83 | if _steep: 84 | x0, y0 = y0, x0 85 | x1, y1 = y1, x1 86 | if x0 > x1: 87 | x0, x1 = x1, x0 88 | y0, y1 = y1, y0 89 | 90 | delta_x = x1 - x0 91 | delta_y = abs(y1 - y0) 92 | error = delta_x / 2 93 | delta_err = float(delta_y) / float(delta_x) 94 | y = y0 95 | y_step = 1 if y0 < y1 else -1 96 | for x in range(x0, x1+1): 97 | if _steep: 98 | _pts.append((y, x)) 99 | else: 100 | _pts.append((x, y)) 101 | error -= delta_y 102 | if error < 0: 103 | y += y_step 104 | error += delta_x 105 | return _pts 106 | 107 | def calc_distance(x0, y0, x1, y1): 108 | xd = x0 - x1 109 | yd = y0 - y1 110 | 111 | return int(sqrt(xd * xd + yd * yd)) 112 | 113 | def calc_angle_between(x0, y0, x1, y1): 114 | _ang = atan2(float(y1) - float(y0), float(x1) - float(x0)) * 180 / math.pi 115 | return int(round(_ang)) 116 | 117 | def convert_locations_to_dir(row0, col0, row1, col1): 118 | if row0 == row1: 119 | if col0 < col1: 120 | return 'w' 121 | else: 122 | return 'e' 123 | elif col0 == col1: 124 | if row0 < row1: 125 | return 'n' 126 | else: 127 | return 's' 128 | else: 129 | if row0 < row1 and col0 < col1: 130 | return 'nw' 131 | elif row0 > row1 and col0 > col1: 132 | return 'se' 133 | elif row0 < row1 and col0 > col1: 134 | return 'ne' 135 | else: 136 | return 'sw' 137 | 138 | def get_correct_article(word): 139 | if word[0] in ['0','1','2','3','4','5','6','7','8','9']: 140 | return '' 141 | elif word[-1] == 's': 142 | return 'some' 143 | elif word[0].lower() in ['a','e','i','o','u']: 144 | return 'an' 145 | else: 146 | return 'a' 147 | 148 | _directions = {'n':(-1,0 ), 's':(1,0), 'e':(0, 1), 'w':(0, -1), 'nw':(-1, -1), 149 | 'ne':(-1, 1), 'sw':(1, -1), 'se':(1, 1), '<':(0, 0), '>':(0, 0), '.':(0, 0) } 150 | 151 | def get_direction_tuple(direction): 152 | return _directions[direction] 153 | 154 | def get_rnd_direction_tuple(): 155 | _r = 0 156 | _c = 0 157 | 158 | while _r == 0 and _c == 0: 159 | _r = choice([-1,0,1]) 160 | _c = choice([-1,0,1]) 161 | 162 | return (_r,_c) 163 | 164 | def do_d10_roll(rolls, bonus): 165 | _roll = 0 166 | for x in range(rolls): 167 | r = randrange(10) + bonus 168 | _roll += r 169 | 170 | return _roll 171 | 172 | def do_dN(num_of_dice, sides): 173 | return sum(randrange(sides)+1 for x in range(num_of_dice)) 174 | 175 | def pluralize(word): 176 | if word in ['Armour','Ammunition']: 177 | return word 178 | elif word[-1] == 'x': 179 | return word + 'es' 180 | else: 181 | return word + 's' 182 | 183 | -------------------------------------------------------------------------------- /src/__init__.py: -------------------------------------------------------------------------------- 1 | # This file is empty but has to exist so that python considers the 2 | # files in /src to be a 'package'. Although if there was 3 | # initialization code for the entire package, it could go here. -------------------------------------------------------------------------------- /src/ca_cave.py: -------------------------------------------------------------------------------- 1 | # Copyright 2010 by Dana Larose 2 | 3 | # This file is part of crashRun. 4 | 5 | # crashRun is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | 10 | # crashRun is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | 15 | # You should have received a copy of the GNU General Public License 16 | # along with crashRun. If not, see . 17 | 18 | from .Terrain import TerrainFactory 19 | from .Terrain import FLOOR 20 | from .Terrain import WALL 21 | from .Terrain import PERM_WALL 22 | from .Terrain import UP_STAIRS 23 | from .Terrain import DOWN_STAIRS 24 | from .Terrain import OCEAN 25 | from random import randrange 26 | from .DisjointSet import DSNode 27 | from .DisjointSet import union 28 | from .DisjointSet import find 29 | from .DisjointSet import split_sets 30 | 31 | class CA_CaveFactory: 32 | def __init__(self,length,width,initial_open=0.40): 33 | self.__length = length 34 | self.__width = width 35 | self.__area = length * width 36 | self.__tf = TerrainFactory() 37 | self.map = [] 38 | self.ds_nodes = [] 39 | self.__up_loc = 0 40 | self.center_pt = (int(self.__length/2),int(self.__width/2)) 41 | self.__gen_initial_map(initial_open) 42 | 43 | def set_cell(self,r, c, sqr): 44 | self.map[r][c] = sqr 45 | 46 | # make all border squares walls 47 | # This could be moved to a superclass 48 | def __set_border(self): 49 | for j in range(0,self.__length): 50 | self.map[j][0] = self.__tf.get_terrain_tile(PERM_WALL) 51 | self.map[j][self.__width-1] = self.__tf.get_terrain_tile(PERM_WALL) 52 | 53 | for j in range(0,self.__width): 54 | self.map[0][j] = self.__tf.get_terrain_tile(PERM_WALL) 55 | self.map[self.__length-1][j] = self.__tf.get_terrain_tile(PERM_WALL) 56 | 57 | def __gen_initial_map(self,initial_open): 58 | for r in range(0,self.__length): 59 | row = [] 60 | ds_row = [] 61 | for c in range(0,self.__width): 62 | ds_row.append(DSNode((r,c))) 63 | row.append(self.__tf.get_terrain_tile(WALL)) 64 | self.ds_nodes.append(ds_row) 65 | self.map.append(row) 66 | 67 | open_count = int(self.__area * initial_open) 68 | self.__set_border() 69 | 70 | while open_count > 0: 71 | rand_r = randrange(1,self.__length) 72 | rand_c = randrange(1,self.__width) 73 | 74 | if self.map[rand_r][rand_c].get_type() == WALL: 75 | self.set_cell(rand_r,rand_c,self.__tf.get_terrain_tile(FLOOR)) 76 | open_count -= 1 77 | 78 | def print_grid(self): 79 | x = 0 80 | row = "" 81 | 82 | for r in range(0,self.__length): 83 | for c in range(0,self.__width): 84 | ch = self.map[r][c].get_ch() 85 | if ch == ' ': 86 | ch = '#' 87 | 88 | print(ch, end=' ') 89 | print() 90 | 91 | def __adj_wall_count(self,sr,sc): 92 | count = 0 93 | 94 | for r in (-1,0,1): 95 | for c in (-1,0,1): 96 | if (r != 0 or c != 0) and self.map[(sr + r)][sc + c].get_type() != FLOOR: 97 | count += 1 98 | 99 | return count 100 | 101 | def up_stairs_loc(self): 102 | return self.__up_loc 103 | 104 | def gen_map(self,set_stairs=[]): 105 | for r in range(1,self.__length-1): 106 | for c in range(1,self.__width-1): 107 | self.__update_cell(r,c) 108 | 109 | self.__join_rooms() 110 | self.add_stairs(set_stairs) 111 | self.lvl_width = self.__width 112 | self.lvl_length = self.__length 113 | 114 | return self.map 115 | 116 | def __update_cell(self,r,c): 117 | wall_count = self.__adj_wall_count(r,c) 118 | 119 | if self.map[r][c].get_type() == FLOOR: 120 | if wall_count > 5: 121 | self.set_cell(r,c,self.__tf.get_terrain_tile(WALL)) 122 | elif wall_count < 4: 123 | self.set_cell(r,c,self.__tf.get_terrain_tile(FLOOR)) 124 | 125 | def __join_rooms(self): 126 | # divide the square into equivalence classes 127 | for r in range(1,self.__length-1): 128 | for c in range(1,self.__width-1): 129 | self.__union_adj_sqr(r,c) 130 | 131 | _nodes = [] 132 | for _row in self.ds_nodes: 133 | for _node in _row: 134 | _n = _node.value 135 | if self.map[_n[0]][_n[1]].get_type() == FLOOR: 136 | _nodes.append(_node) 137 | 138 | all_caves = split_sets(_nodes) 139 | 140 | for cave in list(all_caves.keys()): 141 | self.join_points(all_caves[cave][0].value) 142 | 143 | def join_points(self,pt1): 144 | next_pt = pt1 145 | while 1: 146 | dir = self.get_tunnel_dir(pt1,self.center_pt) 147 | move = randrange(0,3) 148 | 149 | if move == 0: 150 | next_pt = (pt1[0] + dir[0],pt1[1]) 151 | elif move == 1: 152 | next_pt = (pt1[0],pt1[1] + dir[1]) 153 | else: 154 | next_pt = (pt1[0] + dir[0],pt1[1] + dir[1]) 155 | 156 | if self.stop_drawing(pt1,next_pt,self.center_pt): 157 | return 158 | 159 | union(self.ds_nodes[next_pt[0]][next_pt[1]], self.ds_nodes[pt1[0]][pt1[1]]) 160 | self.set_cell(next_pt[0],next_pt[1],self.__tf.get_terrain_tile(FLOOR)) 161 | 162 | pt1 = next_pt 163 | 164 | def stop_drawing(self,pt,npt,cpt): 165 | parent_pt = find(self.ds_nodes[pt[0]][pt[1]]) 166 | parent_npt = find(self.ds_nodes[npt[0]][npt[1]]) 167 | parent_cpt = find(self.ds_nodes[cpt[0]][cpt[1]]) 168 | 169 | if parent_npt == parent_cpt: 170 | return True 171 | 172 | if parent_pt != parent_npt and self.map[npt[0]][npt[1]].get_type() == FLOOR: 173 | return True 174 | else: 175 | return False 176 | 177 | def in_bounds(self,pt): 178 | if pt[0] in (0,self.__length-1) or pt[1] in (0,self.__width-1): 179 | return 0 180 | else: 181 | return 1 182 | 183 | def get_tunnel_dir(self,pt1,pt2): 184 | if pt1[0] < pt2[0]: 185 | h_dir = +1 186 | elif pt1[0] > pt2[0]: 187 | h_dir = -1 188 | else: 189 | h_dir = 0 190 | 191 | if pt1[1] < pt2[1]: 192 | v_dir = +1 193 | elif pt1[1] > pt2[1]: 194 | v_dir = -1 195 | else: 196 | v_dir = 0 197 | 198 | return (h_dir,v_dir) 199 | 200 | def add_stairs(self, set_stairs): 201 | while 1: 202 | dr = randrange(1,self.__length-1) 203 | dc = randrange(1,self.__width-1) 204 | ur = randrange(0,self.__length) 205 | uc = randrange(0,self.__width) 206 | 207 | if (dr,dc) != (ur,uc) and self.map[dr][dc].get_type() == FLOOR and self.map[ur][uc].get_type() == FLOOR: 208 | break 209 | 210 | if len(set_stairs) == 0: 211 | _up = True 212 | _down = True 213 | else: 214 | _up = set_stairs[0] 215 | _down = set_stairs[1] 216 | 217 | if _up: 218 | self.__up_loc = (ur,uc) 219 | self.upStairs = (ur,uc) 220 | self.set_cell(ur,uc,self.__tf.get_terrain_tile(UP_STAIRS)) 221 | if _down: 222 | self.__down_loc = (dr,dc) 223 | self.downStairs = (dr,dc) 224 | self.set_cell(dr,dc,self.__tf.get_terrain_tile(DOWN_STAIRS)) 225 | 226 | def __union_adj_sqr(self,sr,sc): 227 | if self.map[sr][sc].get_type() != FLOOR: 228 | return 229 | 230 | loc = (sr,sc) 231 | 232 | for r in (-1,0,1): 233 | for c in (-1,0,1): 234 | if self.map[sr+r][sc+c].get_type() == FLOOR \ 235 | and self.ds_nodes[sr][sc].parent != self.ds_nodes[sr+r][sc+c].parent: 236 | union(self.ds_nodes[sr][sc], self.ds_nodes[sr+r][sc+c]) 237 | 238 | if __name__ == "__main__": 239 | caf = CA_CaveFactory(30,30,0.41) 240 | profile.run("caf.gen_map()") 241 | 242 | caf.print_grid() 243 | 244 | -------------------------------------------------------------------------------- /ttd.txt: -------------------------------------------------------------------------------- 1 | CURRENTLY WORKING ON 2 | - Hacking doesn't appear to be using "get_hacking_bonus()" 3 | - searching beside an already found bomb displays the reveal message again 4 | - check GameLevel end_of_turn() and turn counting in DungeonMaster -- don't want to count turns more than once! 5 | - extra space in message when playing stands on stack of items (ammo, etc) 6 | - when exiting cyberspace, if the player might lose hps but takes 0, the "Attack does no damage" message is displayed 7 | 8 | BUGS 9 | - need a way to stop players from scumming for files in cyberspace 10 | - if backpack is full, and player picks up an item that can stack with something in the player's backpack 11 | the game won't let you add it 12 | - could use a "you are no longer stunned" messaged 13 | - when monsters just stand there, it's 'cause they're scared and the pick-fleeing move stuff is probably fucking up 14 | - LONG STANDING DISPLAY BUG 15 | 16 | FEATURES/IDEAS/NOTES 17 | - moving onto doors auto-opens them (maybe this can be an option?) 18 | - five iron, bludgeoning weapon 19 | - after a sufficient level of hacking, automatically know where stair nodes are? 20 | - or a data file that reveals location? 21 | - monsters need to know to avoid meatspace bombs 22 | - 'D' to disarm traps and bombs 23 | - reanimated mathematician (confuses/dazes the player with math and science babble, resistance based on chutzpah) 24 | - slim chance of improving player's math skills 25 | - Ghosts-in-the-shell 26 | - if dazed, pausing should (sometimes) make you stagger 27 | - instant coffee 28 | - minor stimulant 29 | - can be given to unionized maintenance worker to pacify it 30 | - when cameras enabled and SCP is alive, if player crosses security camera, chance of initiating lockdown 31 | - DocBots, trolls and garbage collectors all say things to the player -- could probably refactor their 32 | talkie-talkie parts into an "interface". Decorators? 33 | - (p)ause and wait command 34 | - Doors and Equipment have completely separate implementations and notions of damage, which is dumb 35 | 36 | Name Ch VR AC HPs Dmg AB SPD XP Value Level 37 | --------------------------------------------------------------------------------