├── alife ├── alife_camp.py ├── alife_work.py ├── alife_manage_items.py ├── stances.py ├── __init__.py ├── alife_follow.py ├── alife_cover.py ├── alife_escape.py ├── alife_guard.py ├── alife_group.py ├── alife_search.py ├── alife_manage_targets.py ├── snapshots.py ├── alife_hidden.py ├── alife_discover.py ├── alife_explore.py ├── alife_shelter.py ├── alife_surrender.py ├── alife_needs.py ├── raids.py ├── alife_combat.py ├── noise.py └── memory.py ├── overwatch ├── __init__.py └── events.py ├── data ├── text │ ├── places.txt │ └── human_first_names.txt ├── tiles │ ├── soldier4_22lr.png │ ├── soldier2_source.png │ ├── soldier3_source.ase │ ├── soldier4_glock17.png │ ├── soldier4_mac10.png │ ├── soldier4_unarmed.png │ ├── terminal16x16_aa_inrow.png │ ├── terminal8x8_gs_as_incol.png │ ├── terminal16x16_gs_as_incol.png │ └── terminal16x16_aa_as_incol_tiles.png ├── items │ ├── burner.json │ ├── radio.json │ ├── office_chair.json │ ├── desk.json │ ├── gas_stove.json │ ├── white_cloth.json │ ├── gas_mask.json │ ├── kevlar_helmet.json │ ├── bed.json │ ├── can_of_corn.json │ ├── coat_rack.json │ ├── cupboard.json │ ├── military_crate.json │ ├── molotov.json │ ├── wooden_dresser.json │ ├── sneakers.json │ ├── metal_shelving.json │ ├── 5.45x39mm_mag.json │ ├── frag_grenade.json │ ├── wooden_display_case.json │ ├── m9.json │ ├── 22_lr_mag.json │ ├── aspirin.json │ ├── cz_511.json │ ├── scout_pack.json │ ├── 22_rifle.json │ ├── glock.json │ ├── mp5_mag.json │ ├── soda.json │ ├── utility_backpack.json │ ├── alice_pack.json │ ├── chest_holster.json │ ├── metal_door.json │ ├── wooden_door.json │ ├── 9x19mm_round.json │ ├── leather_backpack.json │ ├── mp5.json │ ├── 5.45x39mm_round.json │ ├── blue_jeans.json │ ├── kevlar_jacket.json │ ├── ak74.json │ ├── electric_lantern.json │ ├── fall_camo_pants.json │ ├── 22_lr_cartridge.json │ ├── 9x19mm_mag.json │ ├── white_shirt.json │ ├── blue_shirt.json │ ├── trenchcoat.json │ └── brown_hoodie.json ├── missions │ ├── capture_territory.dat │ ├── retrieve_item.dat │ ├── travel_to.dat │ ├── locate_target.dat │ ├── kill_target.dat │ ├── fetch_item.dat │ ├── deliver_item.dat │ └── zes_glock.dat ├── life │ ├── night_terror.goap │ ├── dog.goap │ ├── dog.dat │ ├── night_terror.dat │ ├── human.dat │ ├── night_terror.json │ ├── night_terror.xml │ ├── dog.json │ ├── dog.xml │ ├── human.goap │ └── human.json └── prefabs │ └── test.json ├── art └── pngs │ ├── minilogo.png │ └── flagsdev_logo.png ├── docs ├── weapons.md ├── missions.md ├── mission_planning.md ├── testingnotes.md ├── style_guide.md ├── alife.md └── changelog.txt ├── freeze.py ├── tools ├── life_dump.py ├── show_profile.py ├── templates │ ├── memory.html │ ├── group.html │ ├── camp.html │ ├── index.html │ └── life.html ├── ReactorWatch.py └── guntest.py ├── render_map_setup.py ├── smp.py ├── .gitignore ├── compile_cython_modules.py ├── locks.py ├── tests ├── M01_data_structure.py ├── M01_data_structure_3d.py ├── M05_gas_mode.py ├── fast_render_simple_numpy.py ├── M07_recoil.py ├── M06_fov.py ├── M01_data_visualization.py ├── M06_Melee.py └── M01_map_editor.py ├── license.txt ├── timers.py ├── debug.py ├── crafting.py ├── pyfov.py ├── threads.py ├── contexts.py ├── scripting.py ├── maputils.py ├── encounters.py ├── events.py ├── artifacts.py ├── profiles.py ├── render_fast_los.pyx ├── inputs.py ├── network.py ├── fast_scan_surroundings.pyx ├── readme.md ├── cache.py ├── prefabs.py ├── render_los.pyx ├── drawing.py └── render_map.pyx /alife/alife_camp.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /overwatch/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/text/places.txt: -------------------------------------------------------------------------------- 1 | Hope 2 | Genesis 3 | Fortitude 4 | -------------------------------------------------------------------------------- /art/pngs/minilogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flags/Reactor-3/HEAD/art/pngs/minilogo.png -------------------------------------------------------------------------------- /art/pngs/flagsdev_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flags/Reactor-3/HEAD/art/pngs/flagsdev_logo.png -------------------------------------------------------------------------------- /data/tiles/soldier4_22lr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flags/Reactor-3/HEAD/data/tiles/soldier4_22lr.png -------------------------------------------------------------------------------- /data/tiles/soldier2_source.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flags/Reactor-3/HEAD/data/tiles/soldier2_source.png -------------------------------------------------------------------------------- /data/tiles/soldier3_source.ase: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flags/Reactor-3/HEAD/data/tiles/soldier3_source.ase -------------------------------------------------------------------------------- /data/tiles/soldier4_glock17.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flags/Reactor-3/HEAD/data/tiles/soldier4_glock17.png -------------------------------------------------------------------------------- /data/tiles/soldier4_mac10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flags/Reactor-3/HEAD/data/tiles/soldier4_mac10.png -------------------------------------------------------------------------------- /data/tiles/soldier4_unarmed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flags/Reactor-3/HEAD/data/tiles/soldier4_unarmed.png -------------------------------------------------------------------------------- /data/tiles/terminal16x16_aa_inrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flags/Reactor-3/HEAD/data/tiles/terminal16x16_aa_inrow.png -------------------------------------------------------------------------------- /data/tiles/terminal8x8_gs_as_incol.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flags/Reactor-3/HEAD/data/tiles/terminal8x8_gs_as_incol.png -------------------------------------------------------------------------------- /data/tiles/terminal16x16_gs_as_incol.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flags/Reactor-3/HEAD/data/tiles/terminal16x16_gs_as_incol.png -------------------------------------------------------------------------------- /data/tiles/terminal16x16_aa_as_incol_tiles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flags/Reactor-3/HEAD/data/tiles/terminal16x16_aa_as_incol_tiles.png -------------------------------------------------------------------------------- /docs/weapons.md: -------------------------------------------------------------------------------- 1 | WEP Range/ammo Feed 2 | ----------------------------------- 3 | MP5 100m (9x19mm) 15, 30 4 | Glock 50m (9x19mm) 17, 19 (Floor plate) 5 | 6 | -------------------------------------------------------------------------------- /docs/missions.md: -------------------------------------------------------------------------------- 1 | scout run: Gather intel on NPC/group location 2 | supply run: Travel with group to location, gather airdrop 3 | artifact hunt: Anomaly detected; investigate 4 | -------------------------------------------------------------------------------- /freeze.py: -------------------------------------------------------------------------------- 1 | from distutils.core import setup 2 | 3 | import py2exe 4 | 5 | setup(windows=[{'script': 'reactor-3.py'}], 6 | options = {"py2exe": {"packages": ['alife']}}) -------------------------------------------------------------------------------- /alife/alife_work.py: -------------------------------------------------------------------------------- 1 | from globals import * 2 | 3 | import life as lfe 4 | 5 | import missions 6 | 7 | 8 | def tick(life): 9 | if life['missions']: 10 | missions.do_mission(life, life['missions'].keys()[0]) 11 | -------------------------------------------------------------------------------- /data/items/burner.json: -------------------------------------------------------------------------------- 1 | {"name": "burner", 2 | "prefix": "a", 3 | "type": "artifact", 4 | "icon": 145, 5 | "description": "It is warm to the touch.", 6 | "flags": "SMOKING|BURNING", 7 | "size": "1x1", 8 | "material": "rock"} 9 | -------------------------------------------------------------------------------- /data/items/radio.json: -------------------------------------------------------------------------------- 1 | {"name": "radio", 2 | "prefix": "a", 3 | "type": "radio", 4 | "icon": "r", 5 | "description": "A radio used to communicate with others.", 6 | "flags": "", 7 | "size": "2x2", 8 | "material": "metal", 9 | "thickness": 2} 10 | -------------------------------------------------------------------------------- /data/items/office_chair.json: -------------------------------------------------------------------------------- 1 | {"name": "office chair", 2 | "prefix": "a", 3 | "type": "storage", 4 | "icon": "D", 5 | "description": "A chair.", 6 | "flags": "", 7 | "size": "4x6", 8 | "thickness": 3, 9 | "color": [[175, 175, 175], [145, 145, 145]], 10 | "material": "metal"} 11 | -------------------------------------------------------------------------------- /data/missions/capture_territory.dat: -------------------------------------------------------------------------------- 1 | =CREATE 2 | TASK 1 "Travel." 3 | TASK 1 "Capture." 4 | 5 | =1 6 | WAIT IS_IN_TERRITORY %TERRITORY_ID% 7 | FINISH 1 8 | JUMP 2 9 | 10 | =2 11 | EXEC CAPTURE_TERRITORY %TERRITORY_ID% 12 | JUMP 3 13 | 14 | =3 15 | FINISH 2 16 | COMPLETE 17 | -------------------------------------------------------------------------------- /data/missions/retrieve_item.dat: -------------------------------------------------------------------------------- 1 | =1 2 | WAIT CAN_SEE_ITEM %ITEM_UID% 3 | SET STARTING_CHUNK GET_CHUNK_KEY 4 | JUMP 2 5 | 6 | =2 7 | EXEC PICK_UP_ITEM %ITEM_UID% 8 | JUMPIF 3 HAS_ITEM %ITEM_UID% 9 | LOOP 10 | 11 | =3 12 | EXEC TRAVEL_TO_CHUNK %STARTING_CHUNK% 13 | COMPLETE 14 | -------------------------------------------------------------------------------- /data/items/desk.json: -------------------------------------------------------------------------------- 1 | {"name": "desk", 2 | "prefix": "a", 3 | "type": "storage", 4 | "icon": "-", 5 | "description": "A desk.", 6 | "flags": "CAN_BURN", 7 | "size": "12x12", 8 | "capacity": "2x2", 9 | "thickness": 12, 10 | "color": [[92, 51, 23], [72, 31, 3]], 11 | "material": "wood"} 12 | -------------------------------------------------------------------------------- /data/items/gas_stove.json: -------------------------------------------------------------------------------- 1 | {"name": "gas stove", 2 | "prefix": "a", 3 | "type": "storage", 4 | "icon": "#", 5 | "description": "A gas stove.", 6 | "flags": "CAN_BURN", 7 | "size": "12x12", 8 | "thickness": 12, 9 | "color": [[110, 110, 110], [150, 150, 150]], 10 | "material": "metal"} 11 | -------------------------------------------------------------------------------- /data/items/white_cloth.json: -------------------------------------------------------------------------------- 1 | {"name": "white cloth", 2 | "prefix": "a", 3 | "type": "fabric", 4 | "icon": "W", 5 | "description": "A white piece of cloth. Good for bandages.", 6 | "flags": "CANSTACK|CAN_BURN|CAN_SOAK", 7 | "size": "1x1", 8 | "thickness": 1, 9 | "material": "cloth"} 10 | -------------------------------------------------------------------------------- /data/items/gas_mask.json: -------------------------------------------------------------------------------- 1 | {"name": "gas mask", 2 | "prefix": "a", 3 | "type": "clothing", 4 | "icon": "V", 5 | "color": [[0, 0, 0], null], 6 | "description": "A gas mask.", 7 | "attaches_to": "head", 8 | "flags": "CAN_WEAR", 9 | "size": "2x1", 10 | "thickness": 3, 11 | "material": "metal"} 12 | -------------------------------------------------------------------------------- /data/missions/travel_to.dat: -------------------------------------------------------------------------------- 1 | =CREATE 2 | TASK 1 "Go to." 3 | 4 | =1 5 | JUMPIF 2 IS_PLAYER 6 | EXEC TRAVEL_TO_CHUNK %CHUNK_KEY% 7 | JUMPIF 3 IS_IN_CHUNK %CHUNK_KEY% 8 | LOOP 9 | 10 | =2 11 | WAIT IS_IN_CHUNK %CHUNK_KEY% 12 | FINISH 1 13 | JUMP 3 14 | 15 | =3 16 | FINISH 1 17 | COMPLETE 18 | -------------------------------------------------------------------------------- /data/items/kevlar_helmet.json: -------------------------------------------------------------------------------- 1 | {"name": "kevlar helmet", 2 | "prefix": "a", 3 | "type": "clothing", 4 | "icon": "O", 5 | "description": "A bulletproof helmet.", 6 | "attaches_to": "head", 7 | "flags": "CAN_WEAR|CAN_BURN|CAN_SOAK", 8 | "size": "3x3", 9 | "thickness": 10, 10 | "material": "kevlar"} 11 | -------------------------------------------------------------------------------- /data/items/bed.json: -------------------------------------------------------------------------------- 1 | {"name": "bed", 2 | "prefix": "a", 3 | "type": "door", 4 | "icon": "8", 5 | "description": "A large wooden dresser.", 6 | "flags": "CAN_BURN", 7 | "size": "12x12", 8 | "capacity": "12x12", 9 | "thickness": 3, 10 | "color": [[191, 171, 143], [158, 134, 100]], 11 | "material": "wood"} 12 | -------------------------------------------------------------------------------- /data/items/can_of_corn.json: -------------------------------------------------------------------------------- 1 | {"name": "corn", 2 | "prefix": "a can of", 3 | "type": "food", 4 | "icon": "%", 5 | "description": "A can of corn.", 6 | "flags": "", 7 | "modifiers": {}, 8 | "size": "1x1", 9 | "modifiers": [{"value": "hunger", "increase": 8, "effective_for": 1000}], 10 | "material": "metal"} 11 | -------------------------------------------------------------------------------- /data/items/coat_rack.json: -------------------------------------------------------------------------------- 1 | {"name": "coat rack", 2 | "prefix": "a", 3 | "type": "storage", 4 | "icon": "{", 5 | "description": "A thin metal rod with a few hooks.", 6 | "flags": "", 7 | "size": "12x12", 8 | "capacity": "12x6", 9 | "thickness": 1, 10 | "color": [[94, 75, 47], null], 11 | "material": "metal"} 12 | -------------------------------------------------------------------------------- /data/items/cupboard.json: -------------------------------------------------------------------------------- 1 | {"name": "cupboard", 2 | "prefix": "a", 3 | "type": "storage", 4 | "icon": "|", 5 | "description": "A wooden cupboard.", 6 | "flags": "CAN_BURN", 7 | "size": "8x2", 8 | "capacity": "8x2", 9 | "thickness": 2, 10 | "color": [[133, 120, 91], [154, 135, 107]], 11 | "material": "wood"} 12 | -------------------------------------------------------------------------------- /data/items/military_crate.json: -------------------------------------------------------------------------------- 1 | {"name": "military crate", 2 | "prefix": "a", 3 | "type": "storage", 4 | "icon": "=", 5 | "description": "A large metal crate.", 6 | "flags": "", 7 | "size": "14x14", 8 | "capacity": "14x14", 9 | "thickness": 8, 10 | "color": [[0, 150, 0], [0, 170, 0]], 11 | "material": "metal"} 12 | -------------------------------------------------------------------------------- /data/items/molotov.json: -------------------------------------------------------------------------------- 1 | {"name": "molotov", 2 | "prefix": "a", 3 | "type": "explosive", 4 | "icon": "*", 5 | "description": "Improvised incendiary device.", 6 | "speed": 1, 7 | "drag": 0.02, 8 | "flags": "", 9 | "radius": 8, 10 | "damage": {"force": 0, "fire": 6}, 11 | "size": "1x1", 12 | "material": "glass"} 13 | -------------------------------------------------------------------------------- /data/items/wooden_dresser.json: -------------------------------------------------------------------------------- 1 | {"name": "wooden dresser", 2 | "prefix": "a", 3 | "type": "storage", 4 | "icon": "D", 5 | "description": "A wooden door.", 6 | "flags": "CAN_BURN", 7 | "size": "12x12", 8 | "capacity": "12x12", 9 | "thickness": 3, 10 | "color": [[94, 75, 47], [63, 50, 31]], 11 | "material": "wood"} 12 | -------------------------------------------------------------------------------- /tools/life_dump.py: -------------------------------------------------------------------------------- 1 | #Life Dump 2 | 3 | import json 4 | import sys 5 | 6 | def dump(filename): 7 | with open(filename, 'r') as e: 8 | _life = json.loads(''.join(e.readlines())) 9 | 10 | print json.dumps(_life, indent=3) 11 | 12 | if __name__ == '__main__': 13 | if len(sys.argv)>1: 14 | dump(sys.argv[1]) -------------------------------------------------------------------------------- /data/items/sneakers.json: -------------------------------------------------------------------------------- 1 | {"name": "sneakers", 2 | "prefix": "a pair of", 3 | "type": "clothing", 4 | "icon": "s", 5 | "description": "A pair of running shoes.", 6 | "mods": {"speed": 0.5}, 7 | "attaches_to": "lfoot|rfoot", 8 | "flags": "CAN_WEAR|CAN_BURN|CAN_SOAK", 9 | "size": "1x1", 10 | "thickness": 1, 11 | "material": "cloth"} 12 | -------------------------------------------------------------------------------- /render_map_setup.py: -------------------------------------------------------------------------------- 1 | from distutils.core import setup 2 | from distutils.extension import Extension 3 | from Cython.Distutils import build_ext 4 | 5 | ext_modules = [Extension("render_map", ["render_map.pyx"])] 6 | 7 | setup( 8 | name = 'Render Map', 9 | cmdclass = {'build_ext': build_ext}, 10 | ext_modules = ext_modules 11 | ) -------------------------------------------------------------------------------- /data/items/metal_shelving.json: -------------------------------------------------------------------------------- 1 | {"name": "metal shelving", 2 | "prefix": "the", 3 | "type": "storage", 4 | "icon": "=", 5 | "description": "A metal structure with several shelves.", 6 | "flags": "", 7 | "size": "3x3", 8 | "capacity": "3x3", 9 | "thickness": 3, 10 | "color": [[180, 180, 180], [130, 130, 130]], 11 | "material": "metal"} 12 | -------------------------------------------------------------------------------- /data/items/5.45x39mm_mag.json: -------------------------------------------------------------------------------- 1 | {"name": "5.45x39mm magazine", 2 | "prefix": "a", 3 | "type": "magazine", 4 | "icon": "L", 5 | "description": "A magazine designed for use in the AK-74.", 6 | "flags": "", 7 | "size": "1x3", 8 | "maxrounds": 30, 9 | "rounds": [], 10 | "ammotype": "5.45x39mm", 11 | "material": "metal", 12 | "thickness": 3 13 | } 14 | -------------------------------------------------------------------------------- /data/items/frag_grenade.json: -------------------------------------------------------------------------------- 1 | {"name": "frag grenade", 2 | "prefix": "a", 3 | "type": "explosive", 4 | "icon": "*", 5 | "description": "Mid-range frag grenade with a decent damage radius.", 6 | "speed": 1, 7 | "drag": 0.02, 8 | "flags": "", 9 | "radius": 8, 10 | "damage": {"force": 4, "fire": 3}, 11 | "size": "1x1", 12 | "material": "metal"} 13 | -------------------------------------------------------------------------------- /data/items/wooden_display_case.json: -------------------------------------------------------------------------------- 1 | {"name": "wooden display case", 2 | "prefix": "a", 3 | "type": "storage", 4 | "icon": "#", 5 | "description": "A metal structure with several shelves.", 6 | "flags": "", 7 | "size": "6x10", 8 | "capacity": "6x10", 9 | "thickness": 3, 10 | "color": [[161, 219, 236], [94, 75, 47]], 11 | "material": "wood"} 12 | -------------------------------------------------------------------------------- /data/items/m9.json: -------------------------------------------------------------------------------- 1 | {"name": "M9", 2 | "prefix": "an", 3 | "type": "gun", 4 | "icon": "P", 5 | "description": "A pistol", 6 | "flags": "", 7 | "size": "4x2", 8 | "feed": "magazine", 9 | "ammotype": "9x19mm", 10 | "recoil": 0.4, 11 | "accuracy": 0.69, 12 | "firemodes": ["single"], 13 | "firemode": 0, 14 | "material": "metal", 15 | "thickness": 3} 16 | -------------------------------------------------------------------------------- /data/missions/locate_target.dat: -------------------------------------------------------------------------------- 1 | =CREATE 2 | TASK 1 "Find target." 3 | 4 | =1 5 | EXEC TRACK_TARGET %TARGET% 6 | JUMPIF 2 IS_PLAYER 7 | EXEC SEARCH_FOR_TARGET %TARGET% 8 | JUMPIF 3 CAN_SEE_TARGET %TARGET% 9 | LOOP 10 | 11 | =2 12 | WAIT CAN_SEE_TARGET %TARGET% 13 | FINISH 1 14 | JUMP 3 15 | 16 | =3 17 | EXEC UNTRACK_TARGET %TARGET% 18 | COMPLETE 19 | -------------------------------------------------------------------------------- /data/items/22_lr_mag.json: -------------------------------------------------------------------------------- 1 | {"name": ".22 LR magazine", 2 | "prefix": "a", 3 | "type": "magazine", 4 | "icon": "L", 5 | "description": "A magazine for use in .22 long rifles.", 6 | "flags": "", 7 | "size": "1x3", 8 | "maxrounds": 12, 9 | "rounds": [], 10 | "ammotype": ".22 LR", 11 | "material": "metal", 12 | "thickness": 3, 13 | "upgrades": 14 | {} 15 | } 16 | -------------------------------------------------------------------------------- /data/items/aspirin.json: -------------------------------------------------------------------------------- 1 | {"name": "aspirin", 2 | "prefix": "an", 3 | "type": "medicine", 4 | "icon": "8", 5 | "description": "Pain medication. Recommended dose: 2 capsules.", 6 | "flags": "", 7 | "sustenance": 20, 8 | "value": 175, 9 | "modifiers": [{"value": "pain_threshold", "add": 0.2, "effective_for": 3000}], 10 | "size": "1x1", 11 | "material": "metal"} 12 | -------------------------------------------------------------------------------- /data/items/cz_511.json: -------------------------------------------------------------------------------- 1 | {"name": "CZ 511", 2 | "prefix": "a", 3 | "type": "gun", 4 | "icon": "/", 5 | "description": ".22 rifle", 6 | "flags": "", 7 | "size": "18x1", 8 | "feed": "magazine", 9 | "ammotype": ".22 LR", 10 | "recoil": 0.5, 11 | "accuracy": 0.91, 12 | "firemodes": ["single"], 13 | "firemode": 0, 14 | "material": "metal", 15 | "thickness": 3} 16 | -------------------------------------------------------------------------------- /data/items/scout_pack.json: -------------------------------------------------------------------------------- 1 | {"name": "scout pack", 2 | "prefix": "a", 3 | "type": "backpack", 4 | "icon": "b", 5 | "color": [[94, 75, 47], null], 6 | "description": "A small, low-profile backpack.", 7 | "attaches_to": "chest", 8 | "flags": "CAN_WEAR|CAN_BURN|CAN_SOAK", 9 | "size": "2x3", 10 | "capacity": "2x3", 11 | "thickness": 2, 12 | "material": "cloth"} 13 | -------------------------------------------------------------------------------- /data/items/22_rifle.json: -------------------------------------------------------------------------------- 1 | {"name": ".22 rifle", 2 | "prefix": "a", 3 | "type": "gun", 4 | "icon": "/", 5 | "description": "A .22 rifle", 6 | "flags": "", 7 | "size": "19x1", 8 | "feed": "magazine", 9 | "ammotype": ".22 LR", 10 | "recoil": 0.55, 11 | "accuracy": 0.88, 12 | "firemodes": ["single"], 13 | "firemode": 0, 14 | "material": "metal", 15 | "thickness": 3} 16 | -------------------------------------------------------------------------------- /data/items/glock.json: -------------------------------------------------------------------------------- 1 | {"name": "glock", 2 | "prefix": "a", 3 | "type": "gun", 4 | "icon": "p", 5 | "description": "A pistol", 6 | "flags": "", 7 | "size": "3x2", 8 | "feed": "magazine", 9 | "ammotype": "9x19mm", 10 | "recoil": 0.42, 11 | "accuracy": 0.6, 12 | "firemodes": ["single", "3burst"], 13 | "firemode": 0, 14 | "material": "metal", 15 | "thickness": 3} 16 | -------------------------------------------------------------------------------- /data/items/mp5_mag.json: -------------------------------------------------------------------------------- 1 | {"name": "mp5 magazine", 2 | "prefix": "a", 3 | "type": "magazine", 4 | "icon": "[", 5 | "description": "A magazine for use in the MP5. Holds 15 rounds.", 6 | "flags": "", 7 | "size": "1x2", 8 | "maxrounds": 15, 9 | "rounds": [], 10 | "ammotype": "9x19mm", 11 | "material": "metal", 12 | "thickness": 4, 13 | "upgrades": 14 | {} 15 | } 16 | -------------------------------------------------------------------------------- /data/items/soda.json: -------------------------------------------------------------------------------- 1 | {"name": "soda", 2 | "prefix": "a can of", 3 | "type": "drink", 4 | "icon": "8", 5 | "description": "A can of sugary soda. Contains caffeine.", 6 | "flags": "", 7 | "modifiers": [{"value": "thirst", "increase": 5, "effective_for": 1000}, {"value": "hunger", "increase": -1, "effective_for": 1000}], 8 | "size": "1x1", 9 | "material": "metal"} 10 | -------------------------------------------------------------------------------- /data/items/utility_backpack.json: -------------------------------------------------------------------------------- 1 | {"name": "utility backpack", 2 | "prefix": "a", 3 | "type": "backpack", 4 | "icon": "B", 5 | "color": [[94, 75, 47], null], 6 | "description": "Medium-sized backpack.", 7 | "attaches_to": "chest", 8 | "flags": "CAN_WEAR|CAN_BURN|CAN_SOAK", 9 | "size": "6x7", 10 | "capacity": "6x7", 11 | "thickness": 5, 12 | "material": "cloth"} 13 | -------------------------------------------------------------------------------- /smp.py: -------------------------------------------------------------------------------- 1 | from globals import * 2 | 3 | import life as lfe 4 | import alife 5 | import zones 6 | import fov as _fov 7 | 8 | import time 9 | 10 | def init(): 11 | import pp 12 | 13 | SETTINGS['smp'] = pp.Server() 14 | 15 | def test(life, key=None): 16 | return (life, key) 17 | 18 | def process(callback, life, **kwargs): 19 | return [life, callback(life, **kwargs)] 20 | -------------------------------------------------------------------------------- /data/items/alice_pack.json: -------------------------------------------------------------------------------- 1 | {"name": "ALICE pack", 2 | "prefix": "an", 3 | "type": "backpack", 4 | "icon": "B", 5 | "color": [[94, 75, 47], null], 6 | "description": "All-purpose Lightweight Individual Carrying Equipment.", 7 | "attaches_to": "chest", 8 | "flags": "CAN_WEAR|CAN_BURN|CAN_SOAK", 9 | "size": "7x7", 10 | "capacity": "7x7", 11 | "thickness": 3, 12 | "material": "cloth"} 13 | -------------------------------------------------------------------------------- /data/items/chest_holster.json: -------------------------------------------------------------------------------- 1 | {"name": "chest holster", 2 | "prefix": "a", 3 | "type": "clothing", 4 | "icon": "/", 5 | "color": [[54, 75, 47], null], 6 | "description": "A tactical hoster that is worn around the chest.", 7 | "attaches_to": "chest", 8 | "flags": "CAN_WEAR|CAN_BURN|CAN_SOAK", 9 | "size": "2x2", 10 | "capacity": "2x2", 11 | "thickness": 3, 12 | "material": "cloth"} 13 | -------------------------------------------------------------------------------- /data/items/metal_door.json: -------------------------------------------------------------------------------- 1 | {"name": "metal door", 2 | "prefix": "a", 3 | "type": "door", 4 | "icon": "+", 5 | "description": "A large metal door.", 6 | "flags": "CAN_BURN|CAN_BLOCK|ON_ACTIVATE[TOGGLE_BLOCK()]|ON_DEACTIVATE[TOGGLE_BLOCK()]|ON_COLLIDE[TOGGLE_BLOCK]", 7 | "size": "12x12", 8 | "thickness": 3, 9 | "color": [[120, 120, 120], [140, 140, 140]], 10 | "material": "metal"} 11 | -------------------------------------------------------------------------------- /data/items/wooden_door.json: -------------------------------------------------------------------------------- 1 | {"name": "wooden door", 2 | "prefix": "a", 3 | "type": "door", 4 | "icon": "+", 5 | "description": "A large wooden dresser.", 6 | "flags": "CAN_BURN|CAN_BLOCK|ON_ACTIVATE[TOGGLE_BLOCK()]|ON_DEACTIVATE[TOGGLE_BLOCK()]|ON_COLLIDE[TOGGLE_BLOCK]", 7 | "size": "12x12", 8 | "thickness": 3, 9 | "color": [[127, 101, 63], [94, 75, 47]], 10 | "material": "wood"} 11 | -------------------------------------------------------------------------------- /alife/alife_manage_items.py: -------------------------------------------------------------------------------- 1 | from globals import * 2 | 3 | import life as lfe 4 | 5 | import judgement 6 | import survival 7 | 8 | import logging 9 | 10 | 11 | def conditions(life): 12 | RETURN_VALUE = STATE_UNCHANGED 13 | 14 | if lfe.execute_raw(life, 'state', 'managing'): 15 | return True 16 | 17 | return False 18 | 19 | def tick(life): 20 | return survival.manage_inventory(life) 21 | -------------------------------------------------------------------------------- /data/items/9x19mm_round.json: -------------------------------------------------------------------------------- 1 | {"name": "9x19mm round", 2 | "prefix": "a", 3 | "type": "bullet", 4 | "icon": ".", 5 | "description": "Pistol round effective at short to mid-range combat.", 6 | "speed": 38, 7 | "recoil": 5, 8 | "drag": 0.05, 9 | "scatter_rate": 0.3, 10 | "flags": "ON_STOP[DELETE()]", 11 | "damage": {"sharp": 13}, 12 | "size": "1x1", 13 | "ammotype": "9x19mm", 14 | "material": "metal"} 15 | -------------------------------------------------------------------------------- /data/items/leather_backpack.json: -------------------------------------------------------------------------------- 1 | {"name": "leather backpack", 2 | "prefix": "a", 3 | "type": "backpack", 4 | "icon": "B", 5 | "color": [[94, 75, 47], null], 6 | "description": "As the name suggests, it is a backpack made of quality leather.", 7 | "attaches_to": "chest", 8 | "flags": "CAN_WEAR|CAN_BURN|CAN_SOAK", 9 | "size": "5x5", 10 | "capacity": "5x5", 11 | "thickness": 3, 12 | "material": "cloth"} 13 | -------------------------------------------------------------------------------- /data/items/mp5.json: -------------------------------------------------------------------------------- 1 | {"name": "mp5", 2 | "prefix": "an", 3 | "type": "gun", 4 | "icon": "m", 5 | "description": "A submachine gun. Effective at mid-range.", 6 | "flags": "", 7 | "size": "7x2", 8 | "feed": "magazine", 9 | "ammotype": "9x19mm", 10 | "recoil": 0.25, 11 | "accuracy": 0.63, 12 | "firemodes": ["single", "3burst", "auto"], 13 | "firemode": 1, 14 | "material": "metal", 15 | "thickness": 3} 16 | -------------------------------------------------------------------------------- /data/items/5.45x39mm_round.json: -------------------------------------------------------------------------------- 1 | {"name": "5.45x39mm round", 2 | "prefix": "a", 3 | "type": "bullet", 4 | "icon": ".", 5 | "description": "Rifle round effective at mid to long-range combat.", 6 | "speed": 45, 7 | "recoil": 3, 8 | "drag": 0.005, 9 | "scatter_rate": 0.2, 10 | "flags": "ON_STOP[DELETE()]", 11 | "damage": {"sharp": 19}, 12 | "size": "1x1", 13 | "ammotype": "5.45x39mm", 14 | "material": "metal"} 15 | -------------------------------------------------------------------------------- /data/items/blue_jeans.json: -------------------------------------------------------------------------------- 1 | {"name": "blue jeans", 2 | "prefix": "a pair of", 3 | "type": "clothing", 4 | "icon": "H", 5 | "color": [[0, 0, 255], null], 6 | "description": "A sturdy pair of blue jeans.", 7 | "attaches_to": "hip|rthigh|lthigh|lknee|rknee|llowerleg|rlowerleg", 8 | "flags": "CAN_WEAR|CAN_BURN|CAN_SOAK", 9 | "size": "1x1", 10 | "capacity": "2x1", 11 | "thickness": 1, 12 | "material": "cloth"} 13 | -------------------------------------------------------------------------------- /data/items/kevlar_jacket.json: -------------------------------------------------------------------------------- 1 | {"name": "kevlar jacket", 2 | "prefix": "a", 3 | "type": "clothing", 4 | "icon": "T", 5 | "description": "A bulletproof jacket.", 6 | "attaches_to": "chest|neck|stomach|lshoulder|rshoulder|lupperarm|rupperarm|lelbow|relbow|lforearm|rforearm", 7 | "flags": "CAN_WEAR|CAN_BURN|CAN_SOAK", 8 | "size": "6x6", 9 | "capacity": "2x2", 10 | "thickness": 12, 11 | "material": "kevlar"} 12 | -------------------------------------------------------------------------------- /tools/show_profile.py: -------------------------------------------------------------------------------- 1 | #Reads profile, prints sorted output 2 | import pstats 3 | import sys 4 | 5 | if len(sys.argv)>=2 and sys.argv[1].count('.dat'): 6 | profile = sys.argv[1] 7 | else: 8 | profile = 'profile.dat' 9 | 10 | stats = pstats.Stats(profile) 11 | 12 | if 'highest' in sys.argv: 13 | stats.sort_stats('cumulative').print_stats(20) 14 | else: 15 | stats.strip_dirs().sort_stats(-1).print_stats() 16 | -------------------------------------------------------------------------------- /data/items/ak74.json: -------------------------------------------------------------------------------- 1 | {"name": "AK-74", 2 | "prefix": "an", 3 | "type": "gun", 4 | "icon": "m", 5 | "description": "Kalashnikov automatic. Good for long-range engagements.", 6 | "flags": "", 7 | "size": "9x2", 8 | "feed": "magazine", 9 | "ammotype": "5.45x39mm", 10 | "recoil": 0.35, 11 | "accuracy": 0.72, 12 | "firemodes": ["single", "3burst", "auto"], 13 | "firemode": 1, 14 | "material": "metal", 15 | "thickness": 3} 16 | -------------------------------------------------------------------------------- /data/items/electric_lantern.json: -------------------------------------------------------------------------------- 1 | {"name": "electric lantern", 2 | "prefix": "an", 3 | "type": "light", 4 | "icon": 15, 5 | "description": "A small light. Built with metal.", 6 | "brightness": 2, 7 | "light_shake": 0.3, 8 | "flags": "ON_ACTIVATE[LIGHT_FOLLOW(self, self.brightness, self.light_shake)]|ON_DEACTIVATE[LIGHT_FOLLOW_REMOVE(self, self.brightness, self.light_shake)]", 9 | "size": "2x2", 10 | "material": "metal"} 11 | -------------------------------------------------------------------------------- /data/missions/kill_target.dat: -------------------------------------------------------------------------------- 1 | =CREATE 2 | TASK 1 "Find target." 3 | TASK 2 "Kill target." 4 | 5 | =1 6 | JUMPIF 2 IS_PLAYER 7 | EXEC SEARCH_FOR_TARGET %TARGET% 8 | JUMPIF 3 CAN_SEE_TARGET %TARGET% 9 | LOOP 10 | 11 | =2 12 | EXEC TRACK_TARGET %TARGET% 13 | WAIT CAN_SEE_TARGET %TARGET% 14 | FINISH 1 15 | JUMP 4 16 | 17 | =3 18 | FINISH 1 19 | JUMP 4 20 | 21 | =4 22 | WAIT IS_TARGET_DEAD %TARGET% 23 | FINISH 2 24 | COMPLETE 25 | -------------------------------------------------------------------------------- /alife/stances.py: -------------------------------------------------------------------------------- 1 | import brain 2 | 3 | import judgement 4 | 5 | import logging 6 | 7 | def get_stance_towards(life, target_id): 8 | _know = brain.knows_alife_by_id(life, target_id) 9 | 10 | if _know: 11 | if judgement.can_trust(life, target_id): 12 | return 'friendly' 13 | elif judgement.is_target_dangerous(life, target_id): 14 | return 'hostile' 15 | else: 16 | return 'neutral' 17 | else: 18 | return 'neutral' -------------------------------------------------------------------------------- /data/items/fall_camo_pants.json: -------------------------------------------------------------------------------- 1 | {"name": "fall camo pants", 2 | "prefix": "a pair of", 3 | "type": "clothing", 4 | "icon": "H", 5 | "color": [[94, 75, 47], null], 6 | "description": "Pants with fall camo. Good for blending in.", 7 | "attaches_to": "hip|rthigh|lthigh|lknee|rknee|llowerleg|rlowerleg", 8 | "flags": "CAN_WEAR|CAN_BURN|CAN_SOAK", 9 | "size": "1x1", 10 | "capacity": "2x2", 11 | "thickness": 1, 12 | "material": "cloth"} 13 | -------------------------------------------------------------------------------- /data/items/22_lr_cartridge.json: -------------------------------------------------------------------------------- 1 | {"name": ".22 LR cartridge", 2 | "prefix": "a", 3 | "type": "bullet", 4 | "icon": ".", 5 | "color": [[94, 75, 47], null], 6 | "description": "Long Rifle cartridge effective at mid to long range.", 7 | "speed": 35, 8 | "recoil": 3, 9 | "drag": 0.05, 10 | "scatter_rate": 0.1, 11 | "flags": "ON_STOP[DELETE()]", 12 | "damage": {"sharp": 17}, 13 | "size": "1x1", 14 | "ammotype": ".22 LR", 15 | "material": "metal"} 16 | -------------------------------------------------------------------------------- /alife/__init__.py: -------------------------------------------------------------------------------- 1 | ############ 2 | # ALife v2 ######################## 3 | # Created by Luke Martin (flags) # 4 | ################################### 5 | # Started: 12:10 AM, 1/16/2013 # 6 | # Ended: Probably not for a while # 7 | ################################### 8 | 9 | __all__ = ['action', 'snapshots', 'judgement', 'chunks', 'brain', 'speech', 'stances', 'jobs', 'groups', 'factions', 'camps', 'sight', 'rawparse', 'noise', 'planner'] 10 | -------------------------------------------------------------------------------- /alife/alife_follow.py: -------------------------------------------------------------------------------- 1 | from globals import * 2 | 3 | import life as lfe 4 | 5 | import judgement 6 | import movement 7 | import sight 8 | 9 | 10 | def tick(life): 11 | _guard = judgement.get_target_to_guard(life) 12 | 13 | if _guard: 14 | return movement.find_target(life, _guard, follow=False, distance=sight.get_vision(life)*.25, call=False) 15 | 16 | return movement.find_target(life, judgement.get_target_to_follow(life), follow=True, call=False) -------------------------------------------------------------------------------- /data/items/9x19mm_mag.json: -------------------------------------------------------------------------------- 1 | {"name": "9x19mm magazine", 2 | "prefix": "a", 3 | "type": "magazine", 4 | "icon": "L", 5 | "description": "A magazine designed for use in the Glock 17. Extendable to 19 rounds with optional floor plate.", 6 | "flags": "", 7 | "size": "1x2", 8 | "maxrounds": 17, 9 | "rounds": [], 10 | "ammotype": "9x19mm", 11 | "material": "metal", 12 | "thickness": 3, 13 | "upgrades": 14 | {"ext. 9x19mm mag floor plate": 15 | {"magsize": 19} 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /data/missions/fetch_item.dat: -------------------------------------------------------------------------------- 1 | =1 2 | SET LOCATION_CHUNK FIND_NEAREST_CHUNK_IN_REFERENCE TOWNS 3 | JUMP 2 4 | 5 | =2 6 | EXEC TRAVEL_TO_CHUNK %LOCATION_CHUNK% 7 | WAIT IS_IN_CHUNK %LOCATION_CHUNK% 8 | #WAIT SEARCH_FOR_ITEM %ITEM% 9 | 10 | JUMPIF 3 HAS_ITEM_TYPE %ITEM% 11 | JUMP 2 12 | 13 | =3 14 | JUMPIF 3 HAS_FACTION_BASE 15 | JUMPIF 4 %GATHERED_ITEM%&!HAS_FACTION_BASE 16 | 17 | =4 18 | SET LOCATION_CHUNK FIND_FACTION_BASE 19 | EXEC TRAVEL_TO_CHUNK %LOCATION_CHUNK% 20 | COMPLETE 21 | 22 | =5 23 | COMPLETE -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyd 2 | *.pyc 3 | *.c 4 | *.so 5 | *.sh 6 | *.xcf 7 | *.swp 8 | *.wpr 9 | *.gif 10 | *.png 11 | *.ase 12 | *.txt 13 | *.patch 14 | *.lprof 15 | *.idea_wall 16 | *.html 17 | *.prof 18 | *.zip 19 | *_simple_* 20 | *.dll 21 | *.wpu 22 | 23 | .stats/* 24 | .idea/* 25 | art/txt/* 26 | build/* 27 | tests/* 28 | dist/* 29 | data/maps* 30 | docs/devjournal.md 31 | docs/shortlist.md 32 | docs/talks/* 33 | 34 | mergedown 35 | nounlist.py 36 | kernprof.py 37 | profile.dat 38 | profile.txt 39 | diffout.txt 40 | data/text/wikiscrape.py 41 | -------------------------------------------------------------------------------- /data/text/human_first_names.txt: -------------------------------------------------------------------------------- 1 | Abram 2 | Alexander 3 | Anatoly 4 | Artyom 5 | Bogdan 6 | Vadim 7 | Veniamin 8 | Vladislav 9 | Vyacheslav 10 | Gavriil 11 | Georgy 12 | Daniil 13 | Evgeny 14 | Yegor 15 | Yefim 16 | Zakhar 17 | Ignat 18 | Illarion 19 | Immanuil 20 | Iosif 21 | Lev 22 | Leonid 23 | Makar 24 | Marat 25 | Matvei 26 | Mikhail 27 | Nikolay 28 | Oleg 29 | Pavel 30 | Pyotr 31 | Rodion 32 | Rostislav 33 | Ruslan 34 | Semyon 35 | Sergei 36 | Spartak 37 | Stanislav 38 | Stepan 39 | Timofei 40 | Timur 41 | Trofim 42 | Eduard 43 | Yulian 44 | Yakov 45 | Yaroslav -------------------------------------------------------------------------------- /compile_cython_modules.py: -------------------------------------------------------------------------------- 1 | from distutils.core import setup 2 | from distutils.extension import Extension 3 | from Cython.Distutils import build_ext 4 | 5 | setup( 6 | cmdclass = {'build_ext': build_ext}, 7 | ext_modules = [Extension('render_map', ['render_map.pyx']), 8 | Extension('render_los', ['render_los.pyx']), 9 | Extension('render_fast_los', ['render_fast_los.pyx']), 10 | Extension('fast_dijkstra', ['fast_dijkstra.pyx']), 11 | Extension('fast_scan_surroundings', ['fast_scan_surroundings.pyx']), 12 | Extension('fov', ['fov.pyx'])] 13 | ) 14 | -------------------------------------------------------------------------------- /data/missions/deliver_item.dat: -------------------------------------------------------------------------------- 1 | =CREATE 2 | TASK 1 "Find item." 3 | TASK 2 "Deliver item." 4 | 5 | =1 6 | WAIT HAS_ITEM %ITEM_UID% 7 | FINISH 1 8 | EXEC TRACK_TARGET %TARGET% 9 | JUMPIF 2 IS_PLAYER 10 | JUMP 3 11 | 12 | =2 13 | WAIT CAN_SEE_TARGET %TARGET% 14 | EXEC UNTRACK_TARGET %TARGET% 15 | WAIT !HAS_ITEM %ITEM_UID% 16 | FINISH 2 17 | JUMP 5 18 | 19 | =3 20 | EXEC SEARCH_FOR_TARGET %TARGET% 21 | JUMPIF 4 CAN_SEE_TARGET %TARGET% 22 | LOOP 23 | 24 | =4 25 | EXEC DROP_ITEM %ITEM_UID% 26 | WAIT !HAS_ITEM %ITEM_UID% 27 | FINISH 2 28 | JUMP 5 29 | 30 | =5 31 | COMPLETE 32 | -------------------------------------------------------------------------------- /tools/templates/memory.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | ReactorWatch - Memory {{ life_id }} 4 | 5 | 6 |

Life #{{ life_id }}

7 | 19 | 20 | -------------------------------------------------------------------------------- /data/life/night_terror.goap: -------------------------------------------------------------------------------- 1 | [GOAL_DISCOVER] 2 | DESIRE: HAS_NON_RELAXED_GOAL 3 | TIER: TIER_RELAXED 4 | 5 | [GOAL_LOOT] 6 | DESIRE: !HAS_NEEDS 7 | TIER: TIER_SURVIVAL 8 | SET_FLAGS: {"wanted_items": GET_NEEDED_ITEMS} 9 | 10 | [ACTION_WANDER] 11 | SATISFIES: HAS_NON_RELAXED_GOAL 12 | LOOP_UNTIL: NEVER 13 | EXECUTE: WANDER 14 | 15 | [ACTION_PICK_UP_ITEM] 16 | SATISFIES: HAS_NEEDS 17 | DESIRES: KNOWS_ABOUT_ITEM 18 | LOOP_UNTIL: NEVER 19 | EXECUTE: PICK_UP_ITEM 20 | 21 | [ACTION_FIND_ITEM] 22 | SATISFIES: KNOWS_ABOUT_ITEM 23 | DESIRES: HAS_NON_RELAXED_GOAL 24 | LOOP_UNTIL: HAS_WANTED_ITEMS -------------------------------------------------------------------------------- /alife/alife_cover.py: -------------------------------------------------------------------------------- 1 | from globals import * 2 | 3 | import life as lfe 4 | 5 | import judgement 6 | import movement 7 | import bad_numbers 8 | import sight 9 | import brain 10 | 11 | import logging 12 | 13 | def tick(life): 14 | _threats = judgement.get_threats(life, ignore_escaped=2) 15 | 16 | if not _threats: 17 | return True 18 | 19 | for target in [LIFE[t] for t in _threats]: 20 | if bad_numbers.distance(life['pos'], brain.knows_alife(life, target)['last_seen_at']) >= sight.get_vision(life): 21 | _threats.remove(target['id']) 22 | 23 | return movement.hide(life, _threats) 24 | -------------------------------------------------------------------------------- /data/missions/zes_glock.dat: -------------------------------------------------------------------------------- 1 | =1 2 | WAIT CAN_SEE_TARGET %TARGET% 3 | SET STARTING_CHUNK GET_CHUNK_KEY 4 | JUMP 2 5 | 6 | =2 7 | WAIT !HAS_DIALOG 8 | EXEC MOVE_TO_TARGET %TARGET% 9 | JUMPIF 3 IS_IN_RANGE_OF_TARGET %TARGET% 5. 10 | LOOP 11 | 12 | =3 13 | EXEC STOP 14 | EXEC DROP_ITEM %ITEM_UID% 15 | EXEC GIVE_MISSION kill_target %TARGET% {"target":%KILL_TARGET%,"location":%LOCATION%} 16 | EXEC GIVE_MISSION deliver_item %TARGET% {"target":%DELIVER_TARGET%,"item_uid":%QUEST_ITEM_UID%} 17 | EXEC TRAVEL_TO_CHUNK %STARTING_CHUNK% 18 | EXEC CREATE_MISSION retrieve_item {"item_uid":%QUEST_ITEM_UID%} 19 | COMPLETE 20 | -------------------------------------------------------------------------------- /tools/templates/group.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | ReactorWatch - Group {{ group_id }} 4 | 5 | 6 |

Group {{ group_id }}

7 | 17 | 18 | -------------------------------------------------------------------------------- /tools/templates/camp.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | ReactorWatch - Camp {{ camp.id }} 4 | 5 | 6 |

Camp {{ camp.id }}

7 | 16 | 17 | -------------------------------------------------------------------------------- /data/items/white_shirt.json: -------------------------------------------------------------------------------- 1 | {"name": "white t-shirt", 2 | "prefix": "a", 3 | "type": "clothing", 4 | "icon": "T", 5 | "description": "A plain, white t-shirt.", 6 | "attaches_to": "lshoulder|rshoulder|chest|stomach", 7 | "flags": "CAN_WEAR|CANSTACK|CAN_BURN|CAN_SOAK", 8 | "craft": [{"name": "Make cloth", "type": "create_item", "requirements": [], "time": 20, "difficulty": {"engineering": 3}, "create_item": {"white cloth": {"failure": {"amount": 2}, "partial_success": {"amount": 4}, "success": {"amount": 6}}}, "strings": {"failure": "You struggle tearing the cloth, destroying it.", "partial_success": "You rip some of the cloth, wasting it.", "success": "You tear the cloth evenly."}}], 9 | "size": "1x1", 10 | "thickness": 1, 11 | "material": "cloth"} 12 | -------------------------------------------------------------------------------- /data/items/blue_shirt.json: -------------------------------------------------------------------------------- 1 | {"name": "blue t-shirt", 2 | "prefix": "a", 3 | "type": "clothing", 4 | "icon": "T", 5 | "color": [[0, 0, 150], null], 6 | "description": "A plain, blue t-shirt.", 7 | "attaches_to": "lshoulder|rshoulder|chest|stomach", 8 | "flags": "CAN_WEAR|CANSTACK|CAN_BURN|CAN_SOAK", 9 | "craft": [{"name": "Make cloth", "type": "create_item", "requirements": [], "time": 20, "difficulty": {"engineering": 3}, "create_item": {"white cloth": {"failure": {"amount": 2}, "partial_success": {"amount": 4}, "success": {"amount": 6}}}, "strings": {"failure": "You struggle tearing the cloth, destroying it.", "partial_success": "You rip some of the cloth, wasting it.", "success": "You tear the cloth evenly."}}], 10 | "size": "1x1", 11 | "thickness": 1, 12 | "material": "cloth"} -------------------------------------------------------------------------------- /alife/alife_escape.py: -------------------------------------------------------------------------------- 1 | from globals import * 2 | 3 | import life as lfe 4 | 5 | import judgement 6 | import movement 7 | import sight 8 | import brain 9 | 10 | import logging 11 | 12 | STATE = 'hiding' 13 | TIER = TIER_COMBAT-.2 14 | 15 | def conditions(life, alife_seen, alife_not_seen, targets_seen, targets_not_seen, source_map): 16 | RETURN_VALUE = STATE_UNCHANGED 17 | 18 | if not lfe.execute_raw(life, 'state', 'hide'): 19 | if life['state'] == STATE: 20 | lfe.clear_actions(life) 21 | 22 | return False 23 | 24 | if not life['state'] == STATE: 25 | RETURN_VALUE = STATE_CHANGE 26 | 27 | return RETURN_VALUE 28 | 29 | def tick(life): 30 | _threats = judgement.get_threats(life, limit_distance=sight.get_vision(life)) 31 | 32 | return movement.escape(life, _threats) 33 | -------------------------------------------------------------------------------- /data/items/trenchcoat.json: -------------------------------------------------------------------------------- 1 | {"name": "trenchcoat", 2 | "prefix": "a", 3 | "type": "clothing", 4 | "icon": "E", 5 | "color": [[200, 175, 175], null], 6 | "description": "A long trenchcoat.", 7 | "attaches_to": "lshoulder|rshoulder|chest|stomach", 8 | "flags": "CAN_WEAR|CANSTACK|CAN_BURN|CAN_SOAK", 9 | "craft": [{"name": "Make cloth", "type": "create_item", "requirements": [], "time": 20, "difficulty": {"engineering": 3}, "create_item": {"white cloth": {"failure": {"amount": 2}, "partial_success": {"amount": 4}, "success": {"amount": 6}}}, "strings": {"failure": "You struggle tearing the cloth, destroying it.", "partial_success": "You rip some of the cloth, wasting it.", "success": "You tear the cloth evenly."}}], 10 | "size": "1x6", 11 | "capacity": "1x6", 12 | "thickness": 4, 13 | "material": "cloth"} 14 | 15 | -------------------------------------------------------------------------------- /alife/alife_guard.py: -------------------------------------------------------------------------------- 1 | from globals import * 2 | 3 | import life as lfe 4 | 5 | import judgement 6 | import movement 7 | import brain 8 | 9 | import logging 10 | 11 | STATE = 'guarding' 12 | TIER = TIER_COMBAT+.1 13 | 14 | def conditions(life, alife_seen, alife_not_seen, targets_seen, targets_not_seen, source_map): 15 | RETURN_VALUE = STATE_UNCHANGED 16 | 17 | if not lfe.execute_raw(life, 'state', 'guard'): 18 | if life['state'] == STATE: 19 | lfe.clear_actions(life) 20 | 21 | return False 22 | 23 | if not life['state'] == STATE: 24 | RETURN_VALUE = STATE_CHANGE 25 | 26 | return RETURN_VALUE 27 | 28 | def tick(life, alife_seen, alife_not_seen, targets_seen, targets_not_seen, source_map): 29 | _target = judgement.get_target_to_guard(life) 30 | 31 | movement.find_target(life, _target, call=False) 32 | -------------------------------------------------------------------------------- /data/prefabs/test.json: -------------------------------------------------------------------------------- 1 | {"map": [[[null, null, null, null, null], [null, null, null, null, null], [null, null, null, null, null], [null, null, null, null, null], [null, null, null, null, null]], [[null, null, null, null, null], [null, null, null, null, null], [null, null, null, null, null], [null, null, null, null, null], [null, null, null, null, null]], [[null, null, null, null, null], [null, null, null, null, null], [null, null, null, null, null], [null, null, null, null, null], [null, null, null, null, null]], [[null, null, null, null, null], [null, null, null, null, null], [null, null, null, null, null], [null, null, null, null, null], [null, null, null, null, null]], [[null, null, null, null, null], [null, null, null, null, null], [null, null, null, null, null], [null, null, null, null, null], [null, null, null, null, null]]], "size": [5, 5, 5]} -------------------------------------------------------------------------------- /locks.py: -------------------------------------------------------------------------------- 1 | from globals import LOCKS 2 | 3 | 4 | def create_lock(name, locked=False): 5 | if name in LOCKS: 6 | raise Exception('Lock with name \'%s\' already exists.' % name) 7 | 8 | LOCKS[name] = {'locked': locked, 'lock_reason': ''} 9 | 10 | return LOCKS[name] 11 | 12 | def get_lock(lock_name): 13 | if not lock_name in LOCKS: 14 | raise Exception('Lock with name \'%s\' doest not exist.' % lock_name) 15 | 16 | return LOCKS[lock_name] 17 | 18 | def is_locked(lock_name): 19 | return get_lock(lock_name)['locked'] 20 | 21 | def lock(lock_name, reason=''): 22 | get_lock(lock_name)['locked'] = True 23 | 24 | if reason: 25 | print '%s: %s' % (lock_name, reason) 26 | 27 | def unlock(lock_name, reason=''): 28 | get_lock(lock_name)['locked'] = False 29 | 30 | if reason: 31 | print '%s: %s' % (lock_name, reason) 32 | -------------------------------------------------------------------------------- /data/items/brown_hoodie.json: -------------------------------------------------------------------------------- 1 | {"name": "brown hoodie", 2 | "prefix": "a", 3 | "type": "clothing", 4 | "icon": "H", 5 | "color": [[94, 75, 47], null], 6 | "description": "A brown pull-over jacket with hood.", 7 | "attaches_to": "lshoulder|rshoulder|chest|stomach|lupperarm|lelbow|lforearm|rupperarm|relbow|rforearm|head", 8 | "flags": "CAN_WEAR|CANSTACK|CAN_BURN|CAN_SOAK", 9 | "craft": [{"name": "Make cloth", "type": "create_item", "requirements": [], "time": 20, "difficulty": {"engineering": 3}, "create_item": {"white cloth": {"failure": {"amount": 2}, "partial_success": {"amount": 6}, "success": {"amount": 8}}}, "strings": {"failure": "You struggle tearing the cloth, destroying it.", "partial_success": "You rip some of the cloth, wasting it.", "success": "You tear the cloth evenly."}}], 10 | "size": "2x2", 11 | "capacity": "1x1", 12 | "thickness": 1, 13 | "material": "cloth"} 14 | -------------------------------------------------------------------------------- /alife/alife_group.py: -------------------------------------------------------------------------------- 1 | from globals import * 2 | 3 | import life as lfe 4 | 5 | import judgement 6 | import movement 7 | import groups 8 | import speech 9 | import action 10 | import events 11 | import brain 12 | import stats 13 | import jobs 14 | 15 | 16 | def setup(life): 17 | if not life['group']: 18 | #if not stats.desires_to_create_group(life): 19 | # return False 20 | 21 | #groups.create_group(life) 22 | return False 23 | 24 | if not groups.is_leader_of_any_group(life): 25 | return False 26 | 27 | _group = groups.get_group(life, life['group']) 28 | 29 | #groups.manage_jobs(life, life['group']) 30 | #groups.manage_territory(life, life['group']) 31 | groups.manage_combat(life, life['group']) 32 | groups.manage_raid(life, life['group']) 33 | #groups.manage_resources(life, life['group']) 34 | #groups.manage_known_groups(life, life['group']) 35 | #groups.manage_combat(life, life['group']) -------------------------------------------------------------------------------- /tests/M01_data_structure.py: -------------------------------------------------------------------------------- 1 | GRASS_TILE = {'id':'grass', 2 | 'icon':'.', 3 | 'color':'green', 4 | 'burnable':True, 5 | 'cost':1} 6 | 7 | DIRT_TILE = {'id':'dirt', 8 | 'icon':'.', 9 | 'color':'brown', 10 | 'burnable':False, 11 | 'cost':2} 12 | 13 | TILES = [GRASS_TILE,DIRT_TILE] 14 | MAP = [] 15 | 16 | def create_tile(tile): 17 | _ret_tile = {} 18 | _ret_tile['id'] = tile['id'] 19 | 20 | _ret_tile['items'] = [] 21 | _ret_tile['fire'] = 0 22 | 23 | return _ret_tile 24 | 25 | def get_raw_tile(tile): 26 | for _tile in TILES: 27 | if _tile['id'] == tile['id']: 28 | return _tile 29 | 30 | raise Exception 31 | 32 | for x in range(400): 33 | _y = [] 34 | for y in range(400): 35 | _y.append(create_tile(DIRT_TILE)) 36 | 37 | MAP.append(_y) 38 | 39 | print MAP[3][3],get_raw_tile(MAP[3][3])['cost'] 40 | 41 | import sys 42 | print 'Map size (bytes):',sys.getsizeof(MAP) -------------------------------------------------------------------------------- /tools/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | ReactorWatch - Debug 4 | 5 | 6 |

Stats

7 | 12 |

Life

13 | 18 |

Groups

19 | 24 |

Camps

25 | 30 | 31 | -------------------------------------------------------------------------------- /alife/alife_search.py: -------------------------------------------------------------------------------- 1 | from globals import * 2 | 3 | import life as lfe 4 | 5 | import judgement 6 | import movement 7 | import bad_numbers 8 | import speech 9 | import camps 10 | import brain 11 | import jobs 12 | 13 | import logging 14 | 15 | STATE = 'searching' 16 | TIER = TIER_COMBAT 17 | 18 | def conditions(life, alife_seen, alife_not_seen, targets_seen, targets_not_seen, source_map): 19 | RETURN_VALUE = STATE_UNCHANGED 20 | 21 | if not life['state'] == STATE: 22 | RETURN_VALUE = STATE_CHANGE 23 | 24 | _lost_targets = judgement.get_combat_targets(life, escaped_only=True) 25 | brain.store_in_memory(life, 'lost_targets', _lost_targets) 26 | 27 | if not _lost_targets: 28 | return False 29 | else: 30 | print life['name'], _lost_targets 31 | 32 | return RETURN_VALUE 33 | 34 | def tick(life): 35 | _lost_targets = judgement.get_threats(life, escaped_only=True, ignore_escaped=2) 36 | 37 | movement.search_for_target(life, _lost_targets[0]) -------------------------------------------------------------------------------- /data/life/dog.goap: -------------------------------------------------------------------------------- 1 | [GOAL_DISCOVER] 2 | DESIRE: HAS_THREATS 3 | TIER: RELAXED 4 | 5 | [GOAL_FOLLOW] 6 | DESIRE: !HAS_TARGET_TO_FOLLOW 7 | TIER: URGENT 8 | 9 | [GOAL_GUARD] 10 | DESIRE: !HAS_FOCUS_POINT 11 | TIER: SURVIVAL 12 | 13 | [GOAL_KILL_THREAT] 14 | DESIRE: !HAS_THREATS,-!HAS_VISIBLE_THREAT 15 | TIER: COMBAT 16 | 17 | #[GOAL_SEARCH_FOR_TARGET] 18 | #DESIRE: !HAS_LOST_THREAT,-HAS_VISIBLE_THREAT,-!WEAPON_EQUIPPED_AND_READY 19 | #TIER: TACTIC 20 | 21 | 22 | [ACTION_WANDER] 23 | SATISFIES: HAS_THREATS 24 | LOOP_UNTIL: NEVER 25 | EXECUTE: WANDER 26 | 27 | [ACTION_FOLLOW] 28 | SATISFIES: !HAS_TARGET_TO_FOLLOW 29 | LOOP_UNTIL: NEVER 30 | EXECUTE: FOLLOW_TARGET 31 | 32 | [ACTION_GO_TO] 33 | SATISFIES: !HAS_FOCUS_POINT 34 | LOOP_UNTIL: NEVER 35 | EXECUTE: GUARD_FOCUS_POINT 36 | 37 | [ACTION_MELEE_THREAT] 38 | SATISFIES: !HAS_THREATS,-MELEE_READY 39 | EXECUTE: MELEE_ATTACK 40 | LOOP_UNTIL: NEVER 41 | 42 | [ACTION_IDLE] 43 | SATISFIES: IS_IDLE 44 | EXECUTE: NEVER 45 | LOOP_UNTIL: ALWAYS 46 | -------------------------------------------------------------------------------- /alife/alife_manage_targets.py: -------------------------------------------------------------------------------- 1 | from globals import * 2 | import life as lfe 3 | 4 | import alife 5 | import raids 6 | 7 | TIER = TIER_PASSIVE 8 | 9 | def conditions(life, alife_seen, alife_not_seen, targets_seen, targets_not_seen, source_map): 10 | return RETURN_SKIP 11 | 12 | def add_raid_targets(life): 13 | for raider in [LIFE[r] for r in raids.get_raiders(life['camp'])]: 14 | if raider['id'] == life['id']: 15 | continue 16 | 17 | if not alife.brain.knows_alife(life, raider): 18 | alife.brain.meet_alife(life, raider) 19 | 20 | if not lfe.get_memory(life, matches={'text': 'target_is_raiding', 'target': raider['id'], 'camp': life['camp']}): 21 | lfe.memory(life, 22 | 'target_is_raiding', 23 | target=raider['id'], 24 | camp=life['camp']) 25 | 26 | 27 | def tick(life, alife_seen, alife_not_seen, targets_seen, targets_not_seen, source_map): 28 | if life['camp']: 29 | if raids.camp_has_raid(life['camp']): 30 | add_raid_targets(life) -------------------------------------------------------------------------------- /alife/snapshots.py: -------------------------------------------------------------------------------- 1 | import life as lfe 2 | 3 | import logging 4 | import time 5 | 6 | def update_self_snapshot(life,snapshot): 7 | life['snapshot'] = snapshot 8 | 9 | def update_snapshot_of_target(life,target,snapshot): 10 | life['know'][target['id']]['snapshot'].update(snapshot) 11 | 12 | #logging.debug('%s updated their snapshot of %s.' % (' '.join(life['name']), ' '.join(target['name']))) 13 | 14 | def create_snapshot(life): 15 | _snapshot = {'damage': lfe.get_damage(life), 16 | 'appearance': 0, 17 | 'visible_items': [], 18 | 'generated': time.time()} 19 | 20 | for item in lfe.get_all_visible_items(life): 21 | _snapshot['visible_items'].append(str(item)) 22 | 23 | return _snapshot 24 | 25 | def process_snapshot(life,target): 26 | if life['know'][target['id']]['snapshot'] == target['snapshot']: 27 | return False 28 | 29 | _ss = target['snapshot'].copy() 30 | 31 | update_snapshot_of_target(life,target,_ss) 32 | 33 | return True 34 | 35 | def check_snapshot(life,target): 36 | return life['know'][target['id']]['snapshot'] 37 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | Copyright (C) 2013 Luke Martin (flags) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /alife/alife_hidden.py: -------------------------------------------------------------------------------- 1 | #This is intended to be an example of how the new ALife 2 | #system works. 3 | from globals import * 4 | 5 | import life as lfe 6 | 7 | import judgement 8 | import movement 9 | import combat 10 | 11 | import logging 12 | 13 | STATE = 'hidden' 14 | TIER = TIER_COMBAT-.3 15 | 16 | def conditions(life, alife_seen, alife_not_seen, targets_seen, targets_not_seen, source_map): 17 | RETURN_VALUE = STATE_UNCHANGED 18 | 19 | if not lfe.execute_raw(life, 'state', 'hidden'): 20 | return False 21 | 22 | if not life['state'] == STATE: 23 | RETURN_VALUE = STATE_CHANGE 24 | 25 | return RETURN_VALUE 26 | 27 | def tick(life, alife_seen, alife_not_seen, targets_seen, targets_not_seen, source_map): 28 | _threat = judgement.get_nearest_threat(life) 29 | 30 | if not 'hiding' in life['state_flags']: 31 | movement.hide(life, _threat) 32 | life['state_flags']['hiding'] = True 33 | return True 34 | 35 | _weapon = combat.get_best_weapon(life) 36 | 37 | if _weapon: 38 | if not combat.weapon_equipped_and_ready(life): 39 | combat._equip_weapon(life, _weapon['weapon']['uid'], _weapon['feed']['uid']) 40 | -------------------------------------------------------------------------------- /alife/alife_discover.py: -------------------------------------------------------------------------------- 1 | from globals import * 2 | 3 | import life as lfe 4 | 5 | import judgement 6 | import survival 7 | import chunks 8 | import sight 9 | import brain 10 | import smp 11 | 12 | import logging 13 | 14 | def tick(life): 15 | if not lfe.execute_raw(life, 'discover', 'discover_type'): 16 | _lost_method = lfe.execute_raw(life, 'discover', 'when_lost') 17 | if _lost_method: 18 | if not life['path'] or not brain.retrieve_from_memory(life, 'discovery_lock'): 19 | if not 'scanned_chunks' in life['state_flags']: 20 | life['state_flags']['scanned_chunks'] = [] 21 | 22 | sight.scan_surroundings(life, _chunks=brain.get_flag(life, 'visible_chunks'), ignore_chunks=life['state_flags']['scanned_chunks']) 23 | 24 | _explore_chunk = chunks.find_best_chunk(life, ignore_starting=True, ignore_time=True, lost_method=_lost_method, only_recent=True) 25 | brain.store_in_memory(life, 'discovery_lock', True) 26 | brain.store_in_memory(life, 'explore_chunk', _explore_chunk) 27 | 28 | if not _explore_chunk: 29 | brain.flag(life, 'lost') 30 | return False 31 | 32 | survival.explore_known_chunks(life) 33 | else: 34 | return False 35 | -------------------------------------------------------------------------------- /alife/alife_explore.py: -------------------------------------------------------------------------------- 1 | #TODO: THIS CAN BE IDLE BEHAVIOR 2 | 3 | from globals import * 4 | 5 | import life as lfe 6 | 7 | import judgement 8 | import survival 9 | import chunks 10 | import sight 11 | import brain 12 | 13 | import logging 14 | 15 | STATE = 'exploring' 16 | TIER = TIER_EXPLORE-.2 17 | 18 | def conditions(life, alife_seen, alife_not_seen, targets_seen, targets_not_seen, source_map): 19 | return False 20 | 21 | RETURN_VALUE = STATE_UNCHANGED 22 | 23 | if not lfe.execute_raw(life, 'state', 'explore'): 24 | return False 25 | 26 | if not life['state'] == STATE: 27 | RETURN_VALUE = STATE_CHANGE 28 | 29 | #_leading_target = judgement.get_leading_target(life) 30 | #if _leading_target: 31 | # _known = brain.knows_alife_by_id(life, _leading_target) 32 | # 33 | # print _leading_target 34 | 35 | _explore_chunk = chunks.find_best_chunk(life, ignore_time=True) 36 | brain.store_in_memory(life, 'explore_chunk', _explore_chunk) 37 | 38 | if not _explore_chunk: 39 | return False 40 | 41 | return RETURN_VALUE 42 | 43 | def tick(life, alife_seen, alife_not_seen, targets_seen, targets_not_seen, source_map): 44 | if survival.explore_known_chunks(life): 45 | return True 46 | -------------------------------------------------------------------------------- /alife/alife_shelter.py: -------------------------------------------------------------------------------- 1 | from globals import * 2 | 3 | import life as lfe 4 | 5 | import references 6 | import judgement 7 | import chunks 8 | import goals 9 | import maps 10 | 11 | import random 12 | 13 | STATE = 'shelter' 14 | TIER = TIER_SURVIVAL-.1 15 | 16 | def get_tier(life): 17 | if not lfe.execute_raw(life, 'discover', 'desires_shelter') and lfe.execute_raw(life, 'state', 'shelter'): 18 | return TIER_IDLE-.1 19 | 20 | return TIER 21 | 22 | def tick(life): 23 | if not 'shelter' in life['state_flags']: 24 | life['state_flags']['shelter'] = judgement.get_best_shelter(life) 25 | 26 | if not life['state_flags']['shelter'] in life['known_chunks']: 27 | judgement.judge_chunk(life, life['state_flags']['shelter']) 28 | 29 | if not chunks.get_flag(life, life['state_flags']['shelter'], 'shelter_cover'): 30 | return False 31 | 32 | if not list(life['pos'][:2]) in chunks.get_flag(life, life['state_flags']['shelter'], 'shelter_cover'): 33 | if not lfe.path_dest(life) or (not chunks.position_is_in_chunk(lfe.path_dest(life), life['state_flags']['shelter'])): 34 | _cover = chunks.get_flag(life, life['state_flags']['shelter'], 'shelter_cover') 35 | lfe.walk_to(life, random.choice(_cover)) 36 | -------------------------------------------------------------------------------- /alife/alife_surrender.py: -------------------------------------------------------------------------------- 1 | from globals import * 2 | 3 | import life as lfe 4 | 5 | import judgement 6 | import bad_numbers 7 | import speech 8 | import brain 9 | import stats 10 | 11 | import logging 12 | 13 | STATE = 'surrender' 14 | TIER = TIER_SUBMIT 15 | 16 | STATE_ICONS[STATE] = chr(25) 17 | 18 | def conditions(life, alife_seen, alife_not_seen, targets_seen, targets_not_seen, source_map): 19 | RETURN_VALUE = STATE_UNCHANGED 20 | 21 | if not lfe.execute_raw(life, 'state', 'surrender'): 22 | return False 23 | 24 | if not life['state'] == STATE: 25 | lfe.stop(life) 26 | lfe.say(life, '@n gives up.', action=True) 27 | 28 | RETURN_VALUE = STATE_CHANGE 29 | 30 | return RETURN_VALUE 31 | 32 | def tick(life, alife_seen, alife_not_seen, targets_seen, targets_not_seen, source_map): 33 | if lfe.ticker(life, 'call_for_help', 160, fire=True): 34 | _target = judgement.get_nearest_threat(life) 35 | _knows = brain.knows_alife_by_id(life, _target) 36 | if _target and judgement.get_nearest_trusted_target(life): 37 | if _knows: 38 | speech.announce(life, 'attacked_by_hostile', public=True, target_id=_target, last_seen_at=_knows['last_seen_at']) 39 | else: 40 | speech.announce(life, 'attacked_by_hostile', public=True, target_id=_target) 41 | -------------------------------------------------------------------------------- /data/life/dog.dat: -------------------------------------------------------------------------------- 1 | [TALK] 2 | DESIRES_INTERACTION:IS_SAFE 3 | DESIRES_CONVERSATION_WITH:IS_SAME_SPECIES 4 | CAN_TALK_TO:IS_SAME_SPECIES 5 | BATTLE_CRY:NEVER 6 | 7 | [JUDGE] 8 | TRUST:!IS_SAME_SPECIES{TARGET.TRUST-15},!IS_SAME_SPECIES{TARGET.DANGER+15},IS_NERVOUS{TARGET.TRUST-1} 9 | FACTORS:@ALWAYS 10 | BREAK_TRUST:@NEVER 11 | NERVOUS:%IS_NIGHT,!IS_FAMILY 12 | IS_THREAT_IF:HAS_ATTACKED_SELF|HAS_ATTACKED_TRUSTED{TARGET.DANGER+5}|!IS_SAME_SPECIES,IS_NERVOUS 13 | 14 | [SEARCH] 15 | JUDGE:*DISTANCE_TO_POS 16 | 17 | [DISCOVER] 18 | DISCOVER_TYPE:EXPLORE_UNKNOWN_CHUNKS 19 | REMEMBER_SHELTER:ALWAYS 20 | DESIRES_SHELTER:IS_SAFE,%IS_NIGHT,!IS_IN_GROUP|IS_GROUP_LEADER,!IS_GROUP_MOTIVATED_FOR_CRIME|IS_IN_GROUP,GROUP_HAS_SHELTER,!%IS_NIGHT,IS_GROUP_MOTIVATED_FOR_CRIME 21 | WHEN_LOST:"FURTHEST" 22 | 23 | [FOLLOW] 24 | FOLLOW_TARGET_IF:IS_IN_SAME_GROUP,IS_TARGET_GROUP_LEADER,IS_AWAKE 25 | 26 | [GUARD] 27 | GUARD_TARGET_IF:IS_IN_SAME_GROUP,IS_TARGET_GROUP_LEADER,!IS_AWAKE 28 | 29 | [GROUP] 30 | WANTS_GROUP_MEMBER:@NEVER 31 | CREATE_GROUP:@NEVER 32 | 33 | [CAMP] 34 | CAN_CAMP:@NEVER 35 | 36 | [SAFETY] 37 | INTIMIDATED:@NEVER 38 | 39 | [MEMORY] 40 | CACHE_DROP:"investigate_chunk" 41 | 42 | [ITEMS] 43 | GUN:"equip" 44 | 45 | [COMBAT] 46 | SEEK_COMBAT_IF:%IS_NIGHT 47 | TRUST_LOWER_THAN:"-3" 48 | RANGED:NEVER 49 | RANGED_READY:NEVER 50 | MELEE:ALWAYS 51 | MELEE_READY:ALWAYS 52 | 53 | #What target we engage (CLOSEST, HIGHEST, LOWEST) 54 | ENGAGE:"CLOSEST" 55 | -------------------------------------------------------------------------------- /data/life/night_terror.dat: -------------------------------------------------------------------------------- 1 | [TALK] 2 | DESIRES_INTERACTION:NEVER 3 | DESIRES_CONVERSATION_WITH:IS_SAME_SPECIES 4 | CAN_TALK_TO:IS_SAME_SPECIES 5 | BATTLE_CRY:"action" 6 | BATTLE_CRY_ACTION:"@n lurches forward." 7 | 8 | [JUDGE] 9 | TRUST:IS_SAME_SPECIES{TARGET.TRUST+5} 10 | FACTORS:IS_SAME_SPECIES 11 | BREAK_TRUST:IS_NERVOUS{TARGET.DANGER+6} 12 | NERVOUS:@NEVER 13 | AGGRAVATED:%IS_NIGHT,!IS_SAME_SPECIES 14 | 15 | [MOVEMENT] 16 | FOLLOW:@NEVER 17 | 18 | [SEARCH] 19 | JUDGE:*DISTANCE_TO_POS 20 | 21 | [DISCOVER] 22 | DISCOVER_TYPE:NEVER 23 | REMEMBER_SHELTER:NEVER 24 | DESIRES_SHELTER:NEVER 25 | WHEN_LOST:"FURTHEST" 26 | 27 | [GROUP] 28 | WANTS_GROUP_MEMBER:@NEVER 29 | CREATE_GROUP:NEVER 30 | 31 | [CAMP] 32 | CAN_CAMP:NEVER 33 | 34 | [STATE] 35 | EXPLORE:ALWAYS 36 | DISCOVER:NEVER 37 | SURRENDER:NEVER 38 | NEEDS:NEVER 39 | SHELTER:IS_SAFE 40 | COVER:NEVER 41 | HIDE:NEVER 42 | HIDDEN:NEVER 43 | 44 | [SAFETY] 45 | INTIMIDATED:NEVER 46 | 47 | [MEMORY] 48 | TEMP:ALWAYS 49 | 50 | [COMBAT] 51 | SEEK_COMBAT_IF:ALWAYS 52 | TRUST_LOWER_THAN:"0" 53 | RANGED:NEVER 54 | RANGED_READY:NEVER 55 | MELEE:*CAN_BITE,*CAN_SCRATCH 56 | MELEE_READY:ALWAYS 57 | 58 | #What to use for this engagement. LIMB[arg] or WEAPON 59 | #The bit inside the curly braces defines the type of combat: MELEE or RANGE 60 | USE:LIMB[JAW]{MELEE} 61 | 62 | #What target we engage (CLOSEST, HIGHEST, LOWEST) 63 | ENGAGE:CLOSEST 64 | 65 | #What we do to the target: KILL, MAIME 66 | TARGET:KILL 67 | -------------------------------------------------------------------------------- /alife/alife_needs.py: -------------------------------------------------------------------------------- 1 | from globals import * 2 | 3 | import life as lfe 4 | 5 | import judgement 6 | import movement 7 | import survival 8 | import brain 9 | 10 | 11 | def setup(life): 12 | _needs_to_meet = [] 13 | _needs_to_satisfy = [] 14 | _needs_unmet = [] 15 | 16 | for need in life['needs'].values(): 17 | if not survival.needs_to_satisfy(life, need): 18 | continue 19 | 20 | if survival.can_satisfy(life, need): 21 | _needs_to_satisfy.append(need) 22 | 23 | if not survival.can_satisfy(life, need) and not survival.can_potentially_satisfy(life, need): 24 | _needs_unmet.append(need) 25 | continue 26 | 27 | if not need in _needs_to_satisfy and not need in _needs_to_satisfy: 28 | _needs_to_meet.append(need) 29 | 30 | brain.store_in_memory(life, 'needs_to_meet', _needs_to_meet) 31 | brain.store_in_memory(life, 'needs_to_satisfy', _needs_to_satisfy) 32 | brain.store_in_memory(life, 'needs_unmet', _needs_unmet) 33 | 34 | if not _needs_to_meet and not _needs_to_satisfy: 35 | return False 36 | 37 | def tick(life): 38 | if life['actions']: 39 | return True 40 | 41 | _needs_to_meet = brain.retrieve_from_memory(life, 'needs_to_meet') 42 | 43 | for need in _needs_to_meet: 44 | movement.collect_nearby_wanted_items(life, matches=need['match'], only_visible=False) 45 | break 46 | 47 | _needs_to_satisfy = brain.retrieve_from_memory(life, 'needs_to_satisfy') 48 | 49 | for need in _needs_to_satisfy: 50 | survival.satisfy(life, need) 51 | -------------------------------------------------------------------------------- /timers.py: -------------------------------------------------------------------------------- 1 | from globals import WORLD_INFO, ITEMS, LIFE 2 | 3 | import alife 4 | 5 | def is_life(entity): 6 | _life = (not 'prefix' in entity) 7 | 8 | if _life: 9 | _id_key = 'id' 10 | else: 11 | _id_key = 'uid' 12 | 13 | return _life, _id_key 14 | 15 | def create(entity, action, time): 16 | _life, _id_key = is_life(entity) 17 | 18 | _new_timer = {'action': action, 19 | 'time': WORLD_INFO['ticks']+time, 20 | 'owner': entity[_id_key], 21 | 'life': _life} 22 | 23 | _i = 0 24 | for timer in WORLD_INFO['timers']: 25 | if _new_timer['time'] > time['time']: 26 | WORLD_INFO['timers'].insert(_i, _new_timer) 27 | return True 28 | 29 | _i += 1 30 | 31 | WORLD_INFO['timers'].append(_new_timer) 32 | 33 | def remove_by_owner(entity): 34 | _life, _id_key = is_life(entity) 35 | 36 | _remove = [] 37 | for timer in WORLD_INFO['timers']: 38 | if timer['owner'] in LIFE: 39 | if LIFE[timer['owner']] == entity: 40 | _remove.append(timer) 41 | else: 42 | if ITEMS[timer['owner']] == entity: 43 | _remove.append(timer) 44 | 45 | while _remove: 46 | WORLD_INFO['timers'].remove(_remove.pop()) 47 | 48 | def tick(): 49 | if not WORLD_INFO['timers']: 50 | return False 51 | 52 | while WORLD_INFO['ticks'] == WORLD_INFO['timers'][0]['time']: 53 | _event = WORLD_INFO['timers'][0] 54 | 55 | if _event['life']: 56 | _owner = LIFE[_event['owner']] 57 | else: 58 | _owner = ITEMS[_event['owner']] 59 | 60 | alife.action.execute_small_script(_owner, _event['action']) 61 | 62 | if _event in WORLD_INFO['timers']: 63 | WORLD_INFO['timers'].remove(_event) 64 | 65 | if not WORLD_INFO['timers']: 66 | break 67 | -------------------------------------------------------------------------------- /docs/mission_planning.md: -------------------------------------------------------------------------------- 1 | # Dynamic storyteller design doc 2 | The storyteller's job changes over the course of a character's life. 3 | 4 | ## Early-game 5 | The player is dropped into a random situation. Their task it to work their 6 | way out, but in actuality the game is watching how they react and shaping 7 | the content on-the-fly. Right now the player's alignment to major factions 8 | is the only factor decided by this process. Moving forward, opportunities 9 | to expand on this should be noted and explored. 10 | 11 | #### So what's the point? 12 | Even if no content-changing events take place, the player is still being led 13 | one way or another based on how they react. We'll need to account for all 14 | possible paths through a situation in order for the player to not end up lost 15 | and afraid. 16 | 17 | #### A note about free will 18 | The player can choose to just leg it out of any of these situations. I'm unsure 19 | how to handle this right now, since it is possible to survive with just food and 20 | water. 21 | 22 | ### Situation 1 23 | The player wakes up next to 1+ dead bodies. The bodies' loot should be 24 | randomized amounts of the LONER loadout. The player is given no items and should 25 | instead pick them up off the ground/bodies. A person arrives and offers to lead 26 | the player to a Loner camp. 27 | 28 | Special event: A bandit group comes in to loot the bodies. This only affects the 29 | urgency of the situation (dialog with the guy helping also changes.) 30 | 31 | #### Paths 32 | * **A)** The player follows the Loner to a camp -> 33 | * **B)** The player ignores the Loner -> Unsure (see "A note about free will") 34 | 35 | The player is taught: 36 | 37 | * Inventory management. 38 | * Group (if they 39 | 40 | -------------------------------------------------------------------------------- /tools/ReactorWatch.py: -------------------------------------------------------------------------------- 1 | #This tool was rushed together over the course of an hour or so. Be gentle. 2 | 3 | from flask import Flask, render_template, request 4 | 5 | import threading 6 | import socket 7 | import json 8 | 9 | app = Flask(__name__) 10 | 11 | def request(request, value=None): 12 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 13 | sock.settimeout(5) 14 | sock.connect(('127.0.0.1', 3335)) 15 | sock.sendall(json.dumps({'type': 'get', 'what': request, 'value': value})) 16 | data = json.loads(sock.recv(9048)) 17 | sock.close() 18 | 19 | return data 20 | 21 | @app.route('/memory/') 22 | def memory(life_id): 23 | memories = request('memory', value=int(life_id)) 24 | 25 | return render_template('memory.html', life_id=life_id, memories=memories) 26 | 27 | @app.route('/life/') 28 | def life(life_id): 29 | life = request('life', value=int(life_id)) 30 | knows = life['know'].values() 31 | 32 | return render_template('life.html', life=life, knows=knows) 33 | 34 | @app.route('/camp/') 35 | def camp(camp_id): 36 | camp = request('camp', value=int(camp_id)) 37 | 38 | return render_template('camp.html', camp=camp) 39 | 40 | @app.route('/group/') 41 | def group(group_id): 42 | groups = request('groups') 43 | group = groups[group_id] 44 | 45 | return render_template('group.html', group_id=group_id, group=group) 46 | 47 | @app.route('/') 48 | def index(): 49 | groups = request('groups') 50 | 51 | life = request('life_list') 52 | life.sort() 53 | 54 | #camps = request('camp_list') 55 | #camps.sort() 56 | 57 | stats = request('stats') 58 | 59 | return render_template('index.html', stats=stats, life=life, groups=groups) 60 | 61 | if __name__ == '__main__': 62 | app.run(debug=True, port=3336) -------------------------------------------------------------------------------- /debug.py: -------------------------------------------------------------------------------- 1 | from globals import * 2 | 3 | import weather 4 | 5 | import zones 6 | import alife 7 | import items 8 | import life 9 | 10 | def suicide(): 11 | life.kill(LIFE[SETTINGS['following']], 'suicide') 12 | 13 | def kill(life_id): 14 | life.kill(LIFE[life_id], 'suicide') 15 | 16 | def stop(): 17 | LIFE[SETTINGS['following']]['path'] = [] 18 | 19 | def clean_slate(): 20 | for alife in LIFE.values(): 21 | if alife['id'] == SETTINGS['controlling'] or alife['dead']: 22 | continue 23 | 24 | life.kill(alife, 'an act of treason') 25 | 26 | def make_hungry(life_id): 27 | LIFE[life_id]['hunger'] = 500 28 | 29 | def world_hunger(): 30 | for l in LIFE.values(): 31 | l['hunger'] = 500 32 | 33 | def make_thirsty(life_id): 34 | LIFE[life_id]['thirst'] = 500 35 | 36 | def simple_lights(): 37 | SETTINGS['draw light'] = False 38 | 39 | def time(time): 40 | WORLD_INFO['real_time_of_day'] = time 41 | 42 | def timescale(scale): 43 | WORLD_INFO['time_scale'] = scale 44 | 45 | def warp(x, y): 46 | LIFE[SETTINGS['controlling']]['pos'][0] = x 47 | LIFE[SETTINGS['controlling']]['pos'][1] = y 48 | 49 | def camps(): 50 | alife.camps.debug_camps() 51 | 52 | def food(): 53 | items.create_item('corn', position=LIFE[SETTINGS['controlling']]['pos']) 54 | 55 | def drink(): 56 | items.create_item('soda', position=LIFE[SETTINGS['controlling']]['pos']) 57 | 58 | def give(item): 59 | items.create_item(item, position=LIFE[SETTINGS['controlling']]['pos']) 60 | 61 | def day(): 62 | WORLD_INFO['real_time_of_day'] = 1500 63 | 64 | def night(): 65 | WORLD_INFO['real_time_of_day'] = 0 66 | 67 | def toss(): 68 | life.push(LIFE[SETTINGS['controlling']], 0, 2) 69 | 70 | def soldier(): 71 | LIFE[SETTINGS['following']]['stats']['firearms'] = 10 72 | 73 | def weather(): 74 | weather.change_weather() -------------------------------------------------------------------------------- /crafting.py: -------------------------------------------------------------------------------- 1 | import graphics as gfx 2 | import life as lfe 3 | 4 | import scripting 5 | import bad_numbers 6 | import items 7 | 8 | 9 | def get_items_for_crafting(life): 10 | return [i for i in life['inventory'] if 'craft' in items.get_item_from_uid(i)] 11 | 12 | def get_recipe_difficulty(life, item, recipe): 13 | _difficulty = 0 14 | 15 | for skill in recipe['difficulty']: 16 | if skill in life['stats']: 17 | _difficulty += recipe['difficulty'][skill]-life['stats'][skill] 18 | 19 | return _difficulty 20 | 21 | #TODO: stub 22 | def meets_requirements(life, requirements): 23 | return True 24 | 25 | def perform_recipe(life, item, recipe): 26 | lfe.add_action(life, {'action': 'craft', 27 | 'item_uid': item['uid'], 28 | 'recipe_id': item['craft'].index(recipe)}, 29 | 99999, 30 | delay=recipe['time']) 31 | 32 | def execute_recipe(life, item, recipe): 33 | _difficulty = get_recipe_difficulty(life, item, recipe) 34 | _dice_percentage = bad_numbers.roll(2, 5)/(10.0+_difficulty) 35 | 36 | if _dice_percentage >= .75: 37 | _recipe_quality = 'success' 38 | elif _dice_percentage >= .5: 39 | _recipe_quality = 'partial_success' 40 | else: 41 | _recipe_quality = 'failure' 42 | 43 | if recipe['type'] == 'create_item': 44 | for create_item in recipe['create_item']: 45 | if not _recipe_quality in recipe['create_item'][create_item]: 46 | continue 47 | 48 | for i in range(recipe['create_item'][create_item][_recipe_quality]['amount']): 49 | _created_item = items.create_item(create_item) 50 | lfe.add_item_to_inventory(life, _created_item) 51 | 52 | if _recipe_quality in recipe['strings']: 53 | gfx.message(recipe['strings'][_recipe_quality]) 54 | else: 55 | gfx.message('You finish crafting.') 56 | -------------------------------------------------------------------------------- /tools/templates/life.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | ReactorWatch - Life {{ life.id }} 4 | 5 | 6 |

Life {{ life.name }}

7 |
    8 |
  • Name: {{ life.name }}
  • 9 |
  • Memory
  • 10 |
  • State: {{ life.state }}
  • 11 |
  • Job: {{ life.job }}
  • 12 |
  • Group: {{ life.group }}
  • 13 |
  • Stats:
    14 | {% for stat in life.stats %} 15 |
    • {{ stat }}: {{ life.stats[stat] }}
    • 16 |
    17 | {% endfor %} 18 |
  • 19 |
  • Flags ({{ life.flags|count }}):
    20 | {% for flag in life.flags %} 21 |
    • {{ flag }}: {{ life.flags[flag] }}
    • 22 |
    23 | {% endfor %} 24 |
  • 25 |
  • Temp Memory: ({{ life.tempstor|count }}):
    26 | {% for memory in life.tempstor %} 27 |
    • {{ memory }}: {{ life.tempstor[memory] }}
    • 28 |
    29 | {% endfor %} 30 |
  • 31 |
  • Knows ({{ life.know|count }}):
    32 | {% for target in knows %} 33 | {{ target.life }} 34 |
    • Trust: {{ target.trust }}
    • 35 |
    • Danger: {{ target.danger }}
    • 36 |
    • Influence: {{ target.influence }}
    • 37 |
    • Flags: {{ target.flags }}
    • 38 |
    • Impressions: {{ target.impressions }}
    • 39 |
    • Memory
    • 40 |
    41 | {% endfor %} 42 |
  • 43 |
44 | 45 | -------------------------------------------------------------------------------- /data/life/human.dat: -------------------------------------------------------------------------------- 1 | [STATE] 2 | MANAGING:!WEAPON_EQUIPPED_AND_READY,HAS_POTENTIALLY_USABLE_WEAPON 3 | 4 | [TALK] 5 | DESIRES_INTERACTION:IS_SAFE 6 | DESIRES_CONVERSATION_WITH:IS_SAME_SPECIES 7 | CAN_TALK_TO:IS_SAME_SPECIES 8 | BATTLE_CRY:NEVER 9 | 10 | [JUDGE] 11 | TRUST:!IS_SAME_SPECIES{TARGET.TRUST-15},!IS_SAME_SPECIES{TARGET.DANGER+15} 12 | FACTORS:@ALWAYS 13 | BREAK_TRUST:IS_TRAITOR{TARGET.DANGER+5}|HAS_ATTACKED_TRUSTED{TARGET.DANGER+5}|IS_TARGET_GROUP_HOSTILE{TARGET.DANGER+5} 14 | NERVOUS:@NEVER 15 | IS_THREAT_IF:HAS_ATTACKED_SELF|HAS_ATTACKED_TRUSTED{TARGET.DANGER+5}|IS_TARGET_GROUP_HOSTILE{TARGET.DANGER+5}|IS_TARGET_HOSTILE 16 | 17 | [SEARCH] 18 | JUDGE:*DISTANCE_TO_POS 19 | 20 | [DISCOVER] 21 | DISCOVER_TYPE:EXPLORE_UNKNOWN_CHUNKS 22 | REMEMBER_SHELTER:ALWAYS 23 | DESIRES_SHELTER:IS_SAFE,%IS_NIGHT,!IS_IN_GROUP|IS_GROUP_LEADER,!IS_GROUP_MOTIVATED_FOR_CRIME|IS_IN_GROUP,GROUP_HAS_SHELTER,!%IS_NIGHT,IS_GROUP_MOTIVATED_FOR_CRIME 24 | WHEN_LOST:"FURTHEST" 25 | 26 | [FOLLOW] 27 | FOLLOW_TARGET_IF:IS_IN_SAME_GROUP,IS_TARGET_GROUP_LEADER,IS_AWAKE|!@HAS_NEEDS,CAN_TRUST 28 | 29 | [GUARD] 30 | GUARD_TARGET_IF:IS_IN_SAME_GROUP,IS_TARGET_GROUP_LEADER,!IS_AWAKE|IS_DISARMING,!@HAS_THREATS 31 | 32 | [GROUP] 33 | WANTS_GROUP_MEMBER:@ALWAYS 34 | CREATE_GROUP:@IS_BORN_LEADER 35 | 36 | [CAMP] 37 | CAN_CAMP:ALWAYS 38 | 39 | [SAFETY] 40 | INTIMIDATED:IS_COMBAT_READY,IS_AWAKE,!IS_DEAD,IS_SAME_SPECIES,@IS_INCAPACITATED,TARGET_IS_COMBAT_READY|IS_COMBAT_READY,IS_AWAKE,!IS_DEAD,!@WEAPON_EQUIPPED_AND_READY,IS_COMBAT_TARGET,TARGET_IS_COMBAT_READY,IS_SAME_SPECIES 41 | DANGER_CLOSE_RANGE:"5" 42 | 43 | [MEMORY] 44 | CACHE_DROP:"investigate_chunk" 45 | 46 | [ITEMS] 47 | GUN:"equip" 48 | 49 | [COMBAT] 50 | SEEK_COMBAT_IF:WEAPON_EQUIPPED_AND_READY 51 | TRUST_LOWER_THAN:"-3" 52 | RANGED_READY:WEAPON_EQUIPPED_AND_READY 53 | MELEE:ALWAYS 54 | MELEE_READY:ALWAYS 55 | 56 | #What target we engage (CLOSEST, HIGHEST, LOWEST) 57 | ENGAGE:"CLOSEST" 58 | -------------------------------------------------------------------------------- /pyfov.py: -------------------------------------------------------------------------------- 1 | def old_light(los_map, world_pos, size, row, start_slope, end_slope, xx, xy, yx, yy, collision_map, map_size): 2 | _return_chunks = set() 3 | 4 | if start_slope < end_slope: 5 | return los_map, _return_chunks 6 | 7 | x, y, z = world_pos 8 | 9 | _next_start_slope = start_slope 10 | 11 | for i in range(row, size): 12 | _blocked = False 13 | 14 | _d_x = -i 15 | _d_y = -i 16 | while _d_x <= 0: 17 | _l_slope = (_d_x - 0.5) / (_d_y + 0.5) 18 | _r_slope = (_d_x + 0.5) / (_d_y - 0.5) 19 | 20 | if start_slope < _r_slope: 21 | _d_x += 1 22 | continue 23 | elif end_slope>_l_slope: 24 | break 25 | 26 | _sax = _d_x * xx + _d_y * xy 27 | _say = _d_x * yx + _d_y * yy 28 | 29 | if (_sax<0 and abs(_sax)>x) or (_say<0 and abs(_say)>y): 30 | _d_x += 1 31 | continue 32 | 33 | _a_x = x + _sax 34 | _a_y = y + _say 35 | 36 | if _a_x >= map_size[0] or _a_y >= map_size[1]: 37 | _d_x += 1 38 | continue 39 | 40 | _rad2 = size*size 41 | _solid = collision_map[_sax+size, _say+size] 42 | 43 | if (_d_x * _d_x + _d_y * _d_y) < _rad2: 44 | los_map[_sax+size, _say+size] = 1 45 | 46 | if not _solid: 47 | _chunk_key = '%s,%s' % ((_a_x/5)*5, (_a_y/5)*5) 48 | 49 | if not _chunk_key in _return_chunks: 50 | _return_chunks.add(_chunk_key) 51 | 52 | if _blocked: 53 | if _solid: 54 | _next_start_slope = _r_slope 55 | _d_x += 1 56 | continue 57 | else: 58 | _blocked = False 59 | start_slope = _next_start_slope 60 | elif _solid: 61 | _blocked = True 62 | _next_start_slope = _r_slope 63 | _map, _chunk_keys = old_light(los_map, world_pos, size, i+1, start_slope, _l_slope, xx, xy, yx, yy, collision_map, map_size) 64 | 65 | los_map += _map 66 | _return_chunks.update(_chunk_keys) 67 | 68 | _d_x += 1 69 | 70 | if _blocked: 71 | break 72 | 73 | return los_map, _return_chunks -------------------------------------------------------------------------------- /threads.py: -------------------------------------------------------------------------------- 1 | from globals import MAP_SIZE, MAP_WINDOW_SIZE, WORLD_INFO, SETTINGS, LIFE 2 | 3 | import graphics as gfx 4 | 5 | import bad_numbers 6 | import maps 7 | 8 | import threading 9 | import logging 10 | import time 11 | 12 | 13 | class ChunkHandler(threading.Thread): 14 | def __init__(self, check_every=5): 15 | threading.Thread.__init__(self) 16 | 17 | self.last_checked = -check_every 18 | self.check_every = check_every 19 | self.load_clusters = [] 20 | 21 | def check_chunks(self, force=False): 22 | if not force and WORLD_INFO['ticks']-self.last_checked ' 7 | sys.exit(1) 8 | 9 | X_SIZE = int(sys.argv[1]) 10 | Y_SIZE = int(sys.argv[2]) 11 | 12 | SOURCE_MAP = numpy.zeros((X_SIZE,Y_SIZE)) 13 | 14 | #print SOURCE_MAP.shape,X_SIZE,Y_SIZE 15 | 16 | def simulate(MAP): 17 | NEXT_MAP = numpy.zeros((X_SIZE,Y_SIZE)) 18 | 19 | _i = 0 20 | while 1: 21 | _i += 1 22 | NEXT_MAP = MAP.copy() 23 | 24 | 25 | for MOD_Y in range(X_SIZE-1): 26 | for MOD_X in range(Y_SIZE-1): 27 | if MAP[MOD_Y,MOD_X] == -1: 28 | continue 29 | 30 | #NEIGHBOR_COUNT = 0 31 | LARGEST_SCORE = MAP[MOD_Y,MOD_X]+.90 32 | 33 | for X_OFFSET in range(-1,2): 34 | for Y_OFFSET in range(-1,2): 35 | x = MOD_X+X_OFFSET 36 | y = MOD_Y+Y_OFFSET 37 | 38 | #print Y_SIZE-1 39 | if 0>x or 0>y or x>=X_SIZE-1 or y>=Y_SIZE-1 or MAP[y,x]<=-1: 40 | continue 41 | 42 | if MAP[y,x] > LARGEST_SCORE: 43 | LARGEST_SCORE = MAP[y,x] 44 | 45 | NEXT_MAP[MOD_Y,MOD_X] = LARGEST_SCORE-(0.95) 46 | 47 | if NEXT_MAP[MOD_Y,MOD_X]<0: 48 | NEXT_MAP[MOD_Y,MOD_X] = 0 49 | 50 | draw_map(NEXT_MAP) 51 | 52 | if numpy.array_equal(MAP,NEXT_MAP): 53 | print 'Took: ',_i 54 | return NEXT_MAP 55 | 56 | MAP = NEXT_MAP.copy() 57 | 58 | def dissolve(MAP): 59 | for MOD_Y in range(Y_SIZE): 60 | for MOD_X in range(X_SIZE): 61 | if MAP[MOD_X,MOD_Y] <= 0: 62 | continue 63 | 64 | 65 | 66 | def draw_map(MAP): 67 | for MOD_Y in range(Y_SIZE): 68 | for MOD_X in range(X_SIZE): 69 | NEIGHBOR_COUNT = 0 70 | LARGEST_SCORE = 0 71 | if MAP[MOD_X,MOD_Y] == -1: 72 | print 'x', 73 | else: 74 | print MAP[MOD_X,MOD_Y], 75 | 76 | print '' 77 | 78 | 79 | SOURCE_MAP[5,5]=8 80 | SOURCE_MAP[5,4]=-1 81 | SOURCE_MAP[5,3]=-1 82 | SOURCE_MAP[6,4]=-1 83 | 84 | for x in range(3): 85 | SOURCE_MAP[7+x,4]=-1 86 | 87 | START_TIME = time.time() 88 | SOURCE_MAP = simulate(SOURCE_MAP) 89 | 90 | print time.time()-START_TIME 91 | 92 | draw_map(SOURCE_MAP) 93 | -------------------------------------------------------------------------------- /contexts.py: -------------------------------------------------------------------------------- 1 | from globals import SETTINGS, LIFE 2 | 3 | import life as lfe 4 | 5 | import encounters 6 | import graphics 7 | import dialog 8 | import alife 9 | import logic 10 | 11 | import logging 12 | 13 | def _create_context_from_phrase(life, phrase): 14 | _reactions = [] 15 | 16 | if phrase['gist'] == 'comply': 17 | _reactions.append({'type': 'say','text': 'I give up!', 18 | 'communicate': 'surrender'}) 19 | 20 | if lfe.get_held_items(life, matches=[{'type': 'gun'}]): 21 | _reactions.append({'action': 'action', 22 | 'text': '' % ' '.join(phrase['from']['name'])}) 23 | 24 | elif phrase['gist'] == 'demand_drop_item': 25 | _reactions.append({'type': 'action','text': 'Drop the item.', 26 | 'action': {'action': 'dropitem','item': phrase['item']}, 27 | 'score': 900, 28 | 'delay': lfe.get_item_access_time(life,phrase['item']), 29 | 'communicate': 'dropped_demanded_item'}) 30 | 31 | elif phrase['gist'] == 'dialog': 32 | if not phrase['dialog_id'] in LIFE[SETTINGS['controlling']]['dialogs']: 33 | life['dialogs'].append(phrase['dialog_id']) 34 | 35 | if dialog.get_last_message(phrase['dialog_id'])['text']: 36 | logic.show_event(dialog.get_last_message(phrase['dialog_id'])['text'], life=phrase['from']) 37 | 38 | if dialog.is_turn_to_talk(LIFE[SETTINGS['controlling']], phrase['dialog_id']): 39 | dialog.process(LIFE[SETTINGS['controlling']], phrase['dialog_id']) 40 | 41 | elif phrase['gist'] == 'looks_hostile': 42 | #encounters.create_encounter(life, phrase['from']) 43 | #logic.show_event( 44 | alife.speech.start_dialog(phrase['from'], life['id'], 'encounter') 45 | #else: 46 | # logging.warning('Unhandled player context: %s' % phrase['gist']) 47 | 48 | return _reactions 49 | 50 | def create_context(life, action, timeout_callback=None): 51 | #logging.debug('** Created new context %s **' % action['gist']) 52 | 53 | if 'gist' in action: 54 | _reactions = _create_context_from_phrase(life, action) 55 | 56 | return {'action': 'None', 57 | 'text': 'Nothing here!', 58 | 'items': [], 59 | 'reactions': _reactions, 60 | 'from': action['from'], 61 | 'timeout_callback': timeout_callback, 62 | 'time': 150} 63 | -------------------------------------------------------------------------------- /tests/fast_render_simple_numpy.py: -------------------------------------------------------------------------------- 1 | #The majority of this code is not mine. 2 | #It was originally used to demonstrate 3 | #fast rendering in libtcod using Numpy. 4 | #I adapted it to do the lighting for 5 | #Reactor 3. 6 | 7 | #Original source: 8 | #http://doryen.eptalys.net/forum/index.php?topic=467.0 9 | 10 | import libtcodpy as libtcod 11 | import os 12 | 13 | try: 14 | from numpy import * 15 | except ImportError: 16 | raise ImportError('----- NumPy must be installed. -----') 17 | 18 | SCREEN_W = 80 19 | SCREEN_H = 50 20 | HALF_W = SCREEN_W / 2 21 | HALF_H = SCREEN_H / 2 22 | 23 | libtcod.console_set_custom_font(os.path.join('arial10x10.png'), 24 | libtcod.FONT_TYPE_GREYSCALE | libtcod.FONT_LAYOUT_TCOD) 25 | libtcod.console_init_root(SCREEN_W, SCREEN_H, 'libtcod sample', False) 26 | 27 | (x, y) = meshgrid(range(SCREEN_W), range(SCREEN_H)) 28 | 29 | lights = [] 30 | lights.append({'x': 40,'y': 20,'brightness': 4.0}) 31 | lights.append({'x': 20,'y': 20,'brightness': 3.0}) 32 | 33 | while not libtcod.console_is_window_closed(): 34 | key = libtcod.console_check_for_keypress() 35 | if key.vk == libtcod.KEY_ESCAPE: break 36 | 37 | _R = zeros((SCREEN_H,SCREEN_W)) 38 | _R = add(_R,255) 39 | _G = zeros((SCREEN_H,SCREEN_W)) 40 | _G = add(_G,255) 41 | _B = zeros((SCREEN_H,SCREEN_W)) 42 | _B = add(_B,255) 43 | _RB = zeros((SCREEN_H,SCREEN_W)) 44 | _GB = zeros((SCREEN_H,SCREEN_W)) 45 | _BB = zeros((SCREEN_H,SCREEN_W)) 46 | _RB = add(_RB,255) 47 | _GB = add(_GB,255) 48 | _BB = add(_BB,255) 49 | render = zeros((SCREEN_H,SCREEN_W)) 50 | 51 | for light in lights: 52 | if lights.index(light) == 0: 53 | light['x'] -= 0.01 54 | 55 | sqr_distance = (x - light['x'])**2 + (y - light['y'])**2 56 | 57 | brightness = light['brightness'] / sqr_distance 58 | brightness = clip(brightness * 255, 0, 255) 59 | 60 | _RB = subtract(_RB,brightness).clip(0,255) 61 | _GB = subtract(_GB,brightness).clip(0,255) 62 | _BB = subtract(_BB,brightness).clip(0,255) 63 | 64 | _R = subtract(_R,_RB).clip(0,255) 65 | _G = subtract(_G,_GB).clip(0,255) 66 | _B = subtract(_B,_BB).clip(0,255) 67 | 68 | libtcod.console_fill_background(0, _R, _G, _B) 69 | libtcod.console_fill_foreground(0, _R, _G, _B) 70 | print libtcod.sys_get_fps() 71 | libtcod.console_flush() 72 | 73 | -------------------------------------------------------------------------------- /docs/testingnotes.md: -------------------------------------------------------------------------------- 1 | Tracking duplicate weapons is difficult 2 | Wounds don't disappear from medical menu 3 | Item access times should be higher for retrieving items from containers that are full 4 | obviously number of items doesn't matter if the container only holds 4 or 5 of them 5 | Large cotainers (10+ items) 6 | de/highlight_position() to get rid of complete screen refresh 7 | The visible targets list appears to grow larger (pushes debug text down) but shows no names 8 | Might be dead agents 9 | Outposts are too close to player spawn. 10 | Limit on number of duplicate cell types in certain radius 11 | Need multiple zone entry points 12 | Crash on equipping storage item from storage 13 | Some military NPCs aren't reloading 14 | 15 | Alife wants group at night 16 | 17 | Put more space between buildings (maybe on a per-type basis. 18 | Space for stores (parking lot, etc) 19 | Space for houses (small yard, fenced-in backyard 20 | 21 | Message box colors? 22 | Sort inventory (or equip menu) by category? 23 | 24 | Worldgen: item spawns 25 | Having buildings generated on the side of the roads? 26 | If we have to seed this, judge based on the location of the lighest color of the road tiles (BROKEN_*) 27 | 28 | Global radio messages 29 | PDA (press tab?) 30 | 31 | Factor weather into visiblity 32 | 33 | "Scan" mode. Over a number of ticks, a heatmap is generated of the 34 | best places to hide in the surrounding area 35 | 36 | When weapons are dropped, the magazines inside the guns are not disowned. 37 | 38 | healed_by memory to boost leadership? 39 | then "Thanks!" 40 | Changes in relationships after memories 41 | Would make combat affect ALife behavior more 42 | 43 | When rendering items, render the most important one on top (gun -> health kit -> ammo) 44 | 45 | AI: What should I do? 46 | 47 | 48 | 49 | Content: 50 | Lock weapon crates: Use small explosives to open them, etc 51 | It's a "task" to open them. Gives the player something to do 52 | 53 | 54 | 55 | Do not clear orders until group leader allows it 56 | i.e., when the AI hits a guard point it should not be cleared (they should wait there) 57 | 58 | 59 | "Are you armed?" 60 | "Negative" 61 | "Stay behind us, then!" 62 | 63 | 64 | 65 | CALL FOR BACKUP - "hot" areas of map that attrack people 66 | -------------------------------------------------------------------------------- /alife/alife_combat.py: -------------------------------------------------------------------------------- 1 | from globals import * 2 | 3 | import graphics as gfx 4 | import life as lfe 5 | 6 | import judgement 7 | import bad_numbers 8 | import combat 9 | import speech 10 | import sight 11 | import camps 12 | import brain 13 | import stats 14 | import logic 15 | import jobs 16 | 17 | import logging 18 | 19 | 20 | STATE = 'combat' 21 | TIER = TIER_COMBAT-.4 22 | 23 | 24 | def conditions(life, alife_seen, alife_not_seen, targets_seen, targets_not_seen, source_map): 25 | RETURN_VALUE = STATE_UNCHANGED 26 | 27 | _mode = None 28 | if lfe.execute_raw(life, 'state', 'combat'): 29 | _mode = 'combat' 30 | 31 | if not _mode and lfe.execute_raw(life, 'state', 'hunt'): 32 | _mode = 'hunt' 33 | 34 | if not _mode: 35 | return False 36 | 37 | if not lfe.execute_raw(life, 'combat', 'ranged') and not lfe.execute_raw(life, 'combat', 'melee'): 38 | return False 39 | 40 | if not life['state'] == STATE: 41 | life['state_flags'] = {} 42 | stats.battle_cry(life) 43 | 44 | if gfx.position_is_in_frame(life['pos']) and SETTINGS['controlling']: 45 | _can_see = sight.can_see_position(life, LIFE[SETTINGS['controlling']]['pos']) 46 | 47 | if _can_see: 48 | _knows = brain.knows_alife_by_id(life, SETTINGS['controlling']) 49 | 50 | if _knows and judgement.can_trust(life, SETTINGS['controlling']): 51 | if lfe.ticker(life, 'enter_combat_message', 3, fire=True): 52 | logic.show_event('%s readies up.' % ' '.join(life['name']), life=life) 53 | 54 | RETURN_VALUE = STATE_CHANGE 55 | 56 | brain.flag(life, 'combat_mode', value=_mode) 57 | 58 | return RETURN_VALUE 59 | 60 | def ranged_attack(life): 61 | _all_targets = judgement.get_threats(life, ignore_escaped=False) 62 | 63 | combat.ranged_combat(life, _all_targets) 64 | 65 | def melee_attack(life): 66 | _all_targets = judgement.get_threats(life) 67 | 68 | combat.melee_combat(life, _all_targets) 69 | 70 | def tick(life, alife_seen, alife_not_seen, targets_seen, targets_not_seen, source_map): 71 | _all_targets = judgement.get_threats(life, ignore_escaped=1) 72 | 73 | if lfe.execute_raw(life, 'combat', 'ranged_ready', break_on_true=True, break_on_false=False): 74 | combat.ranged_combat(life, _all_targets) 75 | 76 | if lfe.execute_raw(life, 'combat', 'melee_ready', break_on_true=True, break_on_false=False): 77 | combat.melee_combat(life, _all_targets) -------------------------------------------------------------------------------- /docs/style_guide.md: -------------------------------------------------------------------------------- 1 | Programming Style Guide 2 | ----------------------- 3 | A quick note: Throughout development my programming style changed somewhat, so you will see code that does not follow the rules below. All new code obeys these rules. 4 | 5 | **This is not a primer on writing effective Python. I am also not a World Famous Superstar Master Python Coder.** 6 | 7 | * Use tabs for line indents. Spaces can be used AFTER tabs to help improve readability or for stylistic reasons if needed. 8 | * Variable names should be prefixed with an underscore (`_chunk_key`) if they are defined outside of a `for` loop. Variables used to iterate through lists/are created in the for loop's definition should omit the underscore. Arguments should never start with an underscore unless keyword arguments (`kwargs`) are expected to clash with them. 9 | 10 | Abusing Lambdas 11 | ------------- 12 | Throughout the code you'll see opportunites to filter results of certain functions by passing a function to keyword argument `filter_if`. However, `filter_if` is usually only called with one argument, rendering the majority of possible filters that need extra arguments completely useless. Instead of filtering the results after they are returned, we can pack the variables we need to access later inside the lambda's definition. 13 | 14 | def manage_combat(life, group_id): 15 | if get_stage(life, group_id) == STAGE_RAIDING: 16 | prepare_for_raid(life, group_id) 17 | return False 18 | 19 | for known_group_id in life['known_groups']: 20 | if group_id == known_group_id: 21 | continue 22 | 23 | if not get_group_memory(life, known_group_id, 'alignment') == 'hostile': 24 | announce(life, group_id, 'inform_of_known_group', group_id=known_group_id, 25 | filter_if=lambda alife: group_exists(alife, known_group_id)) 26 | 27 | _known_group_members = get_group_memory(life, known_group_id, 'members') 28 | 29 | In the above example we can see that `known_group_id` is created in the local scope, but can still be referenced and used inside of the lambda when it is called later. Another example, this time showing an entire `life` structure being passed: 30 | 31 | announce(life, group_id, 'combat_ready', ignore_if_said_in_last=1000, filter_if=lambda alife: brain.get_alife_flag(life, alife['id'], 'combat_ready')) 32 | 33 | Note that the lambda's argument was renamed to `alife` to prevent conflicts with `life`. 34 | -------------------------------------------------------------------------------- /scripting.py: -------------------------------------------------------------------------------- 1 | #Command 2 | # CREATE_ITEM(, ) 3 | # DELETE() 4 | 5 | from globals import * 6 | 7 | import graphics as gfx 8 | 9 | import effects 10 | import items 11 | import life 12 | 13 | import logging 14 | import re 15 | 16 | def parse_console(text): 17 | return text.replace('pc', 'SETTINGS[\'controlling\']') 18 | 19 | def execute(script, **kvargs): 20 | for function in script: 21 | _args = parse_arguments(script[function], **kvargs) 22 | 23 | if function == 'CREATE_AND_OWN_ITEM': 24 | _i = items.create_item(_args[0], position=_args[1]) 25 | life.add_item_to_inventory(kvargs['owner'], _i) 26 | elif function == 'DELETE': 27 | items.delete_item(ITEMS[kvargs['item_uid']]) 28 | elif function == 'LIGHT_FOLLOW': 29 | _item = ITEMS[kvargs['item_uid']] 30 | 31 | effects.create_light(items.get_pos(kvargs['item_uid']), 32 | (255, 255, 255), 33 | _item['brightness'], 34 | _item['light_shake'], 35 | follow_item=kvargs['item_uid']) 36 | elif function == 'LIGHT_FOLLOW_REMOVE': 37 | _item = ITEMS[kvargs['item_uid']] 38 | 39 | effects.delete_light_at(items.get_pos(kvargs['item_uid'])) 40 | elif function == 'TOGGLE_BLOCK': 41 | _item = ITEMS[kvargs['item_uid']] 42 | 43 | if _item['blocking']: 44 | _item['blocking'] = False 45 | else: 46 | _item['blocking'] = True 47 | else: 48 | logging.error('Script: \'%s\' is not a valid function.' % function) 49 | 50 | def initiate(owner, text): 51 | _functions = get_functions(owner, text) 52 | 53 | return _functions 54 | 55 | def parse_arguments(arguments, **kvargs): 56 | _returned_arguments = [] 57 | for arg in [arg.strip().rstrip(')') for arg in arguments.split(',')]: 58 | _returned_arguments.append(parse_argument(ITEMS[kvargs['item_uid']], arg)) 59 | 60 | return _returned_arguments 61 | 62 | def parse_argument(owner, argument): 63 | for match in re.findall('(self.[a-zA-Z_]*)', argument): 64 | _value = match.split('.')[1] 65 | 66 | if not _value in owner: 67 | logging.error('Script syntax: \'%s\' not found in self.' % _value) 68 | return None 69 | 70 | return owner[_value] 71 | 72 | return argument 73 | 74 | def get_functions(owner, text): 75 | _functions = {} 76 | 77 | for func in text.split(':'): 78 | for function in re.findall('[a-zA-Z_]*\(.*\)', func): 79 | _name,_args = function.split('(') 80 | _functions[_name] = _args#parse_arguments(owner, _args) 81 | 82 | return _functions 83 | -------------------------------------------------------------------------------- /maputils.py: -------------------------------------------------------------------------------- 1 | from globals import * 2 | from tiles import * 3 | 4 | import zones 5 | import maps 6 | 7 | import random 8 | import copy 9 | import sys 10 | 11 | def get_map_size(map): 12 | return (len(map),len(map[0]),len(map[0][0])) 13 | 14 | def resize_map(map,size): 15 | _old_size = get_map_size(map) 16 | 17 | if _old_size[0]>size[0] or _old_size[1]>size[1] or _old_size[2]>size[2]: 18 | print 'Warning: Attempting to shink the map! Data will be lost.' 19 | 20 | _new_map = copy.deepcopy(map) 21 | 22 | if _old_size[0]>size[0]: 23 | for x in range(abs(_old_size[0]-size[0])): 24 | _new_map.pop() 25 | elif _old_size[0]size[1]: 44 | for x in range(size[0]): 45 | for y in range(abs(_old_size[1]-size[1])): 46 | _new_map[x].pop() 47 | elif _old_size[1]size[2]: 64 | for x1 in range(_old_size[0]): 65 | for y1 in range(_old_size[1]): 66 | for z in range(abs(_old_size[2]-size[2])): 67 | _new_map[x1][y1].pop() 68 | elif _old_size[2]+f - Appear Friendly') 39 | _text.append('+h - Appear Hostile') 40 | #_text.append('+s - Surrender') 41 | #_text.append('+q - Ignore') 42 | _text.append('_' * 38) 43 | 44 | _encounter['text'] = _text 45 | _encounter['start_time'] = WORLD_INFO['ticks'] 46 | 47 | SETTINGS['following'] = target['id'] 48 | life['encounters'].append(_encounter) 49 | logging.debug('%s created encounter.' % ' '.join(target['name'])) 50 | SETTINGS['encounter animation timer'] = ENCOUNTER_ANIMATION_TIME 51 | 52 | return _encounter 53 | 54 | def draw_encounter(life, encounter): 55 | if SETTINGS['encounter animation timer']>0: 56 | if SETTINGS['encounter animation timer'] == ENCOUNTER_ANIMATION_TIME: 57 | lfe.set_animation(encounter['target'], TICKER, speed=1, loops=2) 58 | 59 | SETTINGS['encounter animation timer']-=1 60 | return False 61 | 62 | if not 'console' in encounter: 63 | encounter['console'] = tcod.console_new(40, 40) 64 | 65 | _y = 1 66 | for line in encounter['text']: 67 | _x = 1 68 | 69 | while line: 70 | _line = line 71 | while len(_line)>=40: 72 | _words = _line.split(' ') 73 | _line = ' '.join(_words[:len(_words)-1]) 74 | 75 | _lines = [_line] 76 | 77 | if not _line == line: 78 | _lines.append(line.replace(_line, '')) 79 | 80 | _i = 0 81 | for txt in _lines: 82 | tcod.console_print(encounter['console'], 83 | _x+_i, 84 | _y, 85 | txt) 86 | 87 | _i += 1 88 | _y += 1 89 | 90 | _x += 1 91 | _y += 1 92 | 93 | break 94 | -------------------------------------------------------------------------------- /events.py: -------------------------------------------------------------------------------- 1 | from globals import LIFE 2 | 3 | import alife 4 | 5 | import logging 6 | 7 | def create(name, process_callback, process_arguments, complete_callback=None, complete_arguments=None, fail_callback=None, fail_arguments=None, repeat_every=-1): 8 | _event = {'name': name, 9 | 'process': {'callback': process_callback, 'arguments': process_arguments}, 10 | 'complete_on': {'callback': complete_callback, 'arguments': complete_arguments}, 11 | 'fail_on': {'callback': fail_callback, 'arguments': fail_arguments}, 12 | 'repeat': repeat_every, 13 | 'completed': False, 14 | 'failed': False, 15 | 'accepted': [], 16 | 'flags': {}} 17 | 18 | return _event 19 | 20 | def process_event(event): 21 | event['failed'] = False 22 | 23 | if event['completed']: 24 | return True 25 | 26 | if event['complete_on']['callback'] and event['complete_on']['callback'](**event['complete_on']['arguments']): 27 | event['completed'] = True 28 | return True 29 | 30 | if event['fail_on']['callback']: 31 | _args = {} 32 | 33 | for key in event['fail_on']['arguments']: 34 | if isinstance(event['fail_on']['arguments'][key], dict) and '_action' in event['fail_on']['arguments'][key]: 35 | _args[key] = alife.action.execute(event['fail_on']['arguments'][key]) 36 | else: 37 | _args[key] = event['fail_on']['arguments'][key] 38 | 39 | if not alife.action.execute(event['fail_on']['callback'])(**_args): 40 | event['failed'] = True 41 | return False 42 | 43 | if event['process']: 44 | _args = {} 45 | for key in event['process']['arguments']: 46 | if isinstance(event['process']['arguments'][key], dict) and '_action' in event['process']['arguments'][key]: 47 | _args[key] = alife.action.execute(event['process']['arguments'][key]) 48 | else: 49 | _args[key] = event['process']['arguments'][key] 50 | 51 | alife.action.execute(event['process']['callback'])(**_args) 52 | 53 | return True 54 | 55 | def accept(event, life_id): 56 | if life_id in event['accepted']: 57 | return False 58 | 59 | event['accepted'].append(life_id) 60 | 61 | logging.debug('%s has accepted event: %s' % (' '.join(LIFE[life_id]['name']), event['name'])) 62 | 63 | def has_accepted(event, life_id): 64 | if life_id in event['accepted']: 65 | return True 66 | 67 | return False 68 | 69 | def clear_accepted(event): 70 | event['accepted'] = [] 71 | 72 | logging.debug('Accepted list cleared for event: %s' % event['name']) 73 | 74 | def flag(event, flag, value): 75 | _event['flags'][flag] = value 76 | 77 | def unflag(event, flag): 78 | del _event['flags'][flag] 79 | 80 | def get_flag(event, flag): 81 | if flag in _event['flags']: 82 | return _event['flags'][flag] 83 | 84 | return False -------------------------------------------------------------------------------- /artifacts.py: -------------------------------------------------------------------------------- 1 | from globals import WORLD_INFO, MAP_SIZE, SETTINGS, LIFE 2 | from alife import factions 3 | 4 | import graphics as gfx 5 | 6 | import effects 7 | import bad_numbers 8 | import timers 9 | import items 10 | import life 11 | 12 | import random 13 | 14 | 15 | def find_territory(has_owner=False, y_min=0): 16 | _territories = [] 17 | 18 | for territory_id in WORLD_INFO['territories']: 19 | _territory = WORLD_INFO['territories'][territory_id] 20 | _chunk_key = random.choice(_territory['chunk_keys']) 21 | 22 | if WORLD_INFO['chunk_map'][_chunk_key]['pos'][1]/float(MAP_SIZE[1])Night Terror 2 | Night Terror 3 | mutant 4 | 5 | LEGS[blleg,brleg]|CAN_GROUP|CAN_SEE|HUNGER|THIRST|MELEE[jaw,flpaw,frpaw] 6 | vision_max=20|hunger_max=20000|thirst_max=10000|hunger=hunger_max|thirst=thirst_max 7 | T 8 | 9 | 10 | 11 | SKIN|BONE|CRUCIAL|CAN_HOLD|CAN_BITE 12 | 1 13 | 1.5 14 | 2 15 | 16 | 17 | 18 | SKIN|BONE|CRUCIAL|AFFECTS[sight,smell,hearing] 19 | jaw 20 | 2 21 | 1.5 22 | 4 23 | 24 | 25 | 26 | SKIN|MUSCLE|BONE|CRUCIAL 27 | head 28 | 2 29 | 2 30 | 3 31 | 32 | 33 | 34 | SKIN|MUSCLE|BONE|CRUCIAL 35 | neck 36 | 2 37 | 1.5 38 | 2 39 | 40 | 41 | 42 | SKIN|MUSCLE|BONE 43 | chest 44 | 1 45 | 0.5 46 | 2 47 | 48 | 49 | 50 | SKIN|MUSCLE|BONE 51 | chest 52 | 1 53 | 0.5 54 | 2 55 | 56 | 57 | 58 | SKIN|MUSCLE|BONE|SHARP 59 | flleg 60 | 1 61 | 0.5 62 | 1 63 | 64 | 65 | 66 | SKIN|MUSCLE|BONE|SHARP 67 | frleg 68 | 1 69 | 0.5 70 | 1 71 | 72 | 73 | 74 | SKIN|MUSCLE|BONE|CRUCIAL 75 | chest 76 | 1 77 | 1.5 78 | 4 79 | 80 | 81 | 82 | SKIN|MUSCLE|BONE|CRUCIAL 83 | stomach 84 | 2 85 | 1 86 | 3 87 | 88 | 89 | 90 | SKIN|MUSCLE|BONE 91 | hip 92 | 1 93 | 1.3 94 | 2 95 | 96 | 97 | 98 | SKIN|MUSCLE|BONE 99 | hip 100 | 1 101 | 1.3 102 | 2 103 | 104 | 105 | 106 | SKIN|MUSCLE|BONE|SHARP 107 | blleg 108 | 1 109 | 0.5 110 | 1 111 | 112 | 113 | 114 | SKIN|MUSCLE|BONE|SHARP 115 | brleg 116 | 1 117 | 0.5 118 | 1 119 | 120 | 121 | -------------------------------------------------------------------------------- /tools/guntest.py: -------------------------------------------------------------------------------- 1 | import random 2 | import numpy 3 | import json 4 | import math 5 | import sys 6 | 7 | 8 | if len(sys.argv) == 1: 9 | sys.exit(1) 10 | 11 | 12 | _weapon = {'recoil': 0.3, 13 | 'accuracy': 0.9} 14 | stance_mod = 1.0 15 | aim_difficulty = 1.8 16 | firearms_skill_mod = 0.55 17 | hit_certainty_mod = 0.6 18 | bullets = 10 19 | simulaton_ticks = 8 20 | 21 | def load_weapon(weapon_file): 22 | with open(weapon_file, 'r') as wfile: 23 | _weapon.update(json.loads(''.join(wfile.readlines()))) 24 | 25 | def velocity(direction, speed): 26 | rad = direction*(math.pi/180) 27 | velocity = numpy.multiply(numpy.array([math.cos(rad), math.sin(rad)]), speed) 28 | 29 | return [velocity[0], -velocity[1], 0] 30 | 31 | def clip(number,start,end): 32 | return max(start, min(number, end)) 33 | 34 | def bullet_trajectory(bullet_pos, bullet_direction, ticks=simulaton_ticks): 35 | bullet_velocity = velocity(bullet_direction, 5) 36 | 37 | for i in range(ticks): 38 | bullet_pos[0] += bullet_velocity[0] 39 | bullet_pos[1] += bullet_velocity[1] 40 | 41 | return bullet_pos 42 | 43 | def simulate(firearms_skill, recoil=0.0): 44 | recoil = float(recoil) 45 | bullet_direction = 0 46 | bullet_pos = [0, 0] 47 | bullet_velocity = [0, 0] 48 | _deviations = [] 49 | _hits = 0 50 | 51 | for i in range(bullets): 52 | bullet_velocity = [0, 0] 53 | bullet_pos = [0, 0] 54 | bullet_deviation = (1-_weapon['accuracy'])+recoil 55 | deviation_mod = aim_difficulty*(1-((firearms_skill/10.0)*firearms_skill_mod)) 56 | deviation = (bullet_deviation*aim_difficulty)*deviation_mod 57 | bullet_direction = random.uniform(-deviation, deviation) 58 | 59 | ######################## 60 | ## ENABLE FOR RELEASE ## 61 | ######################## 62 | #recoil = clip(recoil+(_weapon['recoil']*stance_mod), 0.0, 1.0) 63 | 64 | _end_pos = bullet_trajectory(bullet_pos, bullet_direction) 65 | _direction_deviation = abs(bullet_direction) 66 | _target_hit_certainty = _direction_deviation/hit_certainty_mod 67 | _hits += _target_hit_certainty<=1 68 | _deviations.append(_direction_deviation) 69 | 70 | #print 'Deviations: mean=%0.4f, min=%0.4f, max=%0.4f' % (sum(_deviations)/len(_deviations), min(_deviations), max(_deviations)) 71 | #print 'Accuracy:', _hits/float(bullets), '\n' 72 | 73 | #print 'Trajectory deviation: %0.4f' % _direction_deviation 74 | #print 'Non-certainty: %s (hit=%s)' % (_target_hit_certainty, _target_hit_certainty<=1) 75 | #print 'Limb accuracy: %0.4f' % (1-(_target_hit_certainty/1.0))*int(_target_hit_certainty<=1) 76 | #print 77 | #print 'dir', bullet_direction, 'dev', bullet_deviation, 'rec', recoil 78 | 79 | return _hits/float(bullets), _deviations 80 | 81 | _path = sys.argv[1] 82 | load_weapon(_path) 83 | 84 | _accuracies = [] 85 | 86 | for i in range(1, 10+1): 87 | print 'Skill: %s\t' % i 88 | 89 | for r in range(0, 5): 90 | recoil = r/5.0 91 | _accuracy, _deviations = simulate(i, recoil=recoil) 92 | _accuracies.append(_accuracy) 93 | 94 | print '\trecoil=%0.4f, accuracy=%0.4f, avg. deviation=%0.4f' % (recoil, _accuracy, sum(_deviations)/float(len(_deviations))) 95 | 96 | print _weapon['name'], 'accuracy:', sum(_accuracies)/float(len(_accuracies)) 97 | -------------------------------------------------------------------------------- /render_fast_los.pyx: -------------------------------------------------------------------------------- 1 | from globals import * 2 | 3 | import render_los 4 | import numbers 5 | import numpy 6 | import time 7 | import math 8 | 9 | import cython 10 | 11 | cpdef int clip(int number, int start, int end): 12 | return max(start, min(number, end)) 13 | 14 | @cython.locals(direction=cython.int, speed=cython.int) 15 | def velocity(direction,speed): 16 | cdef double rad = direction*(math.pi/180) 17 | velocity = numpy.multiply(numpy.array([math.cos(rad),math.sin(rad)]),speed) 18 | 19 | return [velocity[0],-velocity[1],0] 20 | 21 | @cython.locals(sight=cython.int, intensity=cython.int) 22 | def check_dirs(at, sight, source_map, los, intensity=45, already_checked={}, scan=(0, 360), quad_check=True, no_edge=False): 23 | cdef int deg, _i, _x, _y, __x, __y, _wall, _end_x, _end_y, end_point[2], start_point[3], _top_left[3] 24 | cdef int X_MAP_WINDOW_SIZE = MAP_WINDOW_SIZE[0] 25 | cdef int Y_MAP_WINDOW_SIZE = MAP_WINDOW_SIZE[1] 26 | cdef int X_MAP_SIZE = MAP_SIZE[0] 27 | cdef int Y_MAP_SIZE = MAP_SIZE[1] 28 | 29 | _check_dirs = already_checked 30 | _checked_quads = [deg/90 for deg in already_checked] 31 | start_point[0] = at[0] 32 | start_point[1] = at[1] 33 | start_point[2] = at[2] 34 | 35 | for deg in range(scan[0], scan[1], intensity): 36 | if quad_check and deg/90 in _checked_quads: 37 | continue 38 | 39 | _end_x,_end_y = velocity(deg, sight)[:2] 40 | end_point[0] = start_point[0]+int(round(_end_x)) 41 | end_point[1] = start_point[1]+int(round(_end_y)) 42 | _line = render_los.draw_line(start_point[0], start_point[1], end_point[0], end_point[1]) 43 | _i = 0 44 | _wall = 0 45 | __x = clip(start_point[0]-(los.shape[1]/2),0,X_MAP_SIZE-(los.shape[1]/2)) 46 | __y = clip(start_point[1]-(los.shape[0]/2),0,Y_MAP_SIZE-(los.shape[0]/2)) 47 | 48 | for pos in _line: 49 | _x,_y = pos 50 | _x -= __x 51 | _y -= __y 52 | _i += 1 53 | 54 | if _x<0 or _x>=los.shape[1]-1 or _y<0 or _y>=los.shape[0]-1 or pos[0]>=MAP_SIZE[0]-1 or pos[1]>=MAP_SIZE[1]-1: 55 | continue 56 | 57 | if source_map[pos[0]][pos[1]][start_point[2]+1]: 58 | _check_dirs[deg] = _line[:_i] 59 | if not _wall: 60 | _wall = 1 61 | 62 | if not no_edge: 63 | los[_y, _x] = 0 64 | 65 | continue 66 | 67 | if _wall: 68 | los[_y, _x] = 0 69 | 70 | return _check_dirs 71 | 72 | def render_fast_los(at, sight_length, source_map, no_edge=False): 73 | _stime = time.time() 74 | cdef int sight = sight_length 75 | cdef int intensity = 45 76 | cdef int quad 77 | los = numpy.ones((sight, sight)) 78 | 79 | _check_dirs = {} 80 | while 1: 81 | _check_dirs = check_dirs(at, sight, source_map, los, intensity=intensity, already_checked=_check_dirs, no_edge=no_edge) 82 | quads_to_check = [] 83 | 84 | for deg in [entry/90 for entry in _check_dirs if _check_dirs[entry]]: 85 | if not deg in quads_to_check: 86 | quads_to_check.append(deg) 87 | 88 | intensity /= 2 89 | 90 | if intensity<=6: 91 | break 92 | 93 | _cover = {'pos': None,'score':9000} 94 | for quad in quads_to_check: 95 | _scan = scan=(numbers.clip(quad*90, 0, 360), (numbers.clip((quad+1)*90, 0, 360))) 96 | _check_dirs = check_dirs(at, sight, source_map, los, intensity=1, scan=_scan, quad_check=False, no_edge=no_edge) 97 | 98 | return los 99 | -------------------------------------------------------------------------------- /data/life/dog.json: -------------------------------------------------------------------------------- 1 | { 2 | "body": { 3 | "hip": { 4 | "damage_mod": 2.0, 5 | "bleed_mod": 1.0, 6 | "flags": "SKIN[{\"thickness\": 2}]|MUSCLE|BONE|CRUCIAL", 7 | "parent": "stomach", 8 | "size": 3 9 | }, 10 | "frleg": { 11 | "damage_mod": 1.0, 12 | "bleed_mod": 0.5, 13 | "flags": "SKIN[{\"thickness\": 2}]|MUSCLE|BONE", 14 | "parent": "chest", 15 | "size": 2 16 | }, 17 | "head": { 18 | "damage_mod": 2.0, 19 | "bleed_mod": 1.5, 20 | "flags": "SKIN[{\"thickness\": 2}]|BONE|CRUCIAL|AFFECTS[sight,smell,hearing]", 21 | "parent": "jaw", 22 | "size": 4 23 | }, 24 | "stomach": { 25 | "damage_mod": 1.0, 26 | "bleed_mod": 1.5, 27 | "flags": "SKIN[{\"thickness\": 2}]|MUSCLE|BONE|CRUCIAL", 28 | "parent": "chest", 29 | "size": 4 30 | }, 31 | "neck": { 32 | "damage_mod": 2.0, 33 | "bleed_mod": 2.0, 34 | "flags": "SKIN[{\"thickness\": 2}]|MUSCLE|BONE|CRUCIAL", 35 | "parent": "head", 36 | "size": 3 37 | }, 38 | "frpaw": { 39 | "damage_mod": 1.0, 40 | "bleed_mod": 0.5, 41 | "flags": "SKIN[{\"thickness\": 2}]|MUSCLE|BONE|SHARP", 42 | "parent": "frleg", 43 | "size": 1 44 | }, 45 | "jaw": { 46 | "damage_mod": 1.0, 47 | "bleed_mod": 1.5, 48 | "flags": "SKIN[{\"thickness\": 2}]|BONE|CRUCIAL|CAN_HOLD|CAN_BITE", 49 | "size": 2 50 | }, 51 | "flpaw": { 52 | "damage_mod": 1.0, 53 | "bleed_mod": 0.5, 54 | "flags": "SKIN[{\"thickness\": 2}]|MUSCLE|BONE|SHARP", 55 | "parent": "flleg", 56 | "size": 1 57 | }, 58 | "chest": { 59 | "damage_mod": 2.0, 60 | "bleed_mod": 1.5, 61 | "flags": "SKIN[{\"thickness\": 2}]|MUSCLE|BONE|CRUCIAL", 62 | "parent": "neck", 63 | "size": 2 64 | }, 65 | "blpaw": { 66 | "damage_mod": 1.0, 67 | "bleed_mod": 0.5, 68 | "flags": "SKIN[{\"thickness\": 2}]|MUSCLE|BONE|SHARP", 69 | "parent": "blleg", 70 | "size": 1 71 | }, 72 | "flleg": { 73 | "damage_mod": 1.0, 74 | "bleed_mod": 0.5, 75 | "flags": "SKIN[{\"thickness\": 2}]|MUSCLE|BONE", 76 | "parent": "chest", 77 | "size": 2 78 | }, 79 | "blleg": { 80 | "damage_mod": 1.0, 81 | "bleed_mod": 1.3, 82 | "flags": "SKIN[{\"thickness\": 2}]|MUSCLE|BONE", 83 | "parent": "hip", 84 | "size": 2 85 | }, 86 | "brleg": { 87 | "damage_mod": 1.0, 88 | "bleed_mod": 1.3, 89 | "flags": "SKIN[{\"thickness\": 2}]|MUSCLE|BONE", 90 | "parent": "hip", 91 | "size": 2 92 | }, 93 | "brpaw": { 94 | "damage_mod": 1.0, 95 | "bleed_mod": 0.5, 96 | "flags": "SKIN[{\"thickness\": 2}]|MUSCLE|BONE|SHARP", 97 | "parent": "brleg", 98 | "size": 1 99 | } 100 | }, 101 | "name": "Wild Dog", 102 | "vars": "vision_max=35|hunger_max=20000|thirst_max=10000|hunger=hunger_max|thirst=thirst_max", 103 | "flags": "LEGS[flleg,frleg,blleg,brleg]|CAN_GROUP|CAN_SEE|HUNGER|THIRST|MELEE[jaw,flpaw,frpaw]", 104 | "type": "animal", 105 | "species": "dog", 106 | "icon": "D" 107 | } -------------------------------------------------------------------------------- /inputs.py: -------------------------------------------------------------------------------- 1 | from cStringIO import StringIO 2 | from globals import * 3 | 4 | from debug import * 5 | 6 | import libtcodpy as tcod 7 | import graphics as gfx 8 | 9 | import scripting 10 | import menus 11 | 12 | import sys 13 | 14 | def reset_input(): 15 | for key in INPUT: 16 | INPUT[key] = False 17 | 18 | def get_input(): 19 | tcod.sys_check_for_event(tcod.EVENT_ANY, KEY, MOUSE) 20 | reset_input() 21 | get_keyboard_input() 22 | get_mouse_input() 23 | 24 | def get_keyboard_input(): 25 | global KEYBOARD_STRING 26 | 27 | if not KEY.vk: 28 | return False 29 | 30 | if KEY.c: 31 | _key = chr(KEY.c) 32 | else: 33 | if KEY.pressed: 34 | if KEY.vk == tcod.KEY_RIGHT: 35 | INPUT['right'] = True 36 | elif KEY.vk == tcod.KEY_LEFT: 37 | INPUT['left'] = True 38 | elif KEY.vk == tcod.KEY_DOWN: 39 | INPUT['down'] = True 40 | elif KEY.vk == tcod.KEY_UP: 41 | INPUT['up'] = True 42 | 43 | return True 44 | 45 | if SETTINGS['draw console']: 46 | if KEY.vk == tcod.KEY_ENTER and len(KEYBOARD_STRING[0]): 47 | #Taken from: http://stackoverflow.com/a/3906309 48 | old_stdout = sys.stdout 49 | redirected_output = sys.stdout = StringIO() 50 | exec(scripting.parse_console(KEYBOARD_STRING[0].rstrip())) 51 | sys.stdout = old_stdout 52 | 53 | gfx.log('>'+KEYBOARD_STRING[0].rstrip()) 54 | gfx.log(' '+redirected_output.getvalue()) 55 | 56 | KEYBOARD_STRING[0] = '' 57 | 58 | elif KEY.vk == tcod.KEY_BACKSPACE: 59 | KEYBOARD_STRING[0] = KEYBOARD_STRING[0][:len(KEYBOARD_STRING[0])-1] 60 | 61 | if not ACTIVE_MENU['menu'] == -1: 62 | _item = menus.is_getting_input(ACTIVE_MENU['menu']) 63 | if _item and KEY.pressed: 64 | _item['values'][0] += _key 65 | 66 | if not INPUT.has_key(_key): 67 | INPUT[_key] = False 68 | 69 | if not INPUT[_key] and KEY.pressed: 70 | if SETTINGS['draw console']: 71 | KEYBOARD_STRING[0] += _key 72 | 73 | INPUT[_key] = True 74 | else: 75 | #if INPUT[_key]: 76 | # #TODO: A 'true' release...? 77 | # pass 78 | 79 | INPUT[_key] = False 80 | 81 | def set_mouse_click_callback(button, function): 82 | if button == 1: 83 | MOUSE_CALLBACKS['m1_click'] = function 84 | else: 85 | MOUSE_CALLBACKS['m2_click'] = function 86 | 87 | def set_mouse_move_callback(function): 88 | MOUSE_CALLBACKS['move'] = function 89 | 90 | def get_mouse_location(): 91 | return CAMERA_POS[0]+MOUSE_POS[0], CAMERA_POS[1]+MOUSE_POS[1] 92 | 93 | def get_mouse_input(): 94 | #TODO: I can't get mouse input to work properly... 95 | _mouse = tcod.mouse_get_status() 96 | _old_x, _old_y = MOUSE_POS 97 | 98 | MOUSE_POS[0] = _mouse.cx 99 | MOUSE_POS[1] = _mouse.cy 100 | 101 | if not [_old_x, _old_y] == MOUSE_POS: 102 | if MOUSE_CALLBACKS['move']: 103 | MOUSE_CALLBACKS['move']() 104 | 105 | if not INPUT['m1'] and _mouse.lbutton_pressed: 106 | if MOUSE_CALLBACKS['m1_click']: 107 | MOUSE_CALLBACKS['m1_click']() 108 | 109 | INPUT['m1'] = True 110 | else: 111 | INPUT['m1'] = False 112 | 113 | if not INPUT['m2'] and _mouse.rbutton_pressed: 114 | if MOUSE_CALLBACKS['m2_click']: 115 | MOUSE_CALLBACKS['m2_click']() 116 | 117 | INPUT['m2'] = True 118 | else: 119 | INPUT['m2'] = False -------------------------------------------------------------------------------- /data/life/dog.xml: -------------------------------------------------------------------------------- 1 | Wild Dog 2 | dog 3 | animal 4 | 5 | LEGS[flleg,frleg,blleg,brleg]|CAN_GROUP|CAN_SEE|HUNGER|THIRST|MELEE[jaw,flpaw,frpaw] 6 | vision_max=35|hunger_max=20000|thirst_max=10000|hunger=hunger_max|thirst=thirst_max 7 | D 8 | 9 | 10 | 11 | SKIN[{"thickness": 2}]|BONE|CRUCIAL|CAN_HOLD|CAN_BITE 12 | 1 13 | 1.5 14 | 2 15 | 16 | 17 | 18 | SKIN[{"thickness": 2}]|BONE|CRUCIAL|AFFECTS[sight,smell,hearing] 19 | jaw 20 | 2 21 | 1.5 22 | 4 23 | 24 | 25 | 26 | SKIN[{"thickness": 2}]|MUSCLE|BONE|CRUCIAL 27 | head 28 | 2 29 | 2 30 | 3 31 | 32 | 33 | 34 | SKIN[{"thickness": 2}]|MUSCLE|BONE|CRUCIAL 35 | neck 36 | 2 37 | 1.5 38 | 2 39 | 40 | 41 | 42 | SKIN[{"thickness": 2}]|MUSCLE|BONE 43 | chest 44 | 1 45 | 0.5 46 | 2 47 | 48 | 49 | 50 | SKIN[{"thickness": 2}]|MUSCLE|BONE 51 | chest 52 | 1 53 | 0.5 54 | 2 55 | 56 | 57 | 58 | SKIN[{"thickness": 2}]|MUSCLE|BONE|SHARP 59 | flleg 60 | 1 61 | 0.5 62 | 1 63 | 64 | 65 | 66 | SKIN[{"thickness": 2}]|MUSCLE|BONE|SHARP 67 | frleg 68 | 1 69 | 0.5 70 | 1 71 | 72 | 73 | 74 | SKIN[{"thickness": 2}]|MUSCLE|BONE|CRUCIAL 75 | chest 76 | 1 77 | 1.5 78 | 4 79 | 80 | 81 | 82 | SKIN[{"thickness": 2}]|MUSCLE|BONE|CRUCIAL 83 | stomach 84 | 2 85 | 1 86 | 3 87 | 88 | 89 | 90 | SKIN[{"thickness": 2}]|MUSCLE|BONE 91 | hip 92 | 1 93 | 1.3 94 | 2 95 | 96 | 97 | 98 | SKIN[{"thickness": 2}]|MUSCLE|BONE 99 | hip 100 | 1 101 | 1.3 102 | 2 103 | 104 | 105 | 106 | SKIN[{"thickness": 2}]|MUSCLE|BONE|SHARP 107 | blleg 108 | 1 109 | 0.5 110 | 1 111 | 112 | 113 | 114 | SKIN[{"thickness": 2}]|MUSCLE|BONE|SHARP 115 | brleg 116 | 1 117 | 0.5 118 | 1 119 | 120 | 121 | -------------------------------------------------------------------------------- /alife/noise.py: -------------------------------------------------------------------------------- 1 | #This isn't for playing sound - R3 uses outside libraries for that, 2 | #and this module utilizes that in places. 3 | # 4 | #This is for simulating noises (that aren't voices) 5 | 6 | from globals import * 7 | 8 | import graphics as gfx 9 | 10 | import language 11 | import judgement 12 | import bad_numbers 13 | import sight 14 | import brain 15 | 16 | import logging 17 | import random 18 | 19 | FAR_TEXT = ['You hear @t to the @d.'] 20 | 21 | def create(position, volume, close_text, far_text, skip_on_visual=True, **sound): 22 | _noise = {'pos': position, 23 | 'volume': volume, 24 | 'text': (close_text, far_text), 25 | 'skip_on_visual': skip_on_visual} 26 | _noise.update(sound) 27 | 28 | _spread(_noise) 29 | 30 | def update_targets_around_noise(life, noise): 31 | _most_likely_target = {'target': None, 'last_seen_time': 0} 32 | 33 | if 'target' in noise and not life['id'] == noise['target']: 34 | _visiblity = bad_numbers.clip(sight.get_stealth_coverage(LIFE[noise['target']]), 0.0, 1.0) 35 | _visiblity = bad_numbers.clip(_visiblity+(bad_numbers.distance(life['pos'], LIFE[noise['target']]['pos']))/(sight.get_vision(life)/2), 0, 1.0) 36 | 37 | if _visiblity >= sight.get_visiblity_of_position(life, LIFE[noise['target']]['pos']): 38 | brain.meet_alife(life, LIFE[noise['target']]) 39 | 40 | life['know'][noise['target']]['escaped'] = 1 41 | life['know'][noise['target']]['last_seen_at'] = noise['pos'][:] 42 | life['know'][noise['target']]['last_seen_time'] = 0 43 | 44 | for target in life['know'].values(): 45 | if not target['escaped'] or not target['last_seen_at'] or target['dead']: 46 | continue 47 | 48 | if bad_numbers.distance(target['last_seen_at'], noise['pos']) > noise['volume']: 49 | continue 50 | 51 | if judgement.is_target_threat(life, target['life']['id']): 52 | if not _most_likely_target['target'] or target['last_seen_time'] < _most_likely_target['last_seen_time']: 53 | _most_likely_target['last_seen_time'] = target['last_seen_time'] 54 | _most_likely_target['target'] = target 55 | 56 | if _most_likely_target['target']: 57 | _most_likely_target['target']['escaped'] = 1 58 | _most_likely_target['target']['last_seen_at'] = noise['pos'][:] 59 | _most_likely_target['target']['last_seen_time'] = 1 60 | 61 | logging.debug('%s heard a noise, attributing it to %s.' % (' '.join(life['name']), ' '.join(_most_likely_target['target']['life']['name']))) 62 | 63 | def _spread(noise): 64 | for alife in LIFE.values(): 65 | if alife['dead']: 66 | continue 67 | 68 | _can_see = False 69 | if sight.can_see_position(alife, noise['pos']): 70 | _can_see = True 71 | 72 | _dist = bad_numbers.distance(noise['pos'], alife['pos']) 73 | 74 | if _dist>noise['volume']: 75 | continue 76 | 77 | update_targets_around_noise(alife, noise) 78 | 79 | _direction_to = bad_numbers.direction_to(alife['pos'], noise['pos']) 80 | _direction_string = language.get_real_direction(_direction_to) 81 | 82 | #TODO: Check walls between positions 83 | #TODO: Add memory 84 | if not _can_see or not noise['skip_on_visual']: 85 | if _dist >=noise['volume']/2: 86 | if 'player' in alife: 87 | gfx.message(random.choice(FAR_TEXT).replace('@t', noise['text'][1]).replace('@d', _direction_string), style='sound') 88 | else: 89 | if 'player' in alife: 90 | gfx.message(random.choice(FAR_TEXT).replace('@t', noise['text'][0]).replace('@d', _direction_string), style='sound') 91 | -------------------------------------------------------------------------------- /network.py: -------------------------------------------------------------------------------- 1 | from globals import * 2 | 3 | import life as lfe 4 | 5 | import alife 6 | 7 | import threading 8 | import logging 9 | import socket 10 | import json 11 | 12 | def parse_packet(packet): 13 | try: 14 | _packet = json.loads(packet.strip()) 15 | except: 16 | logging.error('Debug: Invalid packet.') 17 | return json.dumps({'type': 'text', 'text': 'Invalid packet from this client.'}) 18 | 19 | if _packet['type'] == 'get': 20 | if _packet['what'] == 'stats': 21 | _stats = {'total_memories': sum([len(l['memory']) for l in LIFE.values()]), 22 | 'active_life': len([l for l in LIFE.values() if not l['dead']])} 23 | 24 | return json.dumps(_stats) 25 | elif _packet['what'] == 'groups': 26 | return json.dumps(WORLD_INFO['groups']) 27 | elif _packet['what'] == 'life': 28 | _life = LIFE[_packet['value']] 29 | 30 | _knows = {} 31 | for entry in _life['know'].values(): 32 | _knows[entry['life']['id']] = {} 33 | for key in entry: 34 | if key == 'heard': 35 | continue 36 | 37 | if key == 'life': 38 | _knows[entry['life']['id']][key] = entry['life']['id'] 39 | continue 40 | 41 | _knows[entry['life']['id']][key] = _life['know'][entry['life']['id']][key] 42 | 43 | if _life['job']: 44 | _job = _life['job']['gist'] 45 | else: 46 | _job = None 47 | 48 | _sent_life = {'name': _life['name'], 49 | 'id': _life['id'], 50 | 'state': _life['state'], 51 | 'flags': _life['flags'], 52 | 'job': _job, 53 | 'group': _life['group'], 54 | 'tempstor': _life['tempstor2'], 55 | 'know': _knows, 56 | 'stats': _life['stats']} 57 | 58 | return json.dumps(_sent_life) 59 | elif _packet['what'] == 'memory': 60 | _memory = [] 61 | for entry in LIFE[_packet['value']]['memory']: 62 | if 'question' in entry and entry['question']: 63 | _q = entry.copy() 64 | _memory.append(_q) 65 | continue 66 | 67 | _memory.append(entry) 68 | 69 | return json.dumps(_memory) 70 | elif _packet['what'] == 'life_list': 71 | return json.dumps(LIFE.keys()) 72 | elif _packet['what'] == 'camp_list': 73 | return json.dumps(CAMPS.keys()) 74 | elif _packet['what'] == 'camp': 75 | if not _packet['value'] in CAMPS: 76 | return json.dumps({}) 77 | 78 | _camp = CAMPS[_packet['value']] 79 | _send_camp = _camp.copy() 80 | _send_camp['groups'] = alife.camps.get_controlling_groups(_camp['id']) 81 | 82 | return json.dumps(_send_camp) 83 | 84 | 85 | class DebugHost(threading.Thread): 86 | def __init__(self): 87 | self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 88 | self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 89 | self.socket.bind((SETTINGS['debug host'], SETTINGS['debug port'])) 90 | self.socket.listen(1) 91 | self.socket.settimeout(3) 92 | 93 | threading.Thread.__init__(self) 94 | 95 | def quit(self): 96 | try: 97 | self.conn.close() 98 | except: 99 | pass 100 | 101 | self.socket.close() 102 | self.socket.shutdown(socket.SHUT_RDWR) 103 | logging.debug('Debug: Quit.') 104 | 105 | def run(self): 106 | logging.debug('DebugHost up.') 107 | 108 | while SETTINGS['running']: 109 | try: 110 | self.conn, self.addr = self.socket.accept() 111 | logging.debug('Debug: Connected.') 112 | except socket.timeout: 113 | continue 114 | 115 | data = self.conn.recv(1024) 116 | self.conn.sendall(parse_packet(data)) 117 | self.conn.close() -------------------------------------------------------------------------------- /fast_scan_surroundings.pyx: -------------------------------------------------------------------------------- 1 | from globals import WORLD_INFO 2 | from libc.stdlib cimport malloc, free 3 | 4 | import fast_dijkstra 5 | import life as lfe 6 | 7 | import drawing 8 | import numbers 9 | import alife 10 | import maps 11 | 12 | def scan_surroundings(life, initial=False, _chunks=[], ignore_chunks=[], judge=True, get_chunks=False, visible_check=True): 13 | cdef char *chunk_key = malloc((9+1)) 14 | cdef int i, _chunk_x, _chunk_y, _dist 15 | cdef CHUNK_SIZE = WORLD_INFO['chunk_size'] 16 | 17 | _center_chunk_key = lfe.get_current_chunk_id(life) 18 | 19 | if get_chunks: 20 | _center_chunk = maps.get_chunk(_center_chunk_key) 21 | 22 | _visible_chunks = set() 23 | 24 | if _chunks: 25 | _chunks = [c for c in _chunks if c in WORLD_INFO['chunk_map']] 26 | else: 27 | _temp_chunks = alife.sight._scan_surroundings(_center_chunk_key, CHUNK_SIZE, alife.sight.get_vision(life), ignore_chunks=ignore_chunks) 28 | _chunks = [] 29 | for _chunk in _temp_chunks: 30 | if _chunk in WORLD_INFO['chunk_map']: 31 | _chunks.append(_chunk) 32 | 33 | #Find chunks furthest away 34 | if visible_check: 35 | _outline_chunks = {'distance': 0, 'chunks': []} 36 | 37 | for i in range(len(_chunks)): 38 | chunk_key = _chunks[i] 39 | 40 | _current_chunk = maps.get_chunk(chunk_key) 41 | _dist = numbers.distance(life['pos'], (_current_chunk['pos'][0]+CHUNK_SIZE/2, _current_chunk['pos'][1]+CHUNK_SIZE/2)) 42 | 43 | if _dist>_outline_chunks['distance']+CHUNK_SIZE: 44 | _outline_chunks['distance'] = _dist 45 | _outline_chunks['chunks'] = [chunk_key] 46 | elif _dist>=_outline_chunks['distance']: 47 | _outline_chunks['chunks'].append(chunk_key) 48 | 49 | for outline_chunk_key in _outline_chunks['chunks']: 50 | if outline_chunk_key in _visible_chunks: 51 | continue 52 | 53 | _outline_chunk = WORLD_INFO['chunk_map'][outline_chunk_key] 54 | if _outline_chunk['max_z'] <= life['pos'][2]: 55 | _can_see = drawing.diag_line(life['pos'], (_outline_chunk['pos'][0]+CHUNK_SIZE/2, _outline_chunk['pos'][1]+CHUNK_SIZE/2)) 56 | else: 57 | _can_see = alife.chunks.can_see_chunk(life, outline_chunk_key) 58 | 59 | if _can_see: 60 | _skip = 0 61 | for pos in _can_see: 62 | #if _skip: 63 | # _skip-=1 64 | # continue 65 | 66 | #if _can_see.index(pos)_l_slope: 73 | break 74 | 75 | _sax = _d_x * xx + _d_y * xy 76 | _say = _d_x * yx + _d_y * yy 77 | 78 | if (_sax<0 and abs(_sax)>x) or (_say<0 and abs(_say)>y): 79 | _d_x += 1 80 | continue 81 | 82 | 83 | _a_x = x + _sax 84 | _a_y = y + _say 85 | 86 | if _a_x >= MAP_SIZE[0] or _a_y >= MAP_SIZE[1]: 87 | _d_x += 1 88 | continue 89 | 90 | _rad2 = size*size 91 | 92 | if (_d_x * _d_x + _d_y * _d_y) < _rad2: 93 | los_map[_a_x, _a_y] = 1 94 | 95 | _solid = WORLD_INFO['map'][_a_x][_a_y] 96 | if _blocked: 97 | if _solid: 98 | _next_start_slope = _r_slope 99 | _d_x += 1 100 | continue 101 | else: 102 | _blocked = False 103 | start_slope = _next_start_slope 104 | elif _solid: 105 | _blocked = True 106 | _next_start_slope = _r_slope 107 | light(los_map, world_pos, size, i+1, start_slope, _l_slope, xx, xy, yx, yy) 108 | 109 | _d_x += 1 110 | 111 | if _blocked: 112 | break 113 | 114 | 115 | def los(start_position, distance): 116 | _los = numpy.zeros((distance*2, distance*2)) 117 | 118 | for i in range(8): 119 | light(_los, start_position, distance, 1, 1.0, 0.0, MULT[0][i], 120 | MULT[1][i], MULT[2][i], MULT[3][i]); 121 | #walk_row(_los, (20, 20), octant=(-1, -1)) 122 | #walk_row(_los, (20, 20), octant=(1, -1)) 123 | #walk_row(_los, (20, 20), octant=(-1, 1)) 124 | #walk_row(_los, (20, 20), octant=(1, 1)) 125 | 126 | #walk_col(_los, (20, 20), octant=(-1, -1)) 127 | #walk_col(_los, (20, 20), octant=(-1, 1)) 128 | #walk_col(_los, (20, 20), octant=(1, -1)) 129 | #walk_col(_los, (20, 20), octant=(1, 1)) 130 | 131 | return _los 132 | 133 | _start_time = time.time() 134 | _los = los((20, 20), 20) 135 | print time.time()-_start_time 136 | 137 | draw(_los) -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | Reactor 3 2 | ========= 3 | 4 | Note 5 | ---- 6 | 7 | Please grab the `unstable` branch of the game when cloning. Some changes to 8 | Numpy have rendered the game unplayable due to a file conflict and I've renamed 9 | that file on that branch only. Also, **delete all instances of `numbers.pyc`**. 10 | 11 | I stopped working on this in 2014, but a successor is being privately developed 12 | using a new framework written in C. I will post on Reddit's `roguelike` or 13 | `roguelikedev` subforums eventually. 14 | 15 | Please do not use this game's code as a guide for working with Python / libtcod. 16 | I had never developed a project this size before and was learning along the way. 17 | R3 accomplished what it did via brute force and creative engineering. I don't 18 | recommend that way of working. 19 | 20 | What is Reactor 3? 21 | ------------------ 22 | Reactor 3 is best described as a mix S.T.A.L.K.E.R. and Fallout with the additon of procedurally generated elements. 23 | There is a heavy emphasis on NPC interaction, squad tactics, and survival, with each action you make shaping the Zone, 24 | an unstable area surrounding the Chernobyl Nuclear Power Plant. The Zone itself is a living entity, causing erratic 25 | weather events and transforming the local wildlife into hostile mutants. 26 | 27 | R3 is brutally difficult and unforgiving, punishing those who choose a run-'n-gun playstyle over non-combat solutions. 28 | Combat is heavily grounded in reality, modeling minor injuries like scrapes and cuts to full dismemberment. The player's 29 | inventory is also treated as it should; each item must be either held, worn, or stored away in a container (backpack, 30 | pocket, etc,) which encourages the player to not only pick and choose between what they carry, but also how they carry 31 | it (a pistol would be stored in a holster for quicker access, for example.) 32 | 33 | Join a faction and take over the Zone, or simply exist on your own. 34 | 35 | Installing 36 | ========== 37 | Reactor 3 requires Python 2.7, Cython, Numpy, and [libtcod](http://doryen.eptalys.net/libtcod/download/). 38 | 39 | git clone https://github.com/flags/Reactor-3.git 40 | cd Reactor-3 41 | python compile_cython_modules.py build_ext --inplace 42 | 43 | Next, download the libtcod library and move the `.so` (Windows: `.dll`) files from the archive to the Reactor 3 directory. 44 | 45 | Run `python reactor-3.py` to play. 46 | 47 | See the section `flags` below for more info. 48 | 49 | Controls 50 | ======== 51 | * `Arrow keys` - Move (4-way movement)/Navigate menus 52 | * `Numpad` - Moe (8-way movement) 53 | * `Enter` - Select 54 | * `e` - Equip/hold item 55 | * `E (Shift-e)` - Unequip item 56 | * `,` - Pick up item 57 | * `d` - Drop item 58 | * `r` - Reload / fill mag or clip 59 | * `f` - Enter targeting mode (shoot) 60 | * `F (Shift-f)` - Set fire mode 61 | * `v` - Enter targeting mode (talk) 62 | * `V (Shift-v)` - Use radio 63 | * `Tab` - Group communication 64 | * `k` - Open crafting menu 65 | * `w` - Open medical menu 66 | * `W (Shift-w)` - Open medical menu (heal someone) 67 | * `C (Shift-c)` - Stand up 68 | * `c` - Crouch 69 | * `Z (Shift-z)` - Prone 70 | * `P (Shift-P)` - Pause 71 | * `l` - Look 72 | * `O (Shift-o)` - Options 73 | * `-` - Debug console 74 | * `?` - Record mode (**This will dump potentially hundreds of .BMP files in the game's directory**) 75 | 76 | Flags 77 | ----- 78 | `reactor-3.py` can be run with a few arguments: 79 | 80 | * `--quick` - Load latest game. 81 | * `--profile` - Dumps a profile to `profile.dat`. `tools/show_profile.py` can be used to view the profile (use the argument `highest` to show the most time consuming functions) 82 | 83 | Credits 84 | ------- 85 | Reactor 3 is made possible by the `libtcod` library. All other work was done by flags, a member of the ASCII Worlds Collective. 86 | -------------------------------------------------------------------------------- /cache.py: -------------------------------------------------------------------------------- 1 | from globals import * 2 | 3 | import profiles 4 | import items 5 | 6 | import logging 7 | import json 8 | 9 | import os 10 | 11 | def _capsule(): 12 | return {'uid': None, 13 | 'date': WORLD_INFO['ticks'], 14 | '_on_disk': False, 15 | '_allow_dump': False} 16 | 17 | def _write_to_cache(cache_name, data): 18 | with open(os.path.join(profiles.get_world_directory(WORLD_INFO['id']), '%s_history.dat' % cache_name), 'a') as f: 19 | f.write(json.dumps(data)+'\n\n') 20 | 21 | def _read_from_cache(cache_name, uid): 22 | with open(os.path.join(profiles.get_world_directory(WORLD_INFO['id']), '%s_history.dat' % cache_name)) as f: 23 | for item in [json.loads(s) for s in f]: 24 | if item['uid'] == uid: 25 | item['_on_disk'] = False 26 | item['date'] = WORLD_INFO['ticks'] 27 | ITEMS_HISTORY[item['uid']].update(item) 28 | 29 | logging.debug('Cache: Loaded item %s from cache.' % uid) 30 | return item 31 | 32 | def save_cache(cache_name): 33 | _path = os.path.join(profiles.get_world_directory(WORLD_INFO['id']), '%s_history.dat' % cache_name) 34 | _write_cache = [] 35 | 36 | if not os.path.exists(_path): 37 | return False 38 | 39 | with open(_path, 'r') as f: 40 | _cache = f.readlines() 41 | 42 | for _line in _cache: 43 | line = _line.rstrip() 44 | 45 | if line == '\n' or not line: 46 | continue 47 | 48 | _historic_item = json.loads(line) 49 | 50 | if not _historic_item['_allow_dump']: 51 | continue 52 | 53 | _dump_string = json.dumps(_historic_item) 54 | 55 | _write_cache.append(_dump_string) 56 | 57 | with open(_path, 'w') as f: 58 | f.write('\n'.join(_write_cache)) 59 | 60 | logging.debug('Cache: Saved to disk.') 61 | 62 | def commit_cache(cache_name): 63 | _path = os.path.join(profiles.get_world_directory(WORLD_INFO['id']), '%s_history.dat' % cache_name) 64 | _write_cache = [] 65 | 66 | if not os.path.exists(_path): 67 | return False 68 | 69 | with open(_path, 'r') as f: 70 | _cache = f.readlines() 71 | 72 | for _line in _cache: 73 | line = _line.rstrip() 74 | 75 | if line == '\n' or not line: 76 | continue 77 | 78 | print repr(line) 79 | _historic_item = json.loads(line) 80 | _historic_item['_allow_dump'] = True 81 | _write_cache.append(json.dumps(_historic_item)) 82 | 83 | if cache_name == 'items': 84 | for item_uid in ITEMS_HISTORY: 85 | _historic_item = ITEMS_HISTORY[item_uid] 86 | if not _historic_item['_on_disk']: 87 | continue 88 | 89 | _historic_item['_allow_dump'] = True 90 | 91 | with open(_path, 'w') as f: 92 | f.write('\n'.join(_write_cache)) 93 | 94 | logging.debug('Cache: Committed.') 95 | 96 | def scan_cache(): 97 | return False 98 | 99 | for item_uid in ITEMS_HISTORY: 100 | _historic_item = ITEMS_HISTORY[item_uid] 101 | 102 | if _historic_item['_on_disk']: 103 | continue 104 | 105 | if WORLD_INFO['ticks']-_historic_item['date']>=100: 106 | logging.debug('Cache: Moved item %s to disk' % item_uid) 107 | _historic_item['_on_disk'] = True 108 | _write_to_cache('items', _historic_item) 109 | 110 | if 'item' in _historic_item: 111 | del _historic_item['item'] 112 | 113 | def offload_item(raw_item): 114 | _item = _capsule() 115 | items.clean_item_for_save(raw_item) 116 | _item['uid'] = raw_item['uid'] 117 | _item['item'] = raw_item 118 | 119 | ITEMS_HISTORY[_item['uid']] = _item 120 | 121 | logging.debug('Cache: Offloaded item (in memory)') 122 | 123 | def retrieve_item(item_uid): 124 | _historic_item = ITEMS_HISTORY[item_uid] 125 | 126 | if _historic_item['_on_disk']: 127 | raise Exception('Ahhhhhhhhhhhh') 128 | #TODO: Grab from disk 129 | pass 130 | 131 | return _historic_item['item'] -------------------------------------------------------------------------------- /overwatch/events.py: -------------------------------------------------------------------------------- 1 | from globals import WORLD_INFO, SETTINGS, LIFE, ITEMS 2 | 3 | import graphics as gfx 4 | import libtcodpy as tcod 5 | 6 | import artifacts 7 | import language 8 | import bad_numbers 9 | import drawing 10 | import effects 11 | import spawns 12 | import items 13 | import alife 14 | import maps 15 | import core 16 | 17 | import logging 18 | import random 19 | 20 | 21 | def create_heli_crash(pos, spawn_list): 22 | _size = random.randint(4, 6) 23 | 24 | effects.create_explosion(pos, _size) 25 | 26 | for n_pos in drawing.draw_circle(pos, _size*2): 27 | if random.randint(0, 10): 28 | continue 29 | 30 | _n_pos = list(n_pos) 31 | _n_pos.append(2) 32 | 33 | effects.create_fire(_n_pos, intensity=8) 34 | 35 | def create_cache_drop(pos, spawn_list): 36 | _player = LIFE[SETTINGS['controlling']] 37 | _pos = spawns.get_spawn_point_around(pos, area=10) 38 | _direction = language.get_real_direction(bad_numbers.direction_to(_player['pos'], _pos)) 39 | 40 | for container in spawn_list: 41 | if not container['rarity']>random.uniform(0, 1.0): 42 | continue 43 | 44 | _c = items.create_item(container['item'], position=[_pos[0], _pos[1], 2]) 45 | 46 | for _inside_item in container['spawn_list']: 47 | if _inside_item['rarity']<=random.uniform(0, 1.0): 48 | continue 49 | 50 | _i = items.create_item(_inside_item['item'], position=[_pos[0], _pos[1], 2]) 51 | 52 | if not items.can_store_item_in(_i, _c): 53 | items.delete_item(_i) 54 | 55 | continue 56 | 57 | items.store_item_in(_i, _c) 58 | 59 | effects.create_smoker(_pos, 300, color=tcod.orange) 60 | 61 | gfx.message('You see something parachuting to the ground to the %s.' % _direction, style='event') 62 | 63 | def create_anomaly_field(situation, y_min=0): 64 | return artifacts.create_field(y_min=y_min) 65 | 66 | def spawn_life(life_type, position, event_time, **kwargs): 67 | _life = {'type': life_type, 'position': position[:]} 68 | _life.update(**kwargs) 69 | 70 | WORLD_INFO['scheme'].append({'life': _life, 'time': WORLD_INFO['ticks']+event_time}) 71 | 72 | def order_group(life, group_id, stage, event_time, **kwargs): 73 | WORLD_INFO['scheme'].append({'group': group_id, 74 | 'member': life['id'], 75 | 'stage': stage, 76 | 'flags': kwargs, 77 | 'time': WORLD_INFO['ticks']+event_time}) 78 | 79 | def broadcast(messages, event_time, glitch=False): 80 | _time = WORLD_INFO['ticks']+event_time 81 | _i = 0 82 | 83 | for entry in messages: 84 | if 'source' in entry: 85 | _source = entry['source'] 86 | else: 87 | _source = '???' 88 | 89 | 90 | if glitch: 91 | if 'change_only' in entry: 92 | _change = entry['change_only'] 93 | else: 94 | _change = False 95 | 96 | _delay = (50*bad_numbers.clip(_i, 0, 1))+(len(entry['text'])*2)*_i 97 | 98 | WORLD_INFO['scheme'].append({'glitch': entry['text'], 'change': _change, 'time': _time+_delay}) 99 | else: 100 | WORLD_INFO['scheme'].append({'radio': [_source, entry['text']], 'time': _time}) 101 | 102 | _time += int(round(len(entry['text'])*1.25)) 103 | _i += 1 104 | 105 | def sound(near_text, far_text, position, volume, time): 106 | WORLD_INFO['scheme'].append({'sound': (near_text, far_text), 'pos': position, 'time': WORLD_INFO['ticks']+time, 'volume': volume}) 107 | 108 | def attract_tracked_alife_to(pos): 109 | _chunk_key = alife.chunks.get_chunk_key_at(pos) 110 | 111 | for ai in [LIFE[i] for i in WORLD_INFO['overwatch']['tracked_alife']]: 112 | alife.movement.set_focus_point(ai, _chunk_key) 113 | 114 | logging.debug('[Overwatch]: Attracting %s to %s.' % (' '.join(ai['name']), _chunk_key)) 115 | 116 | 117 | FUNCTION_MAP = {'heli_crash': create_heli_crash, 118 | 'cache_drop': create_cache_drop} -------------------------------------------------------------------------------- /alife/memory.py: -------------------------------------------------------------------------------- 1 | from globals import * 2 | 3 | import life as lfe 4 | 5 | import judgement 6 | import rawparse 7 | import action 8 | import speech 9 | import brain 10 | import jobs 11 | 12 | import logging 13 | 14 | def create_question(life, life_id, gist, ignore_if_said_in_last=0, recent_time=0, **kwargs): 15 | _target = brain.knows_alife_by_id(life, life_id) 16 | _question = {'gist': gist, 'args': kwargs} 17 | 18 | if not _target: 19 | logging.critical('%s does not know %s but is creating questions for them.' % (' '.join(life['name']), ' '.join(LIFE[life_id]['name']))) 20 | return False 21 | 22 | if _target['time_visible'] < recent_time: 23 | return False 24 | 25 | if _question in _target['questions']: 26 | return False 27 | 28 | _sent = speech.has_sent(life, life_id, gist) 29 | 30 | if _sent and (WORLD_INFO['ticks']-_sent', p['next_stance']['stance'] 88 | 89 | p['stance'] = p['next_stance']['stance'] 90 | p['next_stance']['stance'] = None 91 | 92 | return True 93 | 94 | def perform_moves(people): 95 | for life in people: 96 | if not life['stance'] in COMBAT_MOVES: 97 | continue 98 | 99 | if life['next_stance']['towards']: 100 | _target = life['next_stance']['towards'] 101 | 102 | if life['stance'] in COMBAT_MOVES and _target['stance'] in COMBAT_MOVES[life['stance']]['counters']: 103 | print '%s counters %s\'s %s!' % (_target['name'], life['name'], life['stance']) 104 | 105 | force_stance(life, 'off-balance') 106 | else: 107 | if _target['stance'] in 'off-balance': 108 | force_stance(_target, 'prone') 109 | 110 | print '%s\'s %s hits %s!' % (life['name'], life['stance'], _target['name']) 111 | 112 | life['next_stance']['towards'] = None 113 | else: 114 | print '%s\'s %s does nothing!' % (life['name'], life['stance']) 115 | 116 | #TODO: React... 117 | #life['stance'] = 'stand' 118 | 119 | assume_stance(p1, 'punch', towards=p2) 120 | 121 | t = 7 122 | while t: 123 | examine_possible_moves(p1, LIFE) 124 | examine_possible_moves(p2, LIFE) 125 | 126 | tick(p1) 127 | tick(p2) 128 | 129 | perform_moves([p1, p2]) 130 | t -= 1 -------------------------------------------------------------------------------- /prefabs.py: -------------------------------------------------------------------------------- 1 | #Tools for generating buildings (prefabs) 2 | from globals import DATA_DIR 3 | from tiles import * 4 | 5 | import graphics as gfx 6 | 7 | import menus 8 | 9 | import logging 10 | import random 11 | import json 12 | import os 13 | 14 | def create_new_prefab(size): 15 | if not len(size) == 3: 16 | raise Exception('Invalid prefab size: Expected 3 arguments.') 17 | 18 | _prefab = [] 19 | 20 | for x in range(size[0]): 21 | _y = [] 22 | for y in range(size[1]): 23 | _z = [] 24 | for z in range(size[2]): 25 | #if z==0: 26 | # _z.append(create_tile(TALL_GRASS_TILE)) 27 | #else: 28 | _z.append(None) 29 | 30 | _y.append(_z) 31 | _prefab.append(_y) 32 | 33 | logging.debug('Created new prefab of size (%s,%s,%s).' % (size[0],size[1],size[2])) 34 | 35 | return {'map': _prefab,'size': size} 36 | 37 | def cache_prefab(name, path): 38 | with open(path, 'r') as f: 39 | _prefab = json.loads(''.join(f.readlines())) 40 | _prefab['name'] = name 41 | 42 | PREFABS[name] = _prefab 43 | logging.debug('Prefab cached: %s' % name) 44 | 45 | def cache_all_prefabs(): 46 | logging.debug('Caching all prefabs...') 47 | for (dirpath, dirname, filenames) in os.walk(PREFAB_DIR): 48 | for f in [f for f in filenames if f.count('.json')]: 49 | cache_prefab(f.partition('.')[0], os.path.join(PREFAB_DIR, f)) 50 | 51 | def save(prefab): 52 | with open(os.path.join(PREFAB_DIR, 'test.json'), 'w') as f: 53 | f.write(json.dumps(prefab)) 54 | 55 | def _draw_prefab(prefab): 56 | _X_MAX = PREFAB_CAMERA_POS[0]+PREFAB_WINDOW_SIZE[0] 57 | _Y_MAX = PREFAB_CAMERA_POS[1]+PREFAB_WINDOW_SIZE[1] 58 | 59 | #DARK_BUFFER[0] = numpy.zeros((MAP_WINDOW_SIZE[1], PREFAB_WINDOW_SIZE[0])) 60 | #LIGHT_BUFFER[0] = numpy.zeros((MAP_WINDOW_SIZE[1], MAP_WINDOW_SIZE[0])) 61 | 62 | map = prefab['map'] 63 | 64 | if _X_MAX>prefab['size'][0]: 65 | _X_MAX = prefab['size'][0] 66 | 67 | if _Y_MAX>prefab['size'][1]: 68 | _Y_MAX = prefab['size'][1] 69 | 70 | for x in range(PREFAB_CAMERA_POS[0],_X_MAX): 71 | _RENDER_X = x-PREFAB_CAMERA_POS[0] 72 | 73 | for y in range(PREFAB_CAMERA_POS[1],_Y_MAX): 74 | _RENDER_Y = y-PREFAB_CAMERA_POS[1] 75 | _drawn = False 76 | 77 | for z in range(prefab['size'][2]-1,-1,-1): 78 | if map[x][y][z]: 79 | gfx.blit_tile(_RENDER_X, 80 | _RENDER_Y, 81 | map[x][y][z], 82 | char_buffer=PREFAB_CHAR_BUFFER, 83 | rgb_fore_buffer=PREFAB_RGB_FORE_BUFFER, 84 | rgb_back_buffer=PREFAB_RGB_BACK_BUFFER) 85 | 86 | _drawn = True 87 | break 88 | #if z > PREFAB_CAMERA_POS[2] and SETTINGS['draw z-levels above']: 89 | # gfx.blit_tile(_RENDER_X,_RENDER_Y,map[x][y][z]) 90 | # gfx.darken_tile(_RENDER_X,_RENDER_Y,abs((PREFAB_CAMERA_POS[2]-z))*30) 91 | # _drawn = True 92 | #elif z == PREFAB_CAMERA_POS[2]: 93 | # if (x,y,z) in SELECTED_TILES[0] and time.time()%1>=0.5: 94 | # gfx.blit_char(_RENDER_X,_RENDER_Y,'X',darker_grey,black) 95 | # else: 96 | # gfx.blit_tile(_RENDER_X,_RENDER_Y,map[x][y][z]) 97 | # _drawn = True 98 | #elif z < PREFAB_CAMERA_POS[2] and SETTINGS['draw z-levels below']: 99 | # gfx.blit_tile(_RENDER_X,_RENDER_Y,map[x][y][z]) 100 | # gfx.darken_tile(_RENDER_X,_RENDER_Y,abs((PREFAB_CAMERA_POS[2]-z))*30) 101 | # _drawn = True 102 | 103 | #if SETTINGS['draw z-levels above'] and _drawn: 104 | # break 105 | 106 | if not _drawn: 107 | gfx.blit_tile(_RENDER_X, 108 | _RENDER_Y, 109 | BLANK_TILE, 110 | char_buffer=PREFAB_CHAR_BUFFER, 111 | rgb_fore_buffer=PREFAB_RGB_FORE_BUFFER, 112 | rgb_back_buffer=PREFAB_RGB_BACK_BUFFER) 113 | 114 | def draw_prefab_thumbnail(entry): 115 | #VIEWS 116 | key = entry['key'] 117 | value = entry['values'][entry['value']] 118 | _prefab = PREFABS[key] 119 | 120 | PREFAB_TOP_VIEW_POS = (15, 1) 121 | 122 | for y in range(0, _prefab['size'][1]): 123 | for x in range(0, _prefab['size'][0]): 124 | for z in range(0, _prefab['size'][2]): 125 | if not _prefab['map'][x][y][z]: 126 | continue 127 | 128 | print _prefab['map'][x][y][z] 129 | #tcod.console_put_char_(0, PREFAB_TOP_VIEW_POS[0]+x, PREFAB_TOP_VIEW_POS[1]+y, 130 | 131 | def prefab_selected(entry): 132 | pass 133 | 134 | def create_prefab_list(): 135 | tcod.console_clear(0) 136 | tcod.console_clear(MAP_WINDOW) 137 | _prefabs = [] 138 | for prefab in PREFABS.values(): 139 | _prefabs.append(menus.create_item('single', prefab['name'], None)) 140 | 141 | return menus.create_menu(title='Prefabs', 142 | menu=_prefabs, 143 | padding=(0, 0), 144 | position=(0, 0), 145 | format_str='$k', 146 | on_select=prefab_selected, 147 | on_move=draw_prefab_thumbnail) -------------------------------------------------------------------------------- /tests/M01_map_editor.py: -------------------------------------------------------------------------------- 1 | from libtcodpy import * 2 | 3 | console_init_root(100, 50, 'M01 - Visualization',renderer=RENDERER_OPENGL) 4 | console_set_custom_font('terminal12x12_gs_ro.png',FONT_LAYOUT_ASCII_INCOL) 5 | sys_set_fps(30) 6 | 7 | SHORT_GRASS_TILE = {'id':'short_grass', 8 | 'icon':'.', 9 | 'color':(light_green,Color(0,230,0)), 10 | 'burnable':True, 11 | 'cost':1} 12 | 13 | GRASS_TILE = {'id':'grass', 14 | 'icon':',', 15 | 'color':(green,Color(0,210,0)), 16 | 'burnable':True, 17 | 'cost':1} 18 | 19 | TALL_GRASS_TILE = {'id':'tall_grass', 20 | 'icon':';', 21 | 'color':(dark_green,Color(0,205,0)), 22 | 'burnable':True, 23 | 'cost':1} 24 | 25 | DIRT_TILE = {'id':'dirt', 26 | 'icon':'.', 27 | 'color':(gray,gray), 28 | 'burnable':False, 29 | 'cost':2} 30 | 31 | WALL_TILE = {'id':'wall', 32 | 'icon':'#', 33 | 'color':(black,gray), 34 | 'burnable':False, 35 | 'cost':-1} 36 | 37 | TILES = [SHORT_GRASS_TILE,GRASS_TILE,TALL_GRASS_TILE,DIRT_TILE,WALL_TILE] 38 | MAP = [] 39 | MAP_SIZE = (100,50,5) 40 | CAMERA_Z_LEVEL = 1 41 | KEY = Key() 42 | MOUSE_POS = (0,0) 43 | MOUSE_1_DOWN = False 44 | MOUSE = Mouse() 45 | CURSOR_POS = [0,0] 46 | mouse_move(0,0) 47 | 48 | def create_tile(tile): 49 | _ret_tile = {} 50 | _ret_tile['id'] = tile['id'] 51 | 52 | _ret_tile['items'] = [] 53 | _ret_tile['fire'] = 0 54 | 55 | return _ret_tile 56 | 57 | def get_raw_tile(tile): 58 | for _tile in TILES: 59 | if _tile['id'] == tile['id']: 60 | return _tile 61 | 62 | raise Exception 63 | 64 | import random 65 | 66 | for x in range(MAP_SIZE[0]): 67 | _y = [] 68 | for y in range(MAP_SIZE[1]): 69 | _z = [] 70 | for z in range(MAP_SIZE[2]): 71 | if z == 0: 72 | _z.append(create_tile(DIRT_TILE)) 73 | elif z == 1: 74 | _z.append(create_tile(random.choice([GRASS_TILE,SHORT_GRASS_TILE,TALL_GRASS_TILE]))) 75 | else: 76 | _z.append(None) 77 | 78 | _y.append(_z) 79 | MAP.append(_y) 80 | 81 | MAP[3][3][2]=create_tile(GRASS_TILE) 82 | 83 | def get_mouse_input(): 84 | global MOUSE,MOUSE_POS,MOUSE_1_DOWN 85 | 86 | if MOUSE.lbutton: 87 | if not MOUSE_1_DOWN: 88 | MOUSE_1_DOWN = True 89 | else: 90 | MOUSE_1_DOWN = False 91 | 92 | MOUSE_POS = MOUSE.x/16,MOUSE.y/16 93 | 94 | def get_input(): 95 | global KEY,MOUSE,CURSOR_POS 96 | 97 | sys_check_for_event(EVENT_KEY_PRESS|EVENT_MOUSE,KEY,MOUSE) 98 | 99 | if KEY.vk == KEY_UP: 100 | if CURSOR_POS[1]>0: CURSOR_POS[1]-=1 101 | elif KEY.vk == KEY_DOWN: 102 | if CURSOR_POS[1]0: CURSOR_POS[0]-=1 106 | elif KEY.vk == KEY_RIGHT: 107 | if CURSOR_POS[0]2: 16 | size = 2 17 | 18 | cdef float circle = 0 19 | cdef int width=size 20 | cdef int height=size 21 | cdef float center_x=(width/2.0) 22 | cdef float center_y=(height/2.0) 23 | cdef int i,j 24 | 25 | _circle = [] 26 | 27 | for i in range(height+1): 28 | for j in range(width+1): 29 | circle = (((i-center_y)*(i-center_y))/((float(height)/2)*(float(height)/2)))+((((j-center_x)*(j-center_x))/((float(width)/2)*(float(width)/2)))); 30 | if circle>0 and circle<1.1: 31 | _circle.append((x+(j-(width/2)),y+(i-(height/2)))) 32 | 33 | if not (x, y) in _circle: 34 | _circle.append((x, y)) 35 | 36 | return _circle 37 | 38 | def swap(n1,n2): 39 | return [n2,n1] 40 | 41 | @cython.locals(x1=cython.int, y1=cython.int, x2=cython.int, y2=cython.int) 42 | def draw_line(x1,y1,x2,y2): 43 | path = [] 44 | if (x1, y1) == (x2, y2): 45 | return [(x2, y2)] 46 | 47 | start = [x1,y1] 48 | end = [x2,y2] 49 | 50 | steep = abs(end[1]-start[1]) > abs(end[0]-start[0]) 51 | 52 | if steep: 53 | start = swap(start[0],start[1]) 54 | end = swap(end[0],end[1]) 55 | 56 | if start[0] > end[0]: 57 | start[0],end[0] = swap(start[0],end[0]) 58 | start[1],end[1] = swap(start[1],end[1]) 59 | 60 | dx = end[0] - start[0] 61 | dy = abs(end[1] - start[1]) 62 | error = 0 63 | 64 | try: 65 | derr = dy/float(dx) 66 | except: 67 | return None 68 | 69 | ystep = 0 70 | y = start[1] 71 | 72 | if start[1] < end[1]: ystep = 1 73 | else: ystep = -1 74 | 75 | for x in range(start[0],end[0]+1): 76 | if steep: 77 | path.append((y,x)) 78 | else: 79 | path.append((x,y)) 80 | 81 | error += derr 82 | 83 | if error >= 0.5: 84 | y += ystep 85 | error -= 1.0 86 | 87 | if not path[0] == (x1,y1): 88 | path.reverse() 89 | 90 | return path 91 | 92 | def render_los(position, size, view_size=MAP_WINDOW_SIZE, top_left=CAMERA_POS, no_edge=False, visible_chunks=None, life=None): 93 | los_buffer = numpy.zeros((view_size[1], view_size[0])) 94 | 95 | cdef int _dark = 0 96 | cdef int _x,_y 97 | cdef int X_CAMERA_POS = top_left[0] 98 | cdef int Y_CAMERA_POS = top_left[1] 99 | cdef int Z_CAMERA_POS = top_left[2] 100 | cdef int X_MAP_SIZE = MAP_SIZE[0] 101 | cdef int Y_MAP_SIZE = MAP_SIZE[1] 102 | cdef int X_MAP_WINDOW_SIZE = view_size[0] 103 | cdef int Y_MAP_WINDOW_SIZE = view_size[1] 104 | cdef int POS_X, POS_Y 105 | POS_X = position[0] 106 | POS_Y = position[1] 107 | 108 | #SKIP_CHUNKS = [] 109 | VISIBLE_CHUNKS = [] 110 | if life and alife.brain.get_flag(life, 'visible_chunks'): 111 | VISIBLE_CHUNKS = alife.brain.get_flag(life, 'visible_chunks') 112 | 113 | #for chunk_key in alife.brain.get_flag(life, 'visible_chunks'): 114 | # if maps.get_chunk(chunk_key)['max_z'] > life['pos'][2]: 115 | # SKIP_CHUNKS.append(chunk_key) 116 | 117 | #if not POS_X-X_CAMERA_POS<0 and not POS_X-X_CAMERA_POS >= X_MAP_WINDOW_SIZE and not POS_Y-Y_CAMERA_POS<0 and not POS_Y-Y_CAMERA_POS >= Y_MAP_WINDOW_SIZE: 118 | # los_buffer[POS_Y-Y_CAMERA_POS,POS_X-X_CAMERA_POS] = 1 119 | 120 | for _pos in draw_circle(POS_X, POS_Y, size): 121 | _dark = 0 122 | 123 | _chunk_key = '%s,%s' % ((_pos[0]/WORLD_INFO['chunk_size'])*WORLD_INFO['chunk_size'], 124 | (_pos[1]/WORLD_INFO['chunk_size'])*WORLD_INFO['chunk_size']) 125 | 126 | #if _chunk_key in HIDDEN_CHUNKS: 127 | # continue 128 | 129 | for pos in draw_line(POS_X,POS_Y,_pos[0],_pos[1]): 130 | _x = pos[0]-X_CAMERA_POS 131 | _y = pos[1]-Y_CAMERA_POS 132 | #_distance = 1#numbers.clip(numbers.distance(pos, (POS_X, POS_Y), old=True), 1, 255)*.10 133 | 134 | if _x<0 or _x>=X_MAP_WINDOW_SIZE or _y<0 or _y>=Y_MAP_WINDOW_SIZE: 135 | continue 136 | 137 | if pos[0]<0 or pos[0]>=X_MAP_SIZE or pos[0]<0 or pos[1]>=Y_MAP_SIZE: 138 | continue 139 | 140 | if maps.is_solid((pos[0], pos[1], Z_CAMERA_POS+1)): 141 | if not _dark: 142 | if not no_edge: 143 | los_buffer[_y,_x] = 1 144 | 145 | break 146 | else: 147 | _chunk = alife.chunks.get_chunk(alife.chunks.get_chunk_key_at((pos[0], pos[1], Z_CAMERA_POS))) 148 | _break = False 149 | 150 | for item_uid in _chunk['items'][:]: 151 | if not item_uid in ITEMS: 152 | _chunk['items'].remove(item_uid) 153 | 154 | for item in [ITEMS[uid] for uid in _chunk['items'] if items.is_blocking(uid)]: 155 | if tuple(item['pos']) == (pos[0], pos[1], Z_CAMERA_POS): 156 | if not _dark: 157 | if not no_edge: 158 | los_buffer[_y,_x] = 1 159 | 160 | _break = True 161 | break 162 | 163 | if _break: 164 | break 165 | 166 | los_buffer[_y,_x] = 1 167 | 168 | return los_buffer 169 | -------------------------------------------------------------------------------- /data/life/human.goap: -------------------------------------------------------------------------------- 1 | #Flags: 2 | # - Check for desire, but don't try to meet it 3 | # * If it passes, great! If not, it's OK 4 | # ! Invert 5 | # @ World call (life entity not passed) 6 | 7 | #Goals 8 | 9 | #[GOAL_DISCOVER] 10 | #DESIRE: HAS_NON_RELAXED_GOAL 11 | #TIER: RELAXED 12 | 13 | [GOAL_FOLLOW] 14 | DESIRE: !HAS_TARGET_TO_FOLLOW 15 | TIER: SURVIVAL 16 | 17 | [GOAL_WATCH] 18 | DESIRE: !HAS_TARGET_TO_GUARD 19 | TIRE: URGENT 20 | 21 | #[GOAL_LOOT] 22 | #DESIRE: !HAS_NEEDS 23 | #TIER: SURVIVAL 24 | #SET_FLAGS: {"wanted_items": GET_NEEDED_ITEMS} 25 | 26 | [GOAL_GUARD] 27 | DESIRE: !HAS_FOCUS_POINT 28 | REQUIRE: !HAS_THREATS 29 | TIER: SURVIVAL 30 | 31 | #[GOAL_CAMP] 32 | #DESIRE: -*IS_IN_SHELTER,-!%IS_NIGHT,-HAS_SHELTER 33 | #REQUIRE: %IS_NIGHT 34 | #TIER: URGENT 35 | 36 | [GOAL_ATTACK] 37 | DESIRE: !HAS_THREATS 38 | REQUIRE: IS_CONFIDENT,HAS_USABLE_WEAPON,HAS_VISIBLE_THREAT,!HAS_HIGH_RECOIL 39 | TIER: TACTIC 40 | 41 | [GOAL_ATTACK_PRESSURE] 42 | DESIRE: !HAS_THREATS 43 | REQUIRE: !IS_CONFIDENT,HAS_USABLE_WEAPON,HAS_VISIBLE_THREAT,!HAS_HIGH_RECOIL,DANGER_CLOSE 44 | TIER: TACTIC 45 | 46 | [GOAL_TAKE_POINT] 47 | DESIRE: !HAS_FOCUS_POINT 48 | REQUIRE: !IS_CONFIDENT,IS_RAIDING 49 | TIER: URGENT 50 | 51 | #[GOAL_GET_SHELTER] 52 | #DESIRE: IS_IN_SHELTER,-!%IS_NIGHT 53 | #TIER: URGENT 54 | 55 | [GOAL_MANAGE_INVENTORY] 56 | DESIRE: !NEEDS_TO_MANAGE_INVENTORY 57 | TIER: COMBAT 58 | 59 | [GOAL_SEARCH_FOR_TARGET] 60 | DESIRE: !HAS_LOST_THREAT 61 | REQUIRE: !HAS_THREATS,HAS_LOST_THREAT,WEAPON_EQUIPPED_AND_READY,IS_CONFIDENT 62 | TIER: COMBAT 63 | 64 | [GOAL_COVER] 65 | DESIRE: !HAS_VISIBLE_THREAT 66 | REQUIRE: !IS_CONFIDENT 67 | TIER: URGENT 68 | 69 | [GOAL_HIDE_FROM_TARGET] 70 | DESIRE: !HAS_LOST_THREAT 71 | REQUIRE: !IS_CONFIDENT,!HAS_VISIBLE_THREAT 72 | TIER: URGENT 73 | 74 | #[GOAL_HIDE] 75 | #DESIRE: IS_IN_SHELTER 76 | #REQUIRE: !IS_CONFIDENT,HAS_VISIBLE_THREAT 77 | #TIER: URGENT 78 | 79 | [GOAL_COVER_FOR_REARM] 80 | DESIRE: !HAS_VISIBLE_THREAT 81 | REQUIRE: HAS_POTENTIALLY_USABLE_WEAPON,!WEAPON_EQUIPPED_AND_READY 82 | TIER: TACTIC 83 | 84 | #[GOAL_PREPARE] 85 | #DESIRE: !NEEDS_TO_MANAGE_INVENTORY 86 | #REQUIRE: !HAS_VISIBLE_THREAT,HAS_LOST_THREAT,HAS_POTENTIALLY_USABLE_WEAPON 87 | #TIER: TACTIC 88 | 89 | #[GOAL_RETREAT] 90 | #DESIRE: !HAS_VISIBLE_THREAT 91 | #REQUIRE: !IS_CONFIDENT,!WEAPON_EQUIPPED_AND_READY 92 | #TIER: TACTIC 93 | 94 | #[GOAL_EVADE] 95 | #DESIRE: -!HAS_LOST_THREAT,!HAS_NEEDS 96 | #TIER: TACTIC 97 | 98 | #[GOAL_TAKE_COVER] 99 | #DESIRE: !DANGER_CLOSE,-HAS_USABLE_WEAPON 100 | #TIER: TACTIC 101 | 102 | [GOAL_REPOSITION] 103 | DESIRE: !COVER_EXPOSED 104 | TIER: TACTIC 105 | 106 | [GOAL_MISSION] 107 | DESIRE: !HAS_MISSION 108 | TIER: URGENT 109 | 110 | #Actions 111 | 112 | #[ACTION_WANDER] 113 | #DESIRES: !NEEDS_TO_MANAGE_INVENTORY 114 | #SATISFIES: HAS_NON_RELAXED_GOAL 115 | #LOOP_UNTIL: NEVER 116 | #EXECUTE: WANDER 117 | 118 | [ACTION_GO_TO] 119 | DESIRES: !WEAPON_IS_ARMED 120 | SATISFIES: !HAS_FOCUS_POINT 121 | LOOP_UNTIL: NEVER 122 | EXECUTE: WALK_TO 123 | 124 | [ACTION_PICK_UP_ITEM] 125 | SATISFIES: !HAS_NEEDS 126 | DESIRES: KNOWS_ABOUT_ITEM 127 | LOOP_UNTIL: NEVER 128 | EXECUTE: PICK_UP_ITEM 129 | NON_CRITICAL: True 130 | 131 | [ACTION_FIND_ITEM] 132 | SATISFIES: KNOWS_ABOUT_ITEM 133 | DESIRES: HAS_NON_RELAXED_GOAL 134 | LOOP_UNTIL: HAS_NEEDS_TO_MEET 135 | 136 | [ACTION_GO_TO_SHELTER] 137 | SATISFIES: IS_IN_SHELTER 138 | DESIRES: HAS_SHELTER 139 | EXECUTE: TAKE_SHELTER 140 | LOOP_UNTIL: IS_IN_SHELTER 141 | 142 | [ACTION_FIND_SHELTER] 143 | SATISFIES: HAS_SHELTER 144 | DESIRES: HAS_NON_RELAXED_GOAL 145 | LOOP_UNTIL: HAS_SHELTER 146 | 147 | [ACTION_FOLLOW] 148 | SATISFIES: !HAS_TARGET_TO_FOLLOW,!HAS_TARGET_TO_GUARD 149 | LOOP_UNTIL: NEVER 150 | EXECUTE: FOLLOW_TARGET 151 | 152 | [ACTION_MANAGE_INVENTORY] 153 | SATISFIES: !NEEDS_TO_MANAGE_INVENTORY 154 | EXECUTE: MANAGE_INVENTORY 155 | LOOP_UNTIL: !MANAGE_INVENTORY 156 | 157 | [ACTION_IDLE] 158 | SATISFIES: HAS_NON_RELAXED_GOAL 159 | EXECUTE: NEVER 160 | LOOP_UNTIL: ALWAYS 161 | 162 | [ACTION_ARM_WEAPON] 163 | DESIRES: !NEEDS_TO_MANAGE_INVENTORY 164 | SATISFIES: WEAPON_IS_ARMED 165 | EXECUTE: NEVER 166 | LOOP_UNTIL: NEVER 167 | 168 | [ACTION_SHOOT_THREAT] 169 | DESIRES: !NEEDS_TO_MANAGE_INVENTORY 170 | SATISFIES: !HAS_THREATS 171 | EXECUTE: RANGED_ATTACK 172 | LOOP_UNTIL: NEVER 173 | 174 | [ACTION_MELEE_THREAT] 175 | SATISFIES: !HAS_THREATS,-MELEE_READY,-!RANGED_READY 176 | EXECUTE: MELEE_ATTACK 177 | LOOP_UNTIL: NEVER 178 | 179 | [ACTION_FIND_THREAT] 180 | DESIRES: !WEAPON_IS_ARMED,-HAS_VISIBLE_THREAT 181 | SATISFIES: !HAS_LOST_THREAT 182 | REQUIRE: IS_CONFIDENT 183 | EXECUTE: SEARCH_FOR_THREAT 184 | LOOP_UNTIL: NEVER 185 | 186 | [ACTION_HOLD] 187 | SATISFIES: !HAS_LOST_THREAT 188 | REQUIRE: !IS_CONFIDENT 189 | EXECUTE: NEVER 190 | LOOP_UNTIL: NEVER 191 | 192 | [ACTION_COVER] 193 | SATISFIES: !HAS_VISIBLE_THREAT 194 | EXECUTE: HIDE 195 | LOOP_UNTIL: NEVER 196 | 197 | [ACTION_SAFETY] 198 | SATISFIES: !COVER_EXPOSED 199 | EXECUTE: TAKE_COVER 200 | LOOP_UNTIL: NEVER 201 | 202 | [ACTION_MISSION] 203 | SATISFIES: !HAS_MISSION 204 | EXECUTE: DO_MISSION 205 | LOOP_UNTIL: NEVER 206 | 207 | #END 208 | -------------------------------------------------------------------------------- /drawing.py: -------------------------------------------------------------------------------- 1 | #All of this code is awful and needs to be re-written at some point. 2 | #The line drawing functions have been reimplemented in other modules, 3 | #so I'm not entirely sure how often these are called. 4 | 5 | class line_diag: 6 | def __init__(self, start, end): 7 | self.path = [] 8 | if start == end: return None 9 | 10 | self.start = list(start) 11 | self.end = list(end) 12 | 13 | self.steep = abs(self.end[1]-self.start[1]) > abs(self.end[0]-self.start[0]) 14 | 15 | if self.steep: 16 | self.start = self.swap(self.start[0],self.start[1]) 17 | self.end = self.swap(self.end[0],self.end[1]) 18 | 19 | if self.start[0] > self.end[0]: 20 | self.start[0],self.end[0] = self.swap(self.start[0],self.end[0]) 21 | self.start[1],self.end[1] = self.swap(self.start[1],self.end[1]) 22 | 23 | dx = self.end[0] - self.start[0] 24 | dy = abs(self.end[1] - self.start[1]) 25 | error = 0 26 | 27 | try: 28 | derr = dy/float(dx) 29 | except: 30 | return None 31 | 32 | ystep = 0 33 | y = self.start[1] 34 | 35 | if self.start[1] < self.end[1]: ystep = 1 36 | else: ystep = -1 37 | 38 | for x in range(self.start[0],self.end[0]+1): 39 | if self.steep: 40 | self.path.append((y,x)) 41 | else: 42 | self.path.append((x,y)) 43 | 44 | error += derr 45 | 46 | if error >= 0.5: 47 | y += ystep 48 | error -= 1.0 49 | 50 | if not self.path[0] == (start[0],start[1]): 51 | self.path.reverse() 52 | 53 | def swap(self,n1,n2): 54 | return [n2,n1] 55 | 56 | def diag_line(start, end): 57 | _l = line_diag(start,end) 58 | 59 | return _l.path 60 | 61 | def draw_3d_line(pos1,pos2): 62 | _xchange = pos2[0]-pos1[0] 63 | _ychange = pos2[1]-pos1[1] 64 | _zchange = pos2[2]-pos1[2] 65 | 66 | _line = [tuple(pos1)] 67 | 68 | if abs(_xchange) >= abs(_ychange) and abs(_xchange) >= abs(_zchange): 69 | _xnegative = False 70 | _ynegative = False 71 | _znegative = False 72 | 73 | if _ychange < 0: 74 | _ynegative = True 75 | 76 | if _xchange < 0: 77 | _xnegative = True 78 | 79 | if _zchange < 0: 80 | _znegative = True 81 | 82 | _x = pos1[0] 83 | _y = pos1[1] 84 | _z = pos1[2] 85 | 86 | try: 87 | _ystep = abs(_ychange/float(_xchange)) 88 | except: 89 | _ystep = abs(_ychange) 90 | 91 | try: 92 | _zstep = abs(_zchange/float(_xchange)) 93 | except: 94 | _zstep = abs(_zchange) 95 | 96 | for x in range(1,abs(_xchange)): 97 | if _xnegative: 98 | x = -x 99 | 100 | if _ynegative: 101 | _y -= _ystep 102 | else: 103 | _y += _ystep 104 | 105 | if _znegative: 106 | _z -= _zstep 107 | else: 108 | _z += _zstep 109 | 110 | _line.append((_x+x,int(round(_y)),int(round(_z)))) 111 | 112 | elif abs(_ychange) >= abs(_xchange) and abs(_ychange) >= abs(_zchange): 113 | _xnegative = False 114 | _ynegative = False 115 | _znegative = False 116 | 117 | if _ychange < 0: 118 | _ynegative = True 119 | 120 | if _xchange < 0: 121 | _xnegative = True 122 | 123 | if _zchange < 0: 124 | _znegative = True 125 | 126 | _x = pos1[0] 127 | _y = pos1[1] 128 | _z = pos1[2] 129 | _xstep = abs(_xchange/float(_ychange)) 130 | _zstep = abs(_zchange/float(_ychange)) 131 | 132 | for y in range(1,abs(_ychange)): 133 | if _ynegative: 134 | y = -y 135 | 136 | if _xnegative: 137 | _x -= _xstep 138 | else: 139 | _x += _xstep 140 | 141 | if _znegative: 142 | _z -= _zstep 143 | else: 144 | _z += _zstep 145 | 146 | _line.append((int(round(_x)),_y+y,int(round(_z)))) 147 | 148 | elif abs(_zchange) > abs(_xchange) and abs(_zchange) > abs(_ychange): 149 | _xnegative = False 150 | _ynegative = False 151 | _znegative = False 152 | 153 | if _zchange < 0: 154 | _znegative = True 155 | 156 | if _xchange < 0: 157 | _xnegative = True 158 | 159 | if _ychange < 0: 160 | _ynegative = True 161 | 162 | _x = pos1[0] 163 | _y = pos1[1] 164 | _z = pos1[2] 165 | _xstep = abs(_xchange/float(_zchange)) 166 | _ystep = abs(_ychange/float(_zchange)) 167 | 168 | for z in range(1,abs(_zchange)): 169 | if _znegative: 170 | z = -z 171 | 172 | if _xnegative: 173 | _x -= _xstep 174 | else: 175 | _x += _xstep 176 | 177 | if _ynegative: 178 | _y -= _ystep 179 | else: 180 | _y += _ystep 181 | 182 | _line.append((int(round(_x)),int(round(_y)),_z+z)) 183 | 184 | _line.append(tuple(pos2)) 185 | 186 | return _line 187 | 188 | def draw_circle(at, size): 189 | Circle = 0 190 | width=size 191 | height=size 192 | CenterX=(width/float(2)) 193 | CenterY=(height/float(2)) 194 | circle = [] 195 | 196 | for i in range(height+1): 197 | for j in range(width+1): 198 | Circle = (((i-CenterY)*(i-CenterY))/((float(height)/2)*(float(height)/2)))+((((j-CenterX)*(j-CenterX))/((float(width)/2)*(float(width)/2)))); 199 | if Circle>0 and Circle<1.1: 200 | circle.append((at[0]+(j-(width/2)),at[1]+(i-(height/2)))) 201 | 202 | if not at in circle: 203 | circle.append(at) 204 | 205 | return circle -------------------------------------------------------------------------------- /data/life/human.json: -------------------------------------------------------------------------------- 1 | { 2 | "body": { 3 | "lthigh": { 4 | "damage_mod": 1.0, 5 | "bleed_mod": 1.3, 6 | "flags": "SKIN[{\"thickness\": 2}]|MUSCLE|BONE", 7 | "parent": "hip", 8 | "size": 4 9 | }, 10 | "lknee": { 11 | "damage_mod": 1.0, 12 | "bleed_mod": 0.5, 13 | "flags": "SKIN[{\"thickness\": 2}]|MUSCLE|BONE", 14 | "parent": "lthigh", 15 | "size": 3 16 | }, 17 | "rfoot": { 18 | "damage_mod": 1.0, 19 | "bleed_mod": 0.2, 20 | "flags": "SKIN[{\"thickness\": 2}]|MUSCLE|BONE", 21 | "parent": "rlowerleg", 22 | "size": 2 23 | }, 24 | "llowerleg": { 25 | "damage_mod": 1.0, 26 | "bleed_mod": 0.3, 27 | "flags": "SKIN[{\"thickness\": 2}]|MUSCLE|BONE", 28 | "parent": "lknee", 29 | "size": 3 30 | }, 31 | "rhand": { 32 | "damage_mod": 1.0, 33 | "bleed_mod": 0.5, 34 | "flags": "SKIN[{\"thickness\": 2}]|MUSCLE|BONE", 35 | "parent": "rforearm", 36 | "size": 2 37 | }, 38 | "rupperarm": { 39 | "damage_mod": 1.0, 40 | "bleed_mod": 0.3, 41 | "flags": "SKIN[{\"thickness\": 2}]|MUSCLE|BONE", 42 | "parent": "lshoulder", 43 | "size": 3 44 | }, 45 | "lhand": { 46 | "damage_mod": 1.0, 47 | "bleed_mod": 0.5, 48 | "flags": "SKIN[{\"thickness\": 2}]|MUSCLE|BONE", 49 | "parent": "lforearm", 50 | "size": 2 51 | }, 52 | "rthigh": { 53 | "damage_mod": 1.0, 54 | "bleed_mod": 1.3, 55 | "flags": "SKIN[{\"thickness\": 2}]|MUSCLE|BONE", 56 | "parent": "hip", 57 | "size": 4 58 | }, 59 | "rknee": { 60 | "damage_mod": 1.0, 61 | "bleed_mod": 0.5, 62 | "flags": "SKIN[{\"thickness\": 2}]|MUSCLE|BONE", 63 | "parent": "rthigh", 64 | "size": 3 65 | }, 66 | "chest": { 67 | "damage_mod": 2.0, 68 | "bleed_mod": 1.5, 69 | "flags": "SKIN[{\"thickness\": 2}]|MUSCLE|BONE|CRUCIAL", 70 | "parent": "neck", 71 | "size": 7 72 | }, 73 | "lelbow": { 74 | "damage_mod": 1.0, 75 | "bleed_mod": 0.2, 76 | "flags": "SKIN[{\"thickness\": 2}]|MUSCLE|BONE", 77 | "parent": "lupperarm", 78 | "size": 2 79 | }, 80 | "relbow": { 81 | "damage_mod": 1.0, 82 | "bleed_mod": 0.2, 83 | "flags": "SKIN[{\"thickness\": 2}]|MUSCLE|BONE", 84 | "parent": "rupperarm", 85 | "size": 2 86 | }, 87 | "lupperarm": { 88 | "damage_mod": 1.0, 89 | "bleed_mod": 0.3, 90 | "flags": "SKIN[{\"thickness\": 2}]|MUSCLE|BONE", 91 | "parent": "lshoulder", 92 | "size": 3 93 | }, 94 | "hip": { 95 | "damage_mod": 2.0, 96 | "bleed_mod": 1.0, 97 | "flags": "SKIN[{\"thickness\": 2}]|MUSCLE|BONE|CRUCIAL", 98 | "parent": "stomach", 99 | "size": 4 100 | }, 101 | "head": { 102 | "damage_mod": 2.0, 103 | "bleed_mod": 1.5, 104 | "flags": "SKIN[{\"thickness\": 2}]|BONE|CRUCIAL|AFFECTS[sight,hearing]", 105 | "size": 4 106 | }, 107 | "stomach": { 108 | "damage_mod": 1.0, 109 | "bleed_mod": 1.5, 110 | "flags": "SKIN[{\"thickness\": 2}]|MUSCLE|BONE|CRUCIAL", 111 | "parent": "chest", 112 | "size": 5 113 | }, 114 | "rlowerleg": { 115 | "damage_mod": 1.0, 116 | "bleed_mod": 0.3, 117 | "flags": "SKIN[{\"thickness\": 2}]|MUSCLE|BONE", 118 | "parent": "rknee", 119 | "size": 3 120 | }, 121 | "neck": { 122 | "damage_mod": 2.0, 123 | "bleed_mod": 2.0, 124 | "flags": "SKIN[{\"thickness\": 2}]|MUSCLE|BONE|CRUCIAL", 125 | "parent": "head", 126 | "size": 3 127 | }, 128 | "lshoulder": { 129 | "damage_mod": 1.0, 130 | "bleed_mod": 0.5, 131 | "flags": "SKIN[{\"thickness\": 2}]|MUSCLE|BONE", 132 | "parent": "chest", 133 | "size": 4 134 | }, 135 | "lforearm": { 136 | "damage_mod": 1.0, 137 | "bleed_mod": 0.2, 138 | "flags": "SKIN[{\"thickness\": 2}]|MUSCLE|BONE", 139 | "parent": "lelbow", 140 | "size": 3 141 | }, 142 | "lfoot": { 143 | "damage_mod": 1.0, 144 | "bleed_mod": 0.2, 145 | "flags": "SKIN[{\"thickness\": 2}]|MUSCLE|BONE", 146 | "parent": "llowerleg", 147 | "size": 2 148 | }, 149 | "rforearm": { 150 | "damage_mod": 1.0, 151 | "bleed_mod": 0.2, 152 | "flags": "SKIN[{\"thickness\": 2}]|MUSCLE|BONE", 153 | "parent": "relbow", 154 | "size": 3 155 | }, 156 | "rshoulder": { 157 | "damage_mod": 1.0, 158 | "bleed_mod": 0.5, 159 | "flags": "SKIN[{\"thickness\": 2}]|MUSCLE|BONE", 160 | "parent": "chest", 161 | "size": 4 162 | } 163 | }, 164 | "name": "$FIRST_AND_LAST_NAME_FROM_SPECIES", 165 | "vars": "vision_max=40|hunger_max=20000|thirst_max=10000|hunger=hunger_max|thirst=thirst_max", 166 | "flags": "INTELLIGENT|USES_FIREARMS|LEGS[lthigh,rthigh]|HANDS[lhand,rhand]|ARMS[lshouder,rshoulder]|CAN_GROUP|CAN_SEE|HUNGER|THIRST|MELEE[rfoot,lfoot,lhand,rhand]|RANGED[lhand,rhand]", 167 | "type": "humanoid", 168 | "species": "human", 169 | "icon": "@" 170 | } -------------------------------------------------------------------------------- /render_map.pyx: -------------------------------------------------------------------------------- 1 | from graphics import blit_tile, darken_tile, blit_char, blit_tile, get_view_by_name, blit_char_to_view 2 | from globals import MAP_CHAR_BUFFER, DARK_BUFFER 3 | from tiles import * 4 | 5 | import libtcodpy as tcod 6 | 7 | import numbers 8 | import effects 9 | import numpy 10 | import tiles 11 | import alife 12 | 13 | import time 14 | 15 | VERSION = 6 16 | 17 | def render_map(map, force_camera_pos=None, view_size=MAP_WINDOW_SIZE, **kwargs): 18 | cdef int _CAMERA_POS[2] 19 | cdef int _MAP_SIZE[2] 20 | cdef int _MAP_WINDOW_SIZE[2] 21 | _CAMERA_POS[0] = CAMERA_POS[0] 22 | _CAMERA_POS[1] = CAMERA_POS[1] 23 | _CAMERA_POS[2] = CAMERA_POS[2] 24 | _MAP_SIZE[0] = MAP_SIZE[0] 25 | _MAP_SIZE[1] = MAP_SIZE[1] 26 | _MAP_SIZE[2] = MAP_SIZE[2] 27 | _MAP_WINDOW_SIZE[0] = view_size[0] 28 | _MAP_WINDOW_SIZE[1] = view_size[1] 29 | 30 | cdef int x, y, z 31 | cdef int _X_MAX = _CAMERA_POS[0]+view_size[0] 32 | cdef int _Y_MAX = _CAMERA_POS[1]+view_size[1] 33 | cdef int _X_START = _CAMERA_POS[0] 34 | cdef int _Y_START = _CAMERA_POS[1] 35 | cdef int _RENDER_X = 0 36 | cdef int _RENDER_Y = 0 37 | 38 | cdef int _min_los_x = 0 39 | cdef int _max_los_x = view_size[0] 40 | cdef int _min_los_y = 0 41 | cdef int _max_los_y = view_size[1] 42 | 43 | cdef int _x_mod = 0 44 | cdef int _y_mod = 0 45 | 46 | if force_camera_pos: 47 | _cam_pos = force_camera_pos[:] 48 | else: 49 | _cam_pos = SETTINGS['camera_track'][:] 50 | 51 | if not CAMERA_POS[0]: 52 | _x_mod = SETTINGS['camera_track'][0]-view_size[0]/2 53 | elif CAMERA_POS[0]+view_size[0]>=MAP_SIZE[0]: 54 | _x_mod = 0 55 | 56 | if not CAMERA_POS[1]: 57 | _y_mod = SETTINGS['camera_track'][1]-view_size[1]/2 58 | elif CAMERA_POS[1]+view_size[1]>=MAP_SIZE[1]: 59 | _y_mod = 0 60 | 61 | _camera_x_offset = CAMERA_POS[0]-_cam_pos[0] 62 | _camera_y_offset = CAMERA_POS[1]-_cam_pos[1] 63 | 64 | los = None 65 | if 'los' in kwargs: 66 | los = kwargs['los'] 67 | _min_los_x = (los.shape[0]/2)-view_size[0]/2-_x_mod+_camera_x_offset 68 | _max_los_x = los.shape[0] 69 | _min_los_y = (los.shape[1]/2)-view_size[1]/2-_y_mod+_camera_y_offset 70 | _max_los_y = los.shape[1] 71 | 72 | _view = get_view_by_name('map') 73 | _TEMP_MAP_CHAR_BUFFER = _view['char_buffer'][1].copy() 74 | 75 | if _X_MAX>_MAP_SIZE[0]: 76 | _X_MAX = _MAP_SIZE[0] 77 | 78 | if _Y_MAX>_MAP_SIZE[1]: 79 | _Y_MAX = _MAP_SIZE[1] 80 | 81 | for x in range(_X_START, _X_MAX): 82 | _RENDER_X = x-_CAMERA_POS[0] 83 | for y in range(_Y_START, _Y_MAX): 84 | _RENDER_Y = y-_CAMERA_POS[1] 85 | 86 | if _min_los_x+_RENDER_X<0 or _min_los_x+_RENDER_X>=_max_los_x or _min_los_y+_RENDER_Y<0 or _min_los_y+_RENDER_Y>=_max_los_y: 87 | _visible = False 88 | elif 'los' in kwargs: 89 | _visible = los[_min_los_x+_RENDER_X, _min_los_y+_RENDER_Y] 90 | else: 91 | _visible = True 92 | 93 | if _TEMP_MAP_CHAR_BUFFER[_RENDER_Y,_RENDER_X]: 94 | continue 95 | 96 | _view['light_buffer'][1][_RENDER_Y, _RENDER_X] = 0 97 | _drawn = False 98 | _shadow = 2 99 | for z in range(MAP_SIZE[2]-1,-1,-1): 100 | if map[x][y][z]: 101 | if z > _CAMERA_POS[2] and SETTINGS['draw z-levels above']: 102 | if 'translucent' in tiles.get_raw_tile(tiles.get_tile((x, y, z))): 103 | if _shadow >= 2: 104 | _shadow += 1 105 | else: 106 | _shadow = 0 107 | 108 | if not _visible: 109 | blit_tile(_RENDER_X, _RENDER_Y, map[x][y][z], 'map') 110 | darken_tile(_RENDER_X, _RENDER_Y, abs((_CAMERA_POS[2]-z))*10) 111 | _drawn = True 112 | 113 | elif z == _CAMERA_POS[2]: 114 | if (x,y,z) in SELECTED_TILES[0] and time.time()%1>=0.5: 115 | if time.time()%1>=0.75: 116 | _back_color = tcod.black 117 | else: 118 | _back_color = tcod.white 119 | 120 | blit_char_to_view(_RENDER_X, 121 | _RENDER_Y, 122 | 'X', 123 | (tcod.darker_grey, _back_color), 124 | 'map') 125 | else: 126 | if _shadow > 2: 127 | darken_tile(_RENDER_X, _RENDER_Y, 15*(_shadow-2)) 128 | 129 | if map[x][y][z+1]: 130 | blit_tile(_RENDER_X, _RENDER_Y, map[x][y][z+1], 'map') 131 | else: 132 | blit_tile(_RENDER_X, _RENDER_Y, map[x][y][z], 'map') 133 | 134 | if SETTINGS['draw effects']: 135 | if _visible: 136 | effects.draw_splatter((x,y,z), (_RENDER_X,_RENDER_Y)) 137 | effects.draw_effect((x, y)) 138 | 139 | if not _visible: 140 | darken_tile(_RENDER_X, _RENDER_Y, 30) 141 | 142 | if SETTINGS['draw visible chunks']: 143 | _visible_chunks = alife.brain.get_flag(LIFE[SETTINGS['controlling']], 'visible_chunks') 144 | 145 | if _visible_chunks: 146 | for _chunk in _visible_chunks: 147 | if alife.chunks.position_is_in_chunk((x, y), _chunk): 148 | darken_tile(_RENDER_X, _RENDER_Y, abs(90)) 149 | 150 | _drawn = True 151 | 152 | elif z < _CAMERA_POS[2] and SETTINGS['draw z-levels below']: 153 | blit_tile(_RENDER_X,_RENDER_Y,map[x][y][z], 'map') 154 | darken_tile(_RENDER_X,_RENDER_Y,abs((_CAMERA_POS[2]-z))*10) 155 | _drawn = True 156 | 157 | if SETTINGS['draw z-levels above'] and _drawn: 158 | break 159 | 160 | if not _drawn: 161 | blit_tile(_RENDER_X, _RENDER_Y, BLANK_TILE, 'map') 162 | --------------------------------------------------------------------------------