├── .gitignore ├── alife ├── __init__.py ├── action.py ├── alife_camp.py ├── alife_combat.py ├── alife_cover.py ├── alife_discover.py ├── alife_escape.py ├── alife_explore.py ├── alife_follow.py ├── alife_group.py ├── alife_guard.py ├── alife_hidden.py ├── alife_manage_items.py ├── alife_manage_targets.py ├── alife_needs.py ├── alife_search.py ├── alife_shelter.py ├── alife_surrender.py ├── alife_talk.py ├── alife_work.py ├── brain.py ├── camps.py ├── chunks.py ├── combat.py ├── factions.py ├── goals.py ├── groups.py ├── jobs.py ├── judgement.py ├── memory.py ├── movement.py ├── noise.py ├── planner.py ├── raids.py ├── rawparse.py ├── references.py ├── sight.py ├── snapshots.py ├── sound.py ├── speech.py ├── stances.py ├── stats.py └── survival.py ├── art └── pngs │ ├── flagsdev_logo.png │ └── minilogo.png ├── artifacts.py ├── bad_numbers.py ├── build.bat ├── build_life.py ├── buildinggen.py ├── cache.py ├── compile_cython_modules.py ├── contexts.py ├── crafting.py ├── damage.py ├── data ├── items │ ├── 22_lr_cartridge.json │ ├── 22_lr_mag.json │ ├── 22_rifle.json │ ├── 5.45x39mm_mag.json │ ├── 5.45x39mm_round.json │ ├── 9x19mm_mag.json │ ├── 9x19mm_round.json │ ├── ak74.json │ ├── alice_pack.json │ ├── aspirin.json │ ├── bed.json │ ├── blue_jeans.json │ ├── blue_shirt.json │ ├── brown_hoodie.json │ ├── burner.json │ ├── can_of_corn.json │ ├── chest_holster.json │ ├── coat_rack.json │ ├── cupboard.json │ ├── cz_511.json │ ├── desk.json │ ├── electric_lantern.json │ ├── fall_camo_pants.json │ ├── frag_grenade.json │ ├── gas_mask.json │ ├── gas_stove.json │ ├── glock.json │ ├── kevlar_helmet.json │ ├── kevlar_jacket.json │ ├── leather_backpack.json │ ├── m9.json │ ├── metal_door.json │ ├── metal_shelving.json │ ├── military_crate.json │ ├── molotov.json │ ├── mp5.json │ ├── mp5_mag.json │ ├── office_chair.json │ ├── radio.json │ ├── scout_pack.json │ ├── sneakers.json │ ├── soda.json │ ├── trenchcoat.json │ ├── utility_backpack.json │ ├── white_cloth.json │ ├── white_shirt.json │ ├── wooden_display_case.json │ ├── wooden_door.json │ └── wooden_dresser.json ├── life │ ├── dog.dat │ ├── dog.goap │ ├── dog.json │ ├── dog.xml │ ├── human.dat │ ├── human.goap │ ├── human.json │ ├── human.xml │ ├── night_terror.dat │ ├── night_terror.goap │ ├── night_terror.json │ └── night_terror.xml ├── missions │ ├── capture_territory.dat │ ├── deliver_item.dat │ ├── fetch_item.dat │ ├── kill_target.dat │ ├── locate_target.dat │ ├── retrieve_item.dat │ ├── travel_to.dat │ └── zes_glock.dat ├── prefabs │ └── test.json ├── text │ ├── buildings.txt │ ├── dialog.txt │ ├── human_first_names.txt │ ├── human_last_names.txt │ ├── nouns.txt │ └── places.txt └── tiles │ ├── soldier2_source.png │ ├── soldier3_source.ase │ ├── soldier4_22lr.png │ ├── soldier4_glock17.png │ ├── soldier4_mac10.png │ ├── soldier4_unarmed.png │ ├── terminal16x16_aa_as_incol_tiles.png │ ├── terminal16x16_aa_inrow.png │ ├── terminal16x16_gs_as_incol.png │ └── terminal8x8_gs_as_incol.png ├── debug.py ├── dialog.py ├── dijkstra.py ├── docs ├── 2013arrp.md ├── alife.md ├── alife_todo.md ├── changelog.txt ├── design.md ├── mission_planning.md ├── missions.md ├── roadmap.md ├── style_guide.md ├── testingnotes.md └── weapons.md ├── drawing.py ├── effects.py ├── encounters.py ├── events.py ├── fast_dijkstra.pyx ├── fast_scan_surroundings.pyx ├── fov.pyx ├── freeze.py ├── globals.py ├── graphics.py ├── historygen.py ├── inputs.py ├── items.py ├── language.py ├── libtcodpy.py ├── license.txt ├── life.py ├── locks.py ├── logic.py ├── mainmenu.py ├── mapgen.py ├── maps.py ├── maputils.py ├── melee.py ├── menus.py ├── missions.py ├── network.py ├── overwatch ├── __init__.py ├── core.py ├── events.py └── situations.py ├── pathfinding.py ├── player.py ├── prefabs.py ├── profiles.py ├── pyfov.py ├── reactor-3.py ├── readme.md ├── render_fast_los.pyx ├── render_los.pyx ├── render_map.pyx ├── render_map_setup.py ├── scripting.py ├── smp.py ├── spawns.py ├── terraform.py ├── tests ├── M01_data_structure.py ├── M01_data_structure_3d.py ├── M01_data_visualization.py ├── M01_map_editor.py ├── M05_gas_mode.py ├── M06_Melee.py ├── M06_fov.py ├── M06_zonepath.py ├── M07_buildings.py ├── M07_melee.py ├── M07_recoil.py └── fast_render_simple_numpy.py ├── threads.py ├── tiles.py ├── timers.py ├── tools ├── ReactorWatch.py ├── guntest.py ├── life_dump.py ├── show_profile.py └── templates │ ├── camp.html │ ├── group.html │ ├── index.html │ ├── life.html │ └── memory.html ├── weapons.py ├── weather.py ├── worldgen.py └── zones.py /.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 | -------------------------------------------------------------------------------- /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_camp.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flags/Reactor-3/b41a2904c9ec8cc14bcee03611602d0e568acf12/alife/alife_camp.py -------------------------------------------------------------------------------- /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) -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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_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 | -------------------------------------------------------------------------------- /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_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) -------------------------------------------------------------------------------- /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']) -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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_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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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]) -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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= 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 | -------------------------------------------------------------------------------- /alife/raids.py: -------------------------------------------------------------------------------- 1 | from globals import * 2 | 3 | import life as lfe 4 | 5 | import camps 6 | import brain 7 | 8 | import logging 9 | 10 | def camp_has_raid(camp_id): 11 | if camps.get_camp(camp_id)['raid']: 12 | return True 13 | 14 | return False 15 | 16 | def create_raid(camp_id, raiders=[], join=None): 17 | _camp = camps.get_camp(camp_id) 18 | 19 | if not camp_has_raid(camp_id): 20 | _camp['raid'] = {'started': WORLD_INFO['ticks'], 21 | 'raiders': [], 22 | 'defenders': [], 23 | 'score': 0} 24 | logging.debug('Created raid: %s' % _camp['name']) 25 | 26 | if join: 27 | defend_camp(camp_id, join) 28 | 29 | def add_raiders(camp_id, raiders): 30 | _camp = camps.get_camp(camp_id) 31 | 32 | for raider in [r for r in raiders if not r in _camp['raid']['raiders']]: 33 | _camp['raid']['raiders'].append(raider) 34 | logging.debug('%s added to raid of camp %s' % (' '.join(LIFE[raider]['name']), _camp['name'])) 35 | 36 | for defender in get_defenders(camp_id): 37 | if not brain.knows_alife_by_id(LIFE[defender], raider): 38 | if defender == raider: 39 | logging.warning('FIXME: Raider is member of camp.') 40 | continue 41 | 42 | brain.meet_alife(LIFE[defender], LIFE[raider]) 43 | 44 | def defend_camp(camp_id, life_id): 45 | _camp = camps.get_camp(camp_id) 46 | 47 | if not life_id in _camp['raid']['defenders']: 48 | _camp['raid']['defenders'].append(life_id) 49 | logging.debug('%s is now defending camp %s' % (' '.join(LIFE[life_id]['name']), _camp['name'])) 50 | 51 | for raider in get_raiders(camp_id): 52 | if not brain.knows_alife_by_id(LIFE[life_id], raider): 53 | brain.meet_alife(LIFE[life_id], LIFE[raider]) 54 | 55 | def get_raiders(camp_id): 56 | return camps.get_camp(camp_id)['raid']['raiders'] 57 | 58 | def get_defenders(camp_id): 59 | return camps.get_camp(camp_id)['raid']['defenders'] 60 | 61 | def has_control(camp_id, group_id): 62 | if group_id == camps.get_controlling_group_global(camp_id): 63 | return True 64 | 65 | return False -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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' -------------------------------------------------------------------------------- /art/pngs/flagsdev_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flags/Reactor-3/b41a2904c9ec8cc14bcee03611602d0e568acf12/art/pngs/flagsdev_logo.png -------------------------------------------------------------------------------- /art/pngs/minilogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flags/Reactor-3/b41a2904c9ec8cc14bcee03611602d0e568acf12/art/pngs/minilogo.png -------------------------------------------------------------------------------- /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]) dist\git-version.txt -------------------------------------------------------------------------------- /build_life.py: -------------------------------------------------------------------------------- 1 | from globals import * 2 | 3 | import json 4 | import sys 5 | import os 6 | 7 | def read_xml_file(file): 8 | _file = [] 9 | 10 | with open(os.path.join(LIFE_DIR,file),'r') as e: 11 | for line in e.readlines(): 12 | line = line.strip() 13 | 14 | if not line: 15 | continue 16 | 17 | _file.append(line) 18 | 19 | return _file 20 | 21 | raise Exception('Could not read file: ' % os.path.join(LIFE_DIR,file)) 22 | 23 | def save_json_file(file,data): 24 | with open(os.path.join(LIFE_DIR,file.lower()),'w') as e: 25 | e.write(json.dumps(data,indent=2)) 26 | 27 | def get_value(data,value): 28 | for entry in data: 29 | if entry.count(value): 30 | return entry.partition('>')[2].partition('-1 and entry.count('' % tag): 42 | return get_children_of_tag(data[_start:data.index(entry)]) 43 | 44 | raise Exception('Could not find tag: %s' % tag) 45 | 46 | def connect_limb(name,limb,to,start): 47 | for limb2 in start: 48 | if to == limb2: 49 | start[limb2]['attached'][name] = limb 50 | return True 51 | 52 | connect_limb(name,limb,to,start[limb2]['attached']) 53 | 54 | def get_children_of_tag(taglist): 55 | _limbs = {} 56 | _name = '' 57 | _flags = '' 58 | _parent = '' 59 | _size = 0 60 | _damage_mod = 0 61 | _bleed_mod = 0 62 | 63 | for tag in taglist: 64 | _key = tag.partition('<')[2].partition('>')[0] 65 | 66 | if _key.count('/'): 67 | if not _parent: 68 | _limbs[_name] = {'flags': _flags} 69 | _limbs[_name]['damage_mod'] = float(_damage_mod) 70 | _limbs[_name]['bleed_mod'] = float(_bleed_mod) 71 | _limbs[_name]['size'] = int(_size) 72 | else: 73 | _limb = {'flags': _flags} 74 | _limb['parent'] = _parent 75 | _limb['damage_mod'] = float(_damage_mod) 76 | _limb['bleed_mod'] = float(_bleed_mod) 77 | _limb['size'] = int(_size) 78 | _limbs[_name] = _limb 79 | 80 | #if _parent in _limbs: 81 | # _limbs[_parent]['attached'][_name] = _limb 82 | #else: 83 | # connect_limb(_name,_limb,_parent,_limbs) 84 | 85 | _name = '' 86 | _flags = '' 87 | _parent = '' 88 | continue 89 | 90 | _value = tag.partition('>')[2].rpartition('=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'] -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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/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/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/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/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/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/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/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/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/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"} -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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/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/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/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/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/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/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/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/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/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/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/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 | -------------------------------------------------------------------------------- /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/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/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/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/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/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/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/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/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/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/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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /data/life/night_terror.json: -------------------------------------------------------------------------------- 1 | { 2 | "body": { 3 | "hip": { 4 | "damage_mod": 2.0, 5 | "bleed_mod": 1.0, 6 | "flags": "SKIN|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|MUSCLE|BONE", 14 | "parent": "chest", 15 | "size": 2 16 | }, 17 | "head": { 18 | "damage_mod": 2.0, 19 | "bleed_mod": 1.5, 20 | "flags": "SKIN|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|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|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|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|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|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|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|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|MUSCLE|BONE", 76 | "parent": "chest", 77 | "size": 2 78 | }, 79 | "blleg": { 80 | "damage_mod": 1.0, 81 | "bleed_mod": 1.3, 82 | "flags": "SKIN|MUSCLE|BONE", 83 | "parent": "hip", 84 | "size": 2 85 | }, 86 | "brleg": { 87 | "damage_mod": 1.0, 88 | "bleed_mod": 1.3, 89 | "flags": "SKIN|MUSCLE|BONE", 90 | "parent": "hip", 91 | "size": 2 92 | }, 93 | "brpaw": { 94 | "damage_mod": 1.0, 95 | "bleed_mod": 0.5, 96 | "flags": "SKIN|MUSCLE|BONE|SHARP", 97 | "parent": "brleg", 98 | "size": 1 99 | } 100 | }, 101 | "name": "Night Terror", 102 | "vars": "vision_max=20|hunger_max=20000|thirst_max=10000|hunger=hunger_max|thirst=thirst_max", 103 | "flags": "LEGS[blleg,brleg]|CAN_GROUP|CAN_SEE|HUNGER|THIRST|MELEE[jaw,flpaw,frpaw]", 104 | "type": "mutant", 105 | "species": "night_terror", 106 | "icon": "T" 107 | } -------------------------------------------------------------------------------- /data/life/night_terror.xml: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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/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 | -------------------------------------------------------------------------------- /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]} -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /data/text/places.txt: -------------------------------------------------------------------------------- 1 | Hope 2 | Genesis 3 | Fortitude 4 | -------------------------------------------------------------------------------- /data/tiles/soldier2_source.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flags/Reactor-3/b41a2904c9ec8cc14bcee03611602d0e568acf12/data/tiles/soldier2_source.png -------------------------------------------------------------------------------- /data/tiles/soldier3_source.ase: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flags/Reactor-3/b41a2904c9ec8cc14bcee03611602d0e568acf12/data/tiles/soldier3_source.ase -------------------------------------------------------------------------------- /data/tiles/soldier4_22lr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flags/Reactor-3/b41a2904c9ec8cc14bcee03611602d0e568acf12/data/tiles/soldier4_22lr.png -------------------------------------------------------------------------------- /data/tiles/soldier4_glock17.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flags/Reactor-3/b41a2904c9ec8cc14bcee03611602d0e568acf12/data/tiles/soldier4_glock17.png -------------------------------------------------------------------------------- /data/tiles/soldier4_mac10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flags/Reactor-3/b41a2904c9ec8cc14bcee03611602d0e568acf12/data/tiles/soldier4_mac10.png -------------------------------------------------------------------------------- /data/tiles/soldier4_unarmed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flags/Reactor-3/b41a2904c9ec8cc14bcee03611602d0e568acf12/data/tiles/soldier4_unarmed.png -------------------------------------------------------------------------------- /data/tiles/terminal16x16_aa_as_incol_tiles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flags/Reactor-3/b41a2904c9ec8cc14bcee03611602d0e568acf12/data/tiles/terminal16x16_aa_as_incol_tiles.png -------------------------------------------------------------------------------- /data/tiles/terminal16x16_aa_inrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flags/Reactor-3/b41a2904c9ec8cc14bcee03611602d0e568acf12/data/tiles/terminal16x16_aa_inrow.png -------------------------------------------------------------------------------- /data/tiles/terminal16x16_gs_as_incol.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flags/Reactor-3/b41a2904c9ec8cc14bcee03611602d0e568acf12/data/tiles/terminal16x16_gs_as_incol.png -------------------------------------------------------------------------------- /data/tiles/terminal8x8_gs_as_incol.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flags/Reactor-3/b41a2904c9ec8cc14bcee03611602d0e568acf12/data/tiles/terminal8x8_gs_as_incol.png -------------------------------------------------------------------------------- /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() -------------------------------------------------------------------------------- /docs/alife.md: -------------------------------------------------------------------------------- 1 | ALife - Version 2 2 | ================= 3 | 4 | # What is "ALife"? 5 | "Artificial Life", in technical terms, is artificial intelligence with a focus on emulating actions 6 | similar to those a player would make. Its goal is to make the single player experience more stimulating 7 | by creating companions or opponents that behave as humans would. 8 | 9 | # The Laws of ALife 10 | ALife is defined by the following specifications: 11 | 12 | * Random does not mean intelligent. Given that all facts about a situation are known, the action taken should be predictable. 13 | 14 | # Realizing Limitations 15 | The catch is that true "ALife" is not possible due to the need of heavy processing power and optimization 16 | that would end up being too situation-specific. The real 17 | 18 | Part 1: Thinking and Understanding 19 | ---------------------------------- 20 | An ALife should be able to look at its surroundings and determine its interest in all visible "targets." 21 | The word "targets" represents all ALifes and Items in the ALife's LOS. Upon being spotted for the first 22 | time, a snapshot of the target is created and logged into the ALife's memory. This snapshot contains the 23 | target's outward appearance, including any defining traits (injury, equipped weapon, etc) for later 24 | identification. 25 | 26 | Once a snapshot has been created, the ALife can start remembering events this target performs. Take note 27 | that these should not be trivial things such as movement, but instead memorable events like engaging in 28 | combat. 29 | 30 | These memories play an important roll in the observing ALife's behavior. 31 | 32 | Part 1.2: Judging 33 | ----------------- 34 | **But first, a note**: It should be noted that this step is where optimization comes into play. Remember that 35 | most functions relating to ALife are performed many times a second- the less we have to do in that second 36 | the better. 37 | 38 | When an event is observed, it is first "judged", resulting in a score ranging from negative infinity to 39 | positive infinity. This score is then added to the ALife's impression of the target and the event is 40 | filed away. The value is never recalculated. 41 | 42 | Consider the following: The impression an ALife has of another ALife determines what action is taken by 43 | the observing ALife, so heavy calculations are justified by only being executed once. 44 | 45 | **Note**: 46 | Distance (or any variable that changes on a turn-by-turn basis) does not play a part in judgement. 47 | 48 | # Every Turn 49 | A rundown of the ALife's condition is performed, but only in situations where it is required. 50 | 51 | snapshot = {'condition': 0, 52 | 'appearance': 0, 53 | 'visible_items': []} 54 | 55 | for limb in body 56 | snapshot['condition'] += get_condition(limb) 57 | 58 | for item in limb: 59 | snapshot['appearance'] += get_quality(item) 60 | snapshot['visible_items].append(item['name']) 61 | 62 | update_snapshot(life,target['id'],snapshot) 63 | 64 | Part 2: Pathfinding 65 | ------------------- 66 | Regardless of how complicated the rest of the ALife becomes, pathfinding will almost always be the biggest 67 | bottleneck. This is due to the way most algorithms react when handed a larger map to parse, and since the 68 | level size for most Reactor 3 maps will be much, much larger than 1000x1000, certain precautions must taken. 69 | 70 | Consider the following needs for different pathing types: 71 | 72 | * The most intense pathfinding will done in combat when using cover mechanics or fleeing from an opponent 73 | * An algorithm must take path costs similar to the dijkstra approach (which does not work due to map size) 74 | * When the ALife is not in combat, straight lines can be used to path 75 | 76 | Algorithm 1: Weighted A* 77 | 78 | -------------------------------------------------------------------------------- /docs/changelog.txt: -------------------------------------------------------------------------------- 1 | New in version 0.7: 2 | * AI states now controlled by the GOAP planner. 3 | * Entirely new ALife behaviors. 4 | * Optimized ALife logic 5 | * Dogs now follow the pack leader and spawn in larger amounts. 6 | * ALife now go into a "semi-offline" mode once outside the range of the player. 7 | * Several movement functions optimized to prevent stalling. 8 | * Enhanced groups 9 | * Groups will now gather resources and engage in combat with other groups. 10 | * The player and ALife can now "hear" noises (firearms, explosions, etc.) 11 | * Note: Actual sounds are planned for a future version. 12 | * Reworked judgement. 13 | * NPCs won't talk as much and judge based on a wider variety of factors. 14 | * Completely rewrote all dialog-related functions. 15 | * An extended version of RawScript is now supported. 16 | * All dialog strings are stored externally in `data\text\dialog.txt`. 17 | * Questions have been reimplemented. 18 | * Faster, more detailed world generation. 19 | * Rewrote damage model. 20 | * The size and material of an item now play a role in damage calculations. 21 | * Better crafting. 22 | * Success determined by skills. 23 | * Groups are no longer tracked by the world. 24 | * Instead, individuals track their interpretations of what the group is. 25 | * NPCs can now learn more details about others and items. 26 | * This information can also become out of date, leading to bad decision making. 27 | * Mouse look 28 | * The mouse can now be used to select items in look mode (`l`). 29 | * Looting of NPCs can be done by standing next to them and pressing `o`. 30 | * Huge FPS boost on all systems. 31 | * Many new items. 32 | * NPCs now make sure their LOS is clear before opening fire. 33 | * Weapons now have multiple firing modes (using `f`). 34 | * Group leaders can now issue attack orders (hit `tab`) 35 | * Added movie mode (recording) (hit `?` to start, again to quit.) 36 | * ALife take more factors into consideration when deciding if they are safe. 37 | * Worlds are initially seeded with one group of a random motive. 38 | * Added Night Terrors. 39 | * Revamped recoil. 40 | * Recoil now depends on the type of ammo used. 41 | * Added the MP5, a mid-range submachine gun. Takes 9x19mm ammo. 42 | * Fast-forward is no longer enabled when the player performs an action. 43 | * A cache now exists for deleted items that are still referenced by ALife. 44 | * This is untested on longer playthroughs. 45 | * Updated map generation. 46 | * Maps can now be as large as 600x600 and contain multiple larger towns/cities. 47 | * Slicing/zoning-related speed-ups. 48 | * New map format that no longer eats memory. 49 | * Added weather (only visual effects for now.) 50 | * Longer paths are now generated in segments. 51 | * Created "views," which allows for easier management of multiple consoles. 52 | * Note: Some legacy code remains and is being phased out. 53 | * Explosives are now more effective. 54 | * Certain items can now be activated (via `a`) 55 | * Added caching layer for faster item retrieval. 56 | * Added caching layer for faster chunk map lookups. 57 | * Rewrote lighting system. 58 | 59 | Bugfixes: 60 | * Forest placement no longer crashes mapgen. 61 | * Numerous out-of-bounds issues solved. 62 | * Fixed Linux-specific crash caused by a frame being drawn outside the console. 63 | 64 | New in version 0.6: 65 | * ALife now use dijkstra maps to better calculate distance to targets 66 | * They will also use dijkstra maps to escape somewhat intelligently 67 | * Intimidation by force 68 | * Can order ALife to give up items 69 | * ALife will surrender under some conditions 70 | * Moved more ALife functionality into rawscript 71 | * Items can now have additional prefixes 72 | * Burnable items now burn and damage entities 73 | * Fixed crash when adding items containing items to inventories 74 | * Camera can now follow non-entities 75 | * Explosions now push entities 76 | * Added caching layer to zone lookups, dijkstra maps, and reference maps 77 | * Inventory/item action menus redesigned for quicker access 78 | * New rules for weapon accuracy 79 | * ALife will now properly find and collect wanted items 80 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /encounters.py: -------------------------------------------------------------------------------- 1 | from globals import * 2 | 3 | import life as lfe 4 | import libtcodpy as tcod 5 | 6 | import language 7 | import alife 8 | import menus 9 | 10 | import logging 11 | 12 | def create_encounter(life, target, context=None): 13 | _encounter = {} 14 | 15 | if life['encounters']: 16 | return None 17 | 18 | if not life['id'] in target['know']: 19 | logging.warning('Encounter: %s does not know %s.' % (' '.join(life['name']), ' '.join(target['name']))) 20 | return False 21 | 22 | target['know'][life['id']]['last_encounter_time'] = WORLD_INFO['ticks'] 23 | _encounter['target'] = target 24 | 25 | _remembered_alife = alife.brain.knows_alife(target, life) 26 | _stance = alife.stances.get_stance_towards(target, life['id']) 27 | _time_since_met = WORLD_INFO['ticks'] - _remembered_alife['met_at_time'] 28 | 29 | _text = [] 30 | _text.append('You see %s.' % ' '.join(target['name'])) 31 | _text.append('_' * 38) 32 | 33 | if alife.brain.has_met_in_person(target, life) and _time_since_met<1000: 34 | _text.append('You just met %s recently.' % target['name'][0]) 35 | 36 | _text.append('He appears to be %s towards you.' % _stance) 37 | _text.append('_' * 38) 38 | _text.append('+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 -------------------------------------------------------------------------------- /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)'+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 -------------------------------------------------------------------------------- /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. -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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]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} -------------------------------------------------------------------------------- /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) -------------------------------------------------------------------------------- /profiles.py: -------------------------------------------------------------------------------- 1 | from globals import DATA_DIR, VERSION 2 | 3 | import logging 4 | import shutil 5 | import os 6 | 7 | 8 | def version_check(): 9 | if not has_reactor3(): 10 | logging.debug('First run. Ignoring version check.') 11 | 12 | return False 13 | 14 | logging.debug('Checking current version against existing worlds...') 15 | 16 | _out_of_date_worlds = [] 17 | _config_directory, _worlds_directory = has_reactor3() 18 | 19 | for world_id in os.listdir(_worlds_directory): 20 | _version_file = os.path.join(_worlds_directory, world_id, 'version.txt') 21 | 22 | if not os.path.exists(_version_file): 23 | _out_of_date_worlds.append(world_id) 24 | 25 | continue 26 | 27 | with open(_version_file, 'r') as version_file: 28 | if not version_file.readline().strip() == VERSION: 29 | _out_of_date_worlds.append(world_id) 30 | 31 | continue 32 | 33 | if _out_of_date_worlds: 34 | for world_id in _out_of_date_worlds: 35 | shutil.rmtree(os.path.join(_worlds_directory, world_id)) 36 | 37 | return False 38 | 39 | return True 40 | 41 | def get_home_directory(): 42 | return os.path.expanduser('~') 43 | 44 | def has_reactor3(): 45 | _config_directory = os.path.join(get_home_directory(),'.config','reactor-3') 46 | _worlds_directory = os.path.join(_config_directory, 'worlds') 47 | 48 | try: 49 | os.makedirs(_config_directory) 50 | logging.info('Created config directory: %s' % _config_directory) 51 | except OSError: 52 | if not os.path.exists(_config_directory): 53 | logging.exception('Could not create config directory at \'%s\'' % _config_directory) 54 | raise Exception('Could not create config directory.') 55 | 56 | try: 57 | os.mkdir(_worlds_directory) 58 | logging.info('Created worlds directory: %s' % _worlds_directory) 59 | return (_config_directory, _worlds_directory) 60 | except OSError: 61 | return (_config_directory, _worlds_directory) 62 | 63 | def get_maps(): 64 | _map_dir = os.path.join(DATA_DIR, 'maps') 65 | _maps = [] 66 | 67 | for (dirpath, dirname, filenames) in os.walk(_map_dir): 68 | _maps.extend(dirname) 69 | 70 | return _maps 71 | 72 | def get_worlds(): 73 | _config_directory, _worlds_directory = has_reactor3() 74 | 75 | _dirs = [] 76 | for (dirpath, dirname, filenames) in os.walk(_worlds_directory): 77 | _dirs.extend(dirname) 78 | break 79 | 80 | return _dirs 81 | 82 | def create_world(): 83 | _config_directory, _worlds_directory = has_reactor3() 84 | 85 | _world_name = str(len(get_worlds())+1) 86 | _world_directory = os.path.join(_worlds_directory, _world_name) 87 | 88 | try: 89 | os.mkdir(_world_directory) 90 | logging.info('Created world: %s' % _world_name) 91 | return _world_name 92 | except OSError: 93 | return False 94 | 95 | def get_world_directory(world): 96 | _config_directory, _worlds_directory = has_reactor3() 97 | _world_directory = os.path.join(_worlds_directory, str(world)) 98 | 99 | return _world_directory -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /render_los.pyx: -------------------------------------------------------------------------------- 1 | from globals import * 2 | 3 | import numbers 4 | import cython 5 | import numpy 6 | import alife 7 | import items 8 | import maps 9 | import time 10 | 11 | VERSION = 1 12 | 13 | @cython.locals(x=cython.int, y=cython.int) 14 | def draw_circle(x,y,size): 15 | if not size>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 | -------------------------------------------------------------------------------- /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 | ) -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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) -------------------------------------------------------------------------------- /tests/M01_data_structure_3d.py: -------------------------------------------------------------------------------- 1 | SHORT_GRASS_TILE = {'id':'short_grass', 2 | 'icon':'.', 3 | 'color':'green', 4 | 'burnable':True, 5 | 'cost':1} 6 | 7 | GRASS_TILE = {'id':'grass', 8 | 'icon':',', 9 | 'color':'green', 10 | 'burnable':True, 11 | 'cost':1} 12 | 13 | TALL_GRASS_TILE = {'id':'tall_grass', 14 | 'icon':';', 15 | 'color':'green', 16 | 'burnable':True, 17 | 'cost':1} 18 | 19 | DIRT_TILE = {'id':'dirt', 20 | 'icon':'.', 21 | 'color':'brown', 22 | 'burnable':False, 23 | 'cost':2} 24 | 25 | TILES = [SHORT_GRASS_TILE,GRASS_TILE,TALL_GRASS_TILE,DIRT_TILE] 26 | MAP = [] 27 | MAP_SIZE = (50,50,5) 28 | 29 | def create_tile(tile): 30 | _ret_tile = {} 31 | _ret_tile['id'] = tile['id'] 32 | 33 | _ret_tile['items'] = [] 34 | _ret_tile['fire'] = 0 35 | 36 | return _ret_tile 37 | 38 | def get_raw_tile(tile): 39 | for _tile in TILES: 40 | if _tile['id'] == tile['id']: 41 | return _tile 42 | 43 | raise Exception 44 | 45 | import random 46 | 47 | for x in range(MAP_SIZE[0]): 48 | _y = [] 49 | for y in range(MAP_SIZE[1]): 50 | _z = [] 51 | for z in range(MAP_SIZE[2]): 52 | if z == 0: 53 | _z.append(create_tile(DIRT_TILE)) 54 | elif z == 1: 55 | _z.append(create_tile(random.choice([GRASS_TILE,SHORT_GRASS_TILE,TALL_GRASS_TILE]))) 56 | else: 57 | _z.append(None) 58 | 59 | _y.append(_z) 60 | MAP.append(_y) 61 | 62 | print 'Printing map' 63 | for x in range(MAP_SIZE[0]): 64 | for y in range(MAP_SIZE[1]): 65 | _top_tile = None 66 | for z in range(MAP_SIZE[2]): 67 | if MAP[x][y][z]: 68 | _top_tile = MAP[x][y][z] 69 | 70 | print get_raw_tile(_top_tile)['icon'], 71 | 72 | print 73 | 74 | print 'Timing map fetching' 75 | import time 76 | _stime = time.time() 77 | for x in range(MAP_SIZE[0]): 78 | for y in range(MAP_SIZE[1]): 79 | for z in range(MAP_SIZE[2]): 80 | if MAP[x][y][z]: 81 | get_raw_tile(MAP[x][y][z]) 82 | print time.time()-_stime 83 | 84 | print MAP[3][3] -------------------------------------------------------------------------------- /tests/M01_data_visualization.py: -------------------------------------------------------------------------------- 1 | from libtcodpy import * 2 | 3 | console_init_root(100, 50, 'M01 - Visualization',renderer=RENDERER_OPENGL) 4 | console_set_custom_font('terminal8x12_gs_tc.png') 5 | sys_set_fps(30) 6 | 7 | SHORT_GRASS_TILE = {'id':'short_grass', 8 | 'icon':'.', 9 | 'color':(light_green,light_lime), 10 | 'burnable':True, 11 | 'cost':1} 12 | 13 | GRASS_TILE = {'id':'grass', 14 | 'icon':',', 15 | 'color':(green,light_chartreuse), 16 | 'burnable':True, 17 | 'cost':1} 18 | 19 | TALL_GRASS_TILE = {'id':'tall_grass', 20 | 'icon':';', 21 | 'color':(dark_green,light_green), 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 | mouse_move(0,0) 46 | 47 | def create_tile(tile): 48 | _ret_tile = {} 49 | _ret_tile['id'] = tile['id'] 50 | 51 | _ret_tile['items'] = [] 52 | _ret_tile['fire'] = 0 53 | 54 | return _ret_tile 55 | 56 | def get_raw_tile(tile): 57 | for _tile in TILES: 58 | if _tile['id'] == tile['id']: 59 | return _tile 60 | 61 | raise Exception 62 | 63 | import random 64 | 65 | for x in range(MAP_SIZE[0]): 66 | _y = [] 67 | for y in range(MAP_SIZE[1]): 68 | _z = [] 69 | for z in range(MAP_SIZE[2]): 70 | if z == 0: 71 | _z.append(create_tile(DIRT_TILE)) 72 | elif z == 1: 73 | _z.append(create_tile(random.choice([GRASS_TILE,SHORT_GRASS_TILE,TALL_GRASS_TILE]))) 74 | else: 75 | _z.append(None) 76 | 77 | _y.append(_z) 78 | MAP.append(_y) 79 | 80 | MAP[3][3][2]=create_tile(GRASS_TILE) 81 | 82 | def get_mouse_input(): 83 | global MOUSE,MOUSE_POS,MOUSE_1_DOWN 84 | 85 | if MOUSE.lbutton: 86 | if not MOUSE_1_DOWN: 87 | MOUSE_1_DOWN = True 88 | else: 89 | MOUSE_1_DOWN = False 90 | 91 | MOUSE_POS = MOUSE.cx*2,MOUSE.cy 92 | 93 | def get_input(): 94 | global KEY,MOUSE 95 | 96 | sys_check_for_event(EVENT_KEY_PRESS|EVENT_MOUSE,KEY,MOUSE) 97 | 98 | get_mouse_input() 99 | 100 | def handle_input(): 101 | if MOUSE_1_DOWN: 102 | print MOUSE_POS 103 | MAP[MOUSE_POS[0]][MOUSE_POS[1]][2] = create_tile(WALL_TILE) 104 | 105 | 106 | TIME_OF_DAY = 6 107 | TIME_OF_DAY_MAX = 16 108 | TIME_OF_DAY_TIMER = 1500 109 | TIME_OF_DAY_TIMER_MAX = 1500 110 | 111 | while not console_is_window_closed(): 112 | get_input() 113 | handle_input() 114 | 115 | LIGHT_MAP = color_gen_map((white, 116 | light_blue, 117 | blue, 118 | light_blue, 119 | light_gray, 120 | white),[0,4,8,12,14,16]) 121 | 122 | for x in range(MAP_SIZE[0]): 123 | for y in range(MAP_SIZE[1]): 124 | _top_tile = None 125 | _top_tile_z = -1 126 | for z in range(MAP_SIZE[2]): 127 | if MAP[x][y][z]: 128 | _top_tile = MAP[x][y][z] 129 | _top_tile_z = z 130 | 131 | _tile = get_raw_tile(_top_tile) 132 | 133 | console_set_char_background(None, x, y, _tile['color'][1]-LIGHT_MAP[TIME_OF_DAY]) 134 | console_set_char_foreground(None, x, y, _tile['color'][0]-LIGHT_MAP[TIME_OF_DAY]) 135 | console_set_char(None,x,y,_tile['icon']) 136 | 137 | if TIME_OF_DAY_TIMER: 138 | TIME_OF_DAY_TIMER-=1 139 | else: 140 | if TIME_OF_DAY0: 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] ' 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 | -------------------------------------------------------------------------------- /tests/M06_Melee.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | COMBAT_MOVES = {'punch': {'counters': ['deflect', 'dodge']}} 4 | 5 | CHAR = {'stance': 'stand', 6 | 'next_stance': None, 7 | 'stances': {'tackle': 7, 8 | 'leap': 6, 9 | 'roll': 5, 10 | 'prone': 5, 11 | 'duck': 4, 12 | 'kick': 2, 13 | 'punch': 2, 14 | 'dodge': 2, 15 | 'deflect': 1, 16 | 'parry': 0, 17 | 'grapple': 0, 18 | 'stand': 0, 19 | 'off-balance': -1, 20 | 'trip': -1}} 21 | 22 | p1 = CHAR.copy() 23 | p2 = CHAR.copy() 24 | 25 | LIFE = [p1, p2] 26 | 27 | p1['name'] = 'p1' 28 | p1['next_stance'] = {'delay': 0, 'stance': None, 'towards': None, 'forced': False} 29 | p2['name'] = 'p2' 30 | p2['next_stance'] = {'delay': 0, 'stance': None, 'towards': None, 'forced': False} 31 | 32 | def get_stance_score(p, stance): 33 | _current_score = p['stances'][p['stance']] 34 | _next_score = p['stances'][stance] 35 | 36 | return abs(_current_score-_next_score) 37 | 38 | def assume_stance(p, stance, towards=None): 39 | if p['next_stance']['forced']: 40 | return False 41 | 42 | p['next_stance']['delay'] = get_stance_score(p, stance) 43 | p['next_stance']['stance'] = stance 44 | p['next_stance']['towards'] = towards 45 | p['next_stance']['forced'] = False 46 | 47 | print p['name'], 'begins', p['next_stance']['stance'], '(%s' % p['next_stance']['delay']+')' 48 | return True 49 | 50 | def force_stance(p, stance): 51 | p['next_stance']['delay'] = get_stance_score(p, stance) 52 | p['stance'] = stance 53 | 54 | #TODO: Randomly regain balance or fall over 55 | p['next_stance']['stance'] = 'stand' 56 | 57 | p['next_stance']['forced'] = True 58 | 59 | print p['name'], 'forced into', p['stance'], '(%s' % p['next_stance']['delay']+')' 60 | 61 | def examine_possible_moves(p, targets): 62 | #TODO: Cancel move? 63 | if p['next_stance']['stance']: 64 | return False 65 | 66 | for target in targets: 67 | if target == p: 68 | continue 69 | 70 | _next_stance = target['next_stance']['stance'] 71 | if _next_stance and _next_stance in COMBAT_MOVES and not p['stance'] in COMBAT_MOVES[_next_stance]['counters']: 72 | assume_stance(p, COMBAT_MOVES[_next_stance]['counters'][0], towards=target) 73 | return False 74 | elif not _next_stance or not target['stance'] in COMBAT_MOVES: 75 | assume_stance(p, random.choice(COMBAT_MOVES.keys()), towards=target) 76 | return True 77 | 78 | def tick(p): 79 | if p['next_stance']['delay']: 80 | p['next_stance']['delay'] -= 1 81 | 82 | if p['next_stance']['delay']: 83 | print p['name'], 'waiting:', p['next_stance']['stance'], '(%s' % p['next_stance']['delay']+')' 84 | return False 85 | 86 | if p['next_stance']['stance']: 87 | print p['name'], p['stance'], '->', 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 -------------------------------------------------------------------------------- /tests/M06_fov.py: -------------------------------------------------------------------------------- 1 | #Recursive Shadowcasting 2 | #Implemented in Python by flags 3 | #Ported from C++: http://roguebasin.roguelikedevelopment.org/index.php?title=C%2B%2B_shadowcasting_implementation 4 | #Original implementation by bjorn.bergstrom@roguelikedevelopment.org 5 | #Article: http://roguebasin.roguelikedevelopment.org/index.php?title=FOV_using_recursive_shadowcasting 6 | 7 | import numpy 8 | import time 9 | 10 | MULT = [[1, 0, 0, -1, -1, 0, 0, 1], 11 | [0, 1, -1, 0, 0, -1, 1, 0], 12 | [0, 1, 1, 0, 0, -1, -1, 0], 13 | [1, 0, 0, 1, -1, 0, 0, -1]] 14 | 15 | MAP_SIZE = [40, 40] 16 | BUFFER_MOD = .25 17 | WORLD_INFO = {} 18 | WORLD_INFO['map'] = [] 19 | 20 | for x in range(40): 21 | _y = [] 22 | for y in range(40): 23 | _y.append(0) 24 | 25 | WORLD_INFO['map'].append(_y) 26 | 27 | for i in range(0, 40): 28 | if i%3: 29 | WORLD_INFO['map'][10][i] = 1 30 | #if i%2: 31 | # WORLD_INFO['map'][7][i] = 1 32 | #WORLD_INFO['map'][i][5] = 1 33 | 34 | #WORLD_INFO['map'][21][20] = 1 35 | 36 | def draw(los_map): 37 | for y in range(40): 38 | _x = '' 39 | for x in range(40): 40 | if (x, y) == (20, 20): 41 | _x+='X' 42 | elif WORLD_INFO['map'][x][y]: 43 | _x+='#' 44 | else: 45 | if los_map[x, y]: 46 | _x+=str(int(los_map[x, y])) 47 | else: 48 | _x+=' ' 49 | 50 | print _x 51 | 52 | def light(los_map, world_pos, size, row, start_slope, end_slope, xx, xy, yx, yy): 53 | if start_slope < end_slope: 54 | return False 55 | 56 | x, y = world_pos 57 | 58 | _next_start_slope = start_slope 59 | 60 | for i in range(row, size): 61 | _blocked = False 62 | 63 | _d_x = -i 64 | _d_y = -i 65 | while _d_x <= 0: 66 | _l_slope = (_d_x - 0.5) / (_d_y + 0.5) 67 | _r_slope = (_d_x + 0.5) / (_d_y - 0.5) 68 | 69 | if start_slope < _r_slope: 70 | _d_x += 1 71 | continue 72 | elif end_slope>_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) -------------------------------------------------------------------------------- /tests/M07_recoil.py: -------------------------------------------------------------------------------- 1 | import random 2 | import numpy 3 | import json 4 | import math 5 | 6 | 7 | _weapon = {'recoil': 0.3, 8 | 'accuracy': 0.9} 9 | stance_mod = 1.0 10 | aim_difficulty = 1.8 11 | firearms_skill_mod = 0.55 12 | hit_certainty_mod = 0.6 13 | bullets = 10 14 | simulaton_ticks = 8 15 | 16 | def load_weapon(weapon_file): 17 | with open(weapon_file, 'r') as wfile: 18 | _weapon.update(json.loads(''.join(wfile.readlines()))) 19 | 20 | def velocity(direction, speed): 21 | rad = direction*(math.pi/180) 22 | velocity = numpy.multiply(numpy.array([math.cos(rad), math.sin(rad)]), speed) 23 | 24 | return [velocity[0], -velocity[1], 0] 25 | 26 | def clip(number,start,end): 27 | return max(start, min(number, end)) 28 | 29 | def bullet_trajectory(bullet_pos, bullet_direction, ticks=simulaton_ticks): 30 | bullet_velocity = velocity(bullet_direction, 5) 31 | 32 | for i in range(ticks): 33 | bullet_pos[0] += bullet_velocity[0] 34 | bullet_pos[1] += bullet_velocity[1] 35 | 36 | return bullet_pos 37 | 38 | def simulate(firearms_skill, recoil=0.0): 39 | recoil = float(recoil) 40 | bullet_direction = 0 41 | bullet_pos = [0, 0] 42 | bullet_velocity = [0, 0] 43 | _deviations = [] 44 | _hits = 0 45 | 46 | for i in range(bullets): 47 | bullet_velocity = [0, 0] 48 | bullet_pos = [0, 0] 49 | bullet_deviation = (1-_weapon['accuracy'])+recoil 50 | deviation_mod = aim_difficulty*(1-((firearms_skill/10.0)*firearms_skill_mod)) 51 | deviation = (bullet_deviation*aim_difficulty)*deviation_mod 52 | bullet_direction = random.uniform(-deviation, deviation) 53 | 54 | ######################## 55 | ## ENABLE FOR RELEASE ## 56 | ######################## 57 | #recoil = clip(recoil+(_weapon['recoil']*stance_mod), 0.0, 1.0) 58 | 59 | _end_pos = bullet_trajectory(bullet_pos, bullet_direction) 60 | _direction_deviation = abs(bullet_direction) 61 | _target_hit_certainty = _direction_deviation/hit_certainty_mod 62 | _hits += _target_hit_certainty<=1 63 | _deviations.append(_direction_deviation) 64 | 65 | #print 'Deviations: mean=%0.4f, min=%0.4f, max=%0.4f' % (sum(_deviations)/len(_deviations), min(_deviations), max(_deviations)) 66 | #print 'Accuracy:', _hits/float(bullets), '\n' 67 | 68 | #print 'Trajectory deviation: %0.4f' % _direction_deviation 69 | #print 'Non-certainty: %s (hit=%s)' % (_target_hit_certainty, _target_hit_certainty<=1) 70 | #print 'Limb accuracy: %0.4f' % (1-(_target_hit_certainty/1.0))*int(_target_hit_certainty<=1) 71 | #print 72 | #print 'dir', bullet_direction, 'dev', bullet_deviation, 'rec', recoil 73 | 74 | return _hits/float(bullets), _deviations 75 | 76 | _path = '/home/luke/code/Reactor-3/data/items/glock.json' 77 | load_weapon(_path) 78 | 79 | _accuracies = [] 80 | 81 | for i in range(1, 10+1): 82 | print 'Skill: %s\t' % i 83 | 84 | for r in range(0, 5): 85 | recoil = r/5.0 86 | _accuracy, _deviations = simulate(i, recoil=recoil) 87 | _accuracies.append(_accuracy) 88 | 89 | print '\trecoil=%0.4f, accuracy=%0.4f, avg. deviation=%0.4f' % (recoil, _accuracy, sum(_deviations)/float(len(_deviations))) 90 | 91 | print _weapon['name'], 'accuracy:', sum(_accuracies)/float(len(_accuracies)) 92 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 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 | -------------------------------------------------------------------------------- /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) -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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]) -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /tools/templates/camp.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | ReactorWatch - Camp {{ camp.id }} 4 | 5 | 6 |

Camp {{ camp.id }}

7 | 16 | 17 | -------------------------------------------------------------------------------- /tools/templates/group.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | ReactorWatch - Group {{ group_id }} 4 | 5 | 6 |

Group {{ group_id }}

7 | 17 | 18 | -------------------------------------------------------------------------------- /tools/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | ReactorWatch - Debug 4 | 5 | 6 |

Stats

7 |
  • Memories: {{ stats.total_memories }}
  • 8 |
  • Active life: {{ stats.active_life }}
  • 9 |
  • Groups: {{ groups|count }}
  • 10 |
  • Camps: {{ camps|count }}
  • 11 |
12 |

Life

13 |
    14 | {% for id in life %} 15 |
  • {{ id }}
  • 16 | {% endfor %} 17 |
18 |

Groups

19 | 24 |

Camps

25 |
    26 | {% for camp in camps %} 27 |
  • {{ camp }}
  • 28 | {% endfor %} 29 |
30 | 31 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /tools/templates/memory.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | ReactorWatch - Memory {{ life_id }} 4 | 5 | 6 |

Life #{{ life_id }}

7 |
    8 |
  • Memories ({{ memories|count }}):
    9 | {% for entry in memories %} 10 | {{ entry.text }}
    11 |
      12 | {% for key in entry %} 13 |
    • {{ key }}: {{ entry[key] }}
    • 14 | {% endfor %} 15 |
    16 | {% endfor %} 17 |
  • 18 |
19 | 20 | --------------------------------------------------------------------------------