├── 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 |
--------------------------------------------------------------------------------