From df7832aa4342aefb23a95279b375676da00ad676 Mon Sep 17 00:00:00 2001 From: CabinetOnFire Date: Fri, 17 Jul 2026 20:49:48 +0200 Subject: [PATCH] Replaced our NPC AI with Behavior Trees. (#96628) This PR replaces our current NPC AI with a [behavior tree system](https://en.wikipedia.org/wiki/Behavior_tree_(artificial_intelligence,_robotics_and_control)). Behavior trees are a common way of creating AI in which you place nodes in a tree structure to define what actions an AI should take. AI controllers defined a list of /datum/ai_planning_subtree types in behavior_nodes. Each subtree was a self-contained unit that could call queue_behavior() to fire off /datum/ai_behavior actions. The controller iterated subtrees in order, each one deciding independently whether to queue something and deciding whether the next subtree would run. This has a few issues: 1. There's no real structure; you are just defining a list of things to try in order. 2. There was a loooot of subtrees that were basically the same as another but with some slight modification 3. It was hard to understand. Controllers now define a single json file describing a tree of nodes. The tree is composed of structural composites: Sequence - do A, then B, then C (and so on) Selector - try A, if it fails try B, then C (and so on) Parallel - run A and B simultaneously, with configurable failure/success policies and or looping behavior Subplan - loop a child continiously Along that we also have "Decorators". These are nodes that basically check a condition (E.g.; do we have a combat target). These decorators can be used to gate behavior and are re-useable across behavior trees. They also have a concept known as "Observers". Which lets them cancel lower priority behavior in case their condition changes (Which we check whenever a signal fires that fits that specific decorator). This makes the AI much more responsive to change in environment. For behaviors, we still use the ai_behavior datums. These are the actual behaviors such as "Move to X", "Attack X". The only major change is that these can no longer sleep() since they now run in the ai_controller. Lastly, we now also have subtrees, except now they are essentially pieces of behavior tree that can be re-used, or even overriden at runtime or as a variable. Allowing for making modular AI made out of several smaller trees. You can set variables on these nodes directly via the extension (see below), which should reduce the need to make subtypes of behaviors by a lot. All of these vars are saved on the JSON and will be applied at runtime. If you are using subtrees, you can also assign "bindings" to these variables, which will allow instances of the subtree to override those variables. Since a tree structure with variables becomes hard to parse in a JSON, I've made a VSCode extension to edit these JSONs: https://marketplace.visualstudio.com/items?itemName=BehaviorTreeG.behaviortreeg https://github.com/CabinetOnFire/BehaviorTreeG image This extension allows you to edit the behavior tree JSONs, and browse through all the behaviors/decorators/subtrees we have If you'd like more info on how to build these AI check out the learn_ai.md. I will also make a tutorial to go over more depth on what the system offers because I kind of suck at doing technical write-ups. Targetting has been changed to. I've made a new acquire_targets behavior that takes a target_source (what am I targetting) and targetting_strategy (what does the candidate need to fulfill to be considered a target). This allows us to make composites targetting combinations to reduce the amount of specific find_and_set esque behaviors we had before. Not everything is ported to this system but that would be a longer term goal. I've added a new build_bt script that converts all the behavior tree JSONs into compiled versions. Why is this needed? Because I wanted to keep using defines in behavior trees, so we need a way to convert this into literal values before we send it to DM. This script runs on compile and should also run in CI (If I didn't fuck that up!). This saves to a new build/ folder. I've ported every single AI in the game to this system (except raptors, Kobsa is working on those so should be in soon!), so I do expect some bugs to come out of this. But I also fixed some issues that have probably been in the game for a long time such as: - Fixed penguins being unable to fish - Fixed bileworms not being able to devour people - Fixes goldgrubs not grubbing gold (they could not mine!) - Lizards actually eat food they find Either way, I'd reccomend a long TM on this. 1. (Hopefully) a better development experience for making AI 2. Less copy-paste for behaviors, we should be able to re-use more pieces to make behavior 3. Behavior trees is a more common pattern in making AI, so it should be easier to find resources to find out how to do things. :cl: CabinetOnFire, Iamgoofball, SmartKar, Ben10omintrix refactor: Replaces our AI system with behavior trees, porting all datum/ai to it /:cl: I will add this PR with more details down the line. I think I got the big picture but its a big PR, so sorry if I missed something important. --------- Co-authored-by: Iamgoofball Co-authored-by: SmArtKar <44720187+SmArtKar@users.noreply.github.com> Co-authored-by: Ghom <42542238+Ghommie@users.noreply.github.com> Co-authored-by: Ben10Omintrix <138636438+Ben10Omintrix@users.noreply.github.com> Co-authored-by: SyncIt21 <110812394+SyncIt21@users.noreply.github.com> --- .github/workflows/check_bt_compiled.yml | 24 + .vscode/extensions.json | 3 +- .vscode/tasks.json | 9 +- .../ai/babies/make_babies.bt.compiled.json | 1 + .../datums/ai/bane/bane.bt.compiled.json | 1 + .../consider_venting.bt.compiled.json | 1 + .../escape_captivity.bt.compiled.json | 1 + ...escape_captivity_pacifist.bt.compiled.json | 1 + .../find_paper_and_write.bt.compiled.json | 1 + .../go_for_swim.bt.compiled.json | 1 + .../random_speech_loop.bt.compiled.json | 1 + .../run_away_from_target.bt.compiled.json | 1 + ...from_target_run_and_shoot.bt.compiled.json | 1 + .../skittish_brawler_combat.bt.compiled.json | 1 + .../tip_reaction.bt.compiled.json | 1 + .../pet_command_attack.bt.compiled.json | 1 + .../pet_command_attack_dog.bt.compiled.json | 1 + ...et_command_attack_minebot.bt.compiled.json | 1 + ..._attack_ranged_glockroach.bt.compiled.json | 1 + .../pet_command_beehive.bt.compiled.json | 1 + .../pet_command_breed.bt.compiled.json | 1 + .../pet_command_fetch.bt.compiled.json | 1 + .../pet_command_fish.bt.compiled.json | 1 + .../pet_command_follow.bt.compiled.json | 1 + .../pet_command_mine_walls.bt.compiled.json | 1 + .../pet_command_move_to.bt.compiled.json | 1 + .../pet_command_play_dead.bt.compiled.json | 1 + ...pet_command_protect_owner.bt.compiled.json | 1 + ...t_owner_ranged_glockroach.bt.compiled.json | 1 + .../pet_command_scatter.bt.compiled.json | 1 + .../pet_command_stay.bt.compiled.json | 1 + .../pet_command_swirl.bt.compiled.json | 1 + ..._command_targeted_ability.bt.compiled.json | 1 + ...ommand_untargeted_ability.bt.compiled.json | 1 + .../simple_ability.bt.compiled.json | 1 + .../simple_ability_combat.bt.compiled.json | 1 + .../simple_ability_melee.bt.compiled.json | 1 + ...mple_ability_melee_combat.bt.compiled.json | 1 + .../simple_ability_ranged.bt.compiled.json | 1 + ...ple_ability_ranged_combat.bt.compiled.json | 1 + .../simple_ability_retaliate.bt.compiled.json | 1 + ..._ability_retaliate_combat.bt.compiled.json | 1 + .../simple_capricious.bt.compiled.json | 1 + .../simple_capricious_combat.bt.compiled.json | 1 + .../simple_fearful.bt.compiled.json | 1 + .../simple_fearful_combat.bt.compiled.json | 1 + .../basic_mobs/simple_goon.bt.compiled.json | 1 + .../simple_hostile.bt.compiled.json | 1 + .../simple_hostile_combat.bt.compiled.json | 1 + ...ile_combat_with_retaliate.bt.compiled.json | 1 + .../simple_hostile_obstacles.bt.compiled.json | 1 + ..._hostile_obstacles_combat.bt.compiled.json | 1 + .../basic_mobs/simple_ranged.bt.compiled.json | 1 + .../simple_ranged_combat.bt.compiled.json | 1 + .../simple_ranged_retaliate.bt.compiled.json | 1 + ...e_ranged_retaliate_combat.bt.compiled.json | 1 + .../simple_retaliate.bt.compiled.json | 1 + .../simple_retaliate_combat.bt.compiled.json | 1 + .../simple_skirmisher.bt.compiled.json | 1 + .../simple_skirmisher_combat.bt.compiled.json | 1 + .../simple_skittish.bt.compiled.json | 1 + .../simple_skittish_combat.bt.compiled.json | 1 + .../ai/basic_mobs/talk.bt.compiled.json | 1 + .../ai/bots/bot_patrol.bt.compiled.json | 1 + .../bot_respond_to_summon.bt.compiled.json | 1 + .../bot_salute_authority.bt.compiled.json | 1 + .../datums/ai/cursed/cursed.bt.compiled.json | 1 + .../datums/ai/dog/dog.bt.compiled.json | 1 + .../datums/ai/dog/dog_corgi.bt.compiled.json | 1 + .../ai/dog/dog_harassment.bt.compiled.json | 1 + .../datums/ai/generic_hunger.bt.compiled.json | 1 + .../generic_play_instrument.bt.compiled.json | 1 + .../basic_find_target.bt.compiled.json | 1 + .../capricious_pick_target.bt.compiled.json | 1 + .../climb_tree.bt.compiled.json | 1 + .../find_food.bt.compiled.json | 1 + .../find_partner.bt.compiled.json | 1 + .../find_stealable_object.bt.compiled.json | 1 + .../forage_and_retaliate.bt.compiled.json | 1 + .../move_to_and_eat.bt.compiled.json | 1 + .../move_to_and_hunt.bt.compiled.json | 1 + .../move_to_reinforce.bt.compiled.json | 1 + .../pick_retaliate_target.bt.compiled.json | 1 + .../random_walk.bt.compiled.json | 1 + .../skittish_and_speak.bt.compiled.json | 1 + .../steal_and_flee.bt.compiled.json | 1 + .../ai/hauntium/haunted.bt.compiled.json | 1 + .../datums/ai/monkey/monkey.bt.compiled.json | 1 + .../ai/monkey/monkey_combat.bt.compiled.json | 1 + .../monkey_find_weapon.bt.compiled.json | 1 + .../monkey/monkey_serve_food.bt.compiled.json | 1 + .../monkey_shenanigans.bt.compiled.json | 1 + .../vending_machine.bt.compiled.json | 1 + .../robot_customer.bt.compiled.json | 1 + .../antagonists/netguardian.bt.compiled.json | 1 + .../domains/crewman.bt.compiled.json | 1 + .../domains/crewman_hostile.bt.compiled.json | 1 + .../crewman_hostile_ranged.bt.compiled.json | 1 + .../domains/crewman_ranged.bt.compiled.json | 1 + .../living/basic/alien/alien.bt.compiled.json | 1 + .../basic/alien/basic_alien.bt.compiled.json | 1 + .../living/basic/alien/drone.bt.compiled.json | 1 + .../alien/lay_alien_egg.bt.compiled.json | 1 + .../alien/melee_alien_combat.bt.compiled.json | 1 + .../alien/plant_alien_weeds.bt.compiled.json | 1 + .../living/basic/alien/queen.bt.compiled.json | 1 + .../ranged_alien_combat.bt.compiled.json | 1 + .../basic/alien/sentinel.bt.compiled.json | 1 + .../blob_minions/blob_spore.bt.compiled.json | 1 + .../blob_minions/blob_zombie.bt.compiled.json | 1 + .../blob_minions/blobbernaut.bt.compiled.json | 1 + .../blood_drunk_miner.bt.compiled.json | 1 + .../blood_drunk_miner_combat.bt.compiled.json | 1 + .../boss/thing/thing_aoe.bt.compiled.json | 1 + .../boss/thing/thing_boss.bt.compiled.json | 1 + .../boss/thing/thing_melee.bt.compiled.json | 1 + .../living/basic/bots/bot.bt.compiled.json | 1 + .../clean_pet_target.bt.compiled.json | 1 + .../bots/cleanbot/cleanbot.bt.compiled.json | 1 + .../living/basic/bots/dedbot.bt.compiled.json | 1 + .../basic/bots/ed209/ed209.bt.compiled.json | 1 + .../ed209/ed209_syndicate.bt.compiled.json | 1 + .../bots/firebot/firebot.bt.compiled.json | 1 + .../bots/honkbots/honkbot.bt.compiled.json | 1 + .../honkbots/honkbot_slip.bt.compiled.json | 1 + .../hygienebot/hygienebot.bt.compiled.json | 1 + .../basic/bots/medbot/medbot.bt.compiled.json | 1 + ...ot_find_and_announce_crit.bt.compiled.json | 1 + .../medbot_treat_patient.bt.compiled.json | 1 + .../bots/mulebot/mulebot.bt.compiled.json | 1 + .../bots/repairbot/repairbot.bt.compiled.json | 1 + .../repairbot_emagged.bt.compiled.json | 1 + .../repairbot_find_target.bt.compiled.json | 1 + .../repairbot_repair_target.bt.compiled.json | 1 + .../basic/bots/secbot/secbot.bt.compiled.json | 1 + .../bots/vibebot/vibebot.bt.compiled.json | 1 + .../living/basic/clown/clown.bt.compiled.json | 1 + .../constructs/artificer.bt.compiled.json | 1 + .../constructs/juggernaut.bt.compiled.json | 1 + .../cult/constructs/proteon.bt.compiled.json | 1 + .../cult/constructs/wraith.bt.compiled.json | 1 + .../basic/cytology/vatbeast.bt.compiled.json | 1 + .../farm_animals/bee/bee.bt.compiled.json | 1 + .../bee/find_hive.bt.compiled.json | 1 + .../bee/pollinate_target.bt.compiled.json | 1 + .../bee/queen_bee.bt.compiled.json | 1 + .../transition_hive_status.bt.compiled.json | 1 + .../chicken/chick.bt.compiled.json | 1 + .../chicken/chicken.bt.compiled.json | 1 + .../farm_animals/cow/cow.bt.compiled.json | 1 + .../cow/cow_moonicorn.bt.compiled.json | 1 + .../cow/cow_wisdom.bt.compiled.json | 1 + .../farm_animals/deer/deer.bt.compiled.json | 1 + .../farm_animals/goat/goat.bt.compiled.json | 1 + .../forage_for_goose_food.bt.compiled.json | 1 + .../farm_animals/goose/goose.bt.compiled.json | 1 + .../goose/goose_calm.bt.compiled.json | 1 + .../gorilla/gorilla.bt.compiled.json | 1 + .../basic/farm_animals/pig.bt.compiled.json | 1 + .../basic/farm_animals/pony.bt.compiled.json | 1 + .../farm_animals/rabbit.bt.compiled.json | 1 + .../basic/farm_animals/sheep.bt.compiled.json | 1 + .../basic/festivus_pole.bt.compiled.json | 1 + .../heretic/raw_prophet.bt.compiled.json | 1 + .../heretic/rust_walker.bt.compiled.json | 1 + .../basic/heretic/stalker.bt.compiled.json | 1 + .../basic/heretic/star_gazer.bt.compiled.json | 1 + .../ice_demon/ice_demon.bt.compiled.json | 1 + .../ice_demon_afterimage.bt.compiled.json | 1 + .../ice_demon_combat.bt.compiled.json | 1 + .../ice_demon_flee_from_fire.bt.compiled.json | 1 + .../ice_whelp/ice_whelp.bt.compiled.json | 1 + .../icemoon/polar_bear/polar.bt.compiled.json | 1 + .../basic/icemoon/wolf/wolf.bt.compiled.json | 1 + .../basic/illusion/escape.bt.compiled.json | 1 + .../basic/illusion/retaliate.bt.compiled.json | 1 + .../basic/jungle/human_trap.bt.compiled.json | 1 + .../jungle/leaper/leaper.bt.compiled.json | 1 + .../mega_arachnid.bt.compiled.json | 1 + .../mega_arachnid_combat.bt.compiled.json | 1 + .../jungle/seedling/seedling.bt.compiled.json | 1 + .../seedling/seedling_meanie.bt.compiled.json | 1 + .../basilisk/basilisk.bt.compiled.json | 1 + .../bileworm/bileworm.bt.compiled.json | 1 + .../brimdemon/brimdemon.bt.compiled.json | 1 + .../goldgrub/babygrub.bt.compiled.json | 1 + .../goldgrub/goldgrub.bt.compiled.json | 1 + .../goldgrub/grub_eat_target.bt.compiled.json | 1 + .../lavaland/goliath/goliath.bt.compiled.json | 1 + .../gutlunch_baby.bt.compiled.json | 1 + .../gutlunch_milk.bt.compiled.json | 1 + .../gutlunch_warrior.bt.compiled.json | 1 + .../hivelord/hivelord.bt.compiled.json | 1 + .../lavaland/legion/legion.bt.compiled.json | 1 + .../legion/legion_brood.bt.compiled.json | 1 + .../legion/legion_monkey.bt.compiled.json | 1 + .../lobstrosity/lobstrosity.bt.compiled.json | 1 + .../lobstrosity_calm.bt.compiled.json | 1 + .../lobstrosity_capricious.bt.compiled.json | 1 + .../basic/lavaland/mook/bard.bt.compiled.json | 1 + .../mook/bard_find_targets.bt.compiled.json | 1 + .../mook/bard_play_music.bt.compiled.json | 1 + .../mook/chief_find_targets.bt.compiled.json | 1 + .../chief_issue_commands.bt.compiled.json | 1 + .../chief_manage_village.bt.compiled.json | 1 + .../generic_mook_behavior.bt.compiled.json | 1 + .../lavaland/mook/go_mining.bt.compiled.json | 1 + .../mook/heal_injured.bt.compiled.json | 1 + .../basic/lavaland/mook/mook.bt.compiled.json | 1 + .../lavaland/mook/support.bt.compiled.json | 1 + .../support_find_targets.bt.compiled.json | 1 + .../mook/tribal_chief.bt.compiled.json | 1 + .../mook/worker_find_targets.bt.compiled.json | 1 + .../node_drone/node_drone.bt.compiled.json | 1 + .../raptor/baby_raptor.bt.compiled.json | 1 + .../raptor/care_for_young.bt.compiled.json | 1 + .../raptor/raptor_ai.bt.compiled.json | 1 + .../raptor/raptor_find_food.bt.compiled.json | 1 + .../raptor/raptor_flee.bt.compiled.json | 1 + .../raptor_heal_injured.bt.compiled.json | 1 + .../raptor_play_with_owner.bt.compiled.json | 1 + .../raptor/raptor_trough.bt.compiled.json | 1 + .../lavaland/tendril/tendril.bt.compiled.json | 1 + .../lavaland/watcher/watcher.bt.compiled.json | 1 + .../basic/minebots/minebot.bt.compiled.json | 1 + .../minebots/minebot_combat.bt.compiled.json | 1 + .../minebots/minebot_mining.bt.compiled.json | 1 + .../basic/pets/cat/cat.bt.compiled.json | 1 + .../basic/pets/cat/cat_bread.bt.compiled.json | 1 + .../basic/pets/cat/cat_cake.bt.compiled.json | 1 + .../cat/cat_decorate_donuts.bt.compiled.json | 1 + .../pets/cat/cat_find_food.bt.compiled.json | 1 + .../pets/cat/cat_haul_food.bt.compiled.json | 1 + .../pets/cat/cat_hunt_mice.bt.compiled.json | 1 + .../cat/cat_reside_in_home.bt.compiled.json | 1 + .../cat/cat_turn_off_stove.bt.compiled.json | 1 + .../basic/pets/cat/kitten.bt.compiled.json | 1 + .../basic/pets/dog/guarddog.bt.compiled.json | 1 + .../living/basic/pets/fox.bt.compiled.json | 1 + .../basic/pets/fox_docile.bt.compiled.json | 1 + .../pets/gondolas/gondola.bt.compiled.json | 1 + .../basic/pets/orbie/orbie.bt.compiled.json | 1 + .../parrot/parrot_ai/parrot.bt.compiled.json | 1 + .../parrot_ai/parrot_ghost.bt.compiled.json | 1 + .../parrot_ai/parrot_hoard.bt.compiled.json | 1 + .../parrot_ai/perching.bt.compiled.json | 1 + .../pets/penguin/penguin.bt.compiled.json | 1 + .../penguin/penguin_baby.bt.compiled.json | 1 + .../pets/pet_cult/pet_cult.bt.compiled.json | 1 + .../living/basic/pets/sloth.bt.compiled.json | 1 + .../basic/revolutionary.bt.compiled.json | 1 + .../cybersun_ai_core.bt.compiled.json | 1 + .../dark_wizard.bt.compiled.json | 1 + .../ruin_defender/fleshblob.bt.compiled.json | 1 + .../living_floor.bt.compiled.json | 1 + .../ruin_defender/mad_piano.bt.compiled.json | 1 + .../mimic/mimic_animator.bt.compiled.json | 1 + .../mimic/mimic_copy.bt.compiled.json | 1 + .../mimic/mimic_crate.bt.compiled.json | 1 + .../mimic/mimic_gun.bt.compiled.json | 1 + .../ruin_defender/skeleton.bt.compiled.json | 1 + .../ruin_defender/stickman.bt.compiled.json | 1 + .../stickman_ranged.bt.compiled.json | 1 + .../wizard/wizard.bt.compiled.json | 1 + .../ruin_defender/zombie.bt.compiled.json | 1 + .../zombie_stupid.bt.compiled.json | 1 + .../pet_command_attack_slime.bt.compiled.json | 1 + .../basic/slime/ai/slime.bt.compiled.json | 1 + .../basic/snails/snail.bt.compiled.json | 1 + .../basic/space_fauna/ant.bt.compiled.json | 1 + .../space_fauna/bear/bear.bt.compiled.json | 1 + .../carp/basic_carp_tree.bt.compiled.json | 1 + .../space_fauna/carp/carp.bt.compiled.json | 1 + .../carp/carp_combat.bt.compiled.json | 1 + .../carp/carp_flee.bt.compiled.json | 1 + .../carp/carp_migration.bt.compiled.json | 1 + .../carp/carp_passive.bt.compiled.json | 1 + .../carp_passive_selection.bt.compiled.json | 1 + .../carp/carp_pet.bt.compiled.json | 1 + .../carp/carp_ranged.bt.compiled.json | 1 + .../carp_retaliate_selection.bt.compiled.json | 1 + .../carp_target_selection.bt.compiled.json | 1 + .../cat_butcherer.bt.compiled.json | 1 + .../changeling/headslug.bt.compiled.json | 1 + .../eyeball/eyeball.bt.compiled.json | 1 + .../space_fauna/faithless.bt.compiled.json | 1 + .../space_fauna/garden_gnome.bt.compiled.json | 1 + .../basic/space_fauna/ghost.bt.compiled.json | 1 + .../hivebot/hivebot.bt.compiled.json | 1 + .../hivebot/hivebot_mechanic.bt.compiled.json | 1 + .../hivebot/hivebot_ranged.bt.compiled.json | 1 + .../hivebot_ranged_rapid.bt.compiled.json | 1 + .../relay_to_hive_partner.bt.compiled.json | 1 + .../killer_tomato.bt.compiled.json | 1 + .../space_fauna/lightgeist.bt.compiled.json | 1 + .../meteor_heart.bt.compiled.json | 1 + .../basic/space_fauna/morph.bt.compiled.json | 1 + .../space_fauna/mushroom.bt.compiled.json | 1 + .../paper_wizard.bt.compiled.json | 1 + .../regal_rat/regal_rat.bt.compiled.json | 1 + .../basic/space_fauna/roro.bt.compiled.json | 1 + .../space_fauna/snake/banded.bt.compiled.json | 1 + .../space_fauna/snake/snake.bt.compiled.json | 1 + .../space_fauna/spaceman.bt.compiled.json | 1 + .../giant_spider.bt.compiled.json | 1 + .../giant_spider_pest.bt.compiled.json | 1 + .../giant_spider_retaliate.bt.compiled.json | 1 + .../giant_spider_weak.bt.compiled.json | 1 + .../spiderlings/spiderling.bt.compiled.json | 1 + .../young_spider.bt.compiled.json | 1 + .../statue/stares_at_people.bt.compiled.json | 1 + .../statue/statue.bt.compiled.json | 1 + .../suspicious_mannequin.bt.compiled.json | 1 + .../supermatter_spider.bt.compiled.json | 1 + .../wumborian_fugu.bt.compiled.json | 1 + .../basic/stoats/stoat.bt.compiled.json | 1 + .../basic/trader/trader.bt.compiled.json | 1 + .../mob/living/basic/tree.bt.compiled.json | 1 + .../basic/trooper/burst.bt.compiled.json | 1 + .../basic/trooper/peaceful.bt.compiled.json | 1 + .../trooper/peaceful_burst.bt.compiled.json | 1 + .../basic/trooper/ranged.bt.compiled.json | 1 + .../basic/trooper/shotgunner.bt.compiled.json | 1 + .../basic/trooper/trooper.bt.compiled.json | 1 + .../trooper/trooper_ranged.bt.compiled.json | 1 + .../basic/turtle/turtle.bt.compiled.json | 1 + .../basic/vermin/axolotl.bt.compiled.json | 1 + .../basic/vermin/butterfly.bt.compiled.json | 1 + .../cockroach/cockroach.bt.compiled.json | 1 + .../cockroach_aggro.bt.compiled.json | 1 + .../cockroach_glockroach.bt.compiled.json | 1 + .../cockroach_mobroach.bt.compiled.json | 1 + .../living/basic/vermin/crab.bt.compiled.json | 1 + .../basic/vermin/eat_cable.bt.compiled.json | 1 + .../basic/vermin/eat_cheese.bt.compiled.json | 1 + .../living/basic/vermin/frog.bt.compiled.json | 1 + .../frog_engage_target.bt.compiled.json | 1 + .../basic/vermin/lizard.bt.compiled.json | 1 + .../mothroach/mothroach.bt.compiled.json | 1 + .../basic/vermin/mouse.bt.compiled.json | 1 + .../basic/vermin/mouse_rat.bt.compiled.json | 1 + .../play_instrument_on_floor.bt.compiled.json | 1 + .../basic/vermin/space_bat.bt.compiled.json | 1 + .../vermin/suicide_frog.bt.compiled.json | 1 + .../basic/vermin/trash.bt.compiled.json | 1 + code/__DEFINES/MC.dm | 7 - code/__DEFINES/ai/ai.dm | 59 +- code/__DEFINES/ai/ai_blackboard.dm | 58 +- code/__DEFINES/ai/behavior_trees.dm | 72 ++ code/__DEFINES/ai/bot_keys.dm | 39 +- code/__DEFINES/ai/carp.dm | 2 + code/__DEFINES/ai/haunted.dm | 2 + code/__DEFINES/ai/monkey.dm | 16 +- code/__DEFINES/ai/monsters.dm | 60 +- code/__DEFINES/ai/pet_commands.dm | 4 + code/__DEFINES/ai/pets.dm | 4 + code/__DEFINES/ai/slime.dm | 2 + code/__DEFINES/ai/tourist.dm | 4 + code/__DEFINES/ai/trader.dm | 2 + code/__DEFINES/ai/ventcrawling.dm | 2 + code/__DEFINES/basic_mobs.dm | 6 + .../dcs/signals/signals_ai_controller.dm | 6 +- .../dcs/signals/signals_mob/signals_mob_ai.dm | 3 + .../signals/signals_mob/signals_mob_basic.dm | 2 +- code/__DEFINES/dcs/signals/signals_object.dm | 2 + code/__DEFINES/monkeys.dm | 5 +- code/__DEFINES/robots.dm | 1 - code/__DEFINES/subsystems.dm | 7 +- code/_globalvars/lists/basic_ai.dm | 19 - code/controllers/subsystem/ai_controllers.dm | 156 +++- .../subsystem/ai_controllers_low_priority.dm | 6 + .../subsystem/ai_idle_controllers.dm | 10 - .../subsystem/movement/movement_types.dm | 8 +- .../subsystem/processing/ai_behaviors.dm | 40 - .../subsystem/processing/ai_idle_behaviors.dm | 19 - .../unplanned_ai_idle_controllers.dm | 4 - .../subsystem/unplanned_controllers.dm | 39 - .../actions/mobs/create_legion_skull.dm | 2 +- code/datums/ai/README.md | 8 +- code/datums/ai/_ai_behavior.dm | 157 +++- code/datums/ai/_ai_bt_composites.dm | 307 ++++++++ code/datums/ai/_ai_bt_decorators.dm | 242 ++++++ code/datums/ai/_ai_bt_node.dm | 109 +++ code/datums/ai/_ai_bt_subtree.dm | 88 +++ code/datums/ai/_ai_controller.dm | 726 ++++++++++-------- code/datums/ai/_ai_planning_subtree.dm | 10 - code/datums/ai/_item_behaviors.dm | 71 +- code/datums/ai/babies/babies_behaviors.dm | 60 +- code/datums/ai/babies/babies_subtrees.dm | 30 - code/datums/ai/babies/make_babies.bt.json | 31 + code/datums/ai/bane/bane.bt.json | 43 ++ code/datums/ai/bane/bane_behaviors.dm | 9 - code/datums/ai/bane/bane_controller.dm | 2 +- code/datums/ai/bane/bane_subtrees.dm | 16 - .../ai/basic_mobs/base_basic_controller.dm | 7 +- .../basic_ai_behaviors/basic_attacking.dm | 157 ++-- .../basic_ai_behaviors/befriend_target.dm | 18 +- .../basic_ai_behaviors/call_reinforcements.dm | 50 ++ .../basic_ai_behaviors/clear_key.dm | 14 - .../basic_ai_behaviors/climb_tree.dm | 35 - .../consider_venting.bt.json | 46 ++ .../basic_ai_behaviors/emote_with_target.dm | 28 - .../basic_ai_behaviors/find_flee_location.dm | 37 + .../basic_ai_behaviors/find_parent.dm | 50 +- .../interact_with_target.dm | 29 - .../basic_ai_behaviors/nearest_targeting.dm | 13 +- .../basic_ai_behaviors/pick_up_item.dm | 44 -- .../basic_ai_behaviors/play_dead.dm | 25 + .../basic_ai_behaviors/pull_target.dm | 22 - .../run_away_from_target.dm | 76 -- .../set_travel_destination.dm | 10 - .../basic_ai_behaviors/step_towards_turf.dm | 74 -- .../basic_ai_behaviors/stop_and_stare.dm | 34 +- .../targeted_mob_ability.dm | 78 +- .../basic_ai_behaviors/targeting.dm | 186 +++-- .../basic_ai_behaviors/tipped_reaction.dm | 20 +- .../basic_ai_behaviors/travel_towards.dm | 53 -- .../basic_ai_behaviors/unbuckle_mob.dm | 11 - .../basic_ai_behaviors/use_mob_ability.dm | 39 + .../basic_ai_behaviors/ventcrawling.dm | 279 ++++--- .../basic_ai_behaviors/wounded_targeting.dm | 11 - .../basic_ai_behaviors/write_on_paper.dm | 22 +- .../basic_subtrees/attack_adjacent_target.dm | 35 - .../basic_subtrees/attack_obstacle_in_path.dm | 85 -- .../basic_subtrees/call_reinforcements.dm | 68 -- .../basic_subtrees/capricious_retaliate.dm | 66 +- .../basic_mobs/basic_subtrees/climb_tree.dm | 15 - .../basic_mobs/basic_subtrees/drag_items.dm | 60 -- .../ai/basic_mobs/basic_subtrees/enrage.dm | 49 +- .../basic_subtrees/escape_captivity.bt.json | 82 ++ .../basic_subtrees/escape_captivity.dm | 75 +- .../escape_captivity_pacifist.bt.json | 50 ++ .../basic_subtrees/express_happiness.dm | 44 -- .../ai/basic_mobs/basic_subtrees/find_food.dm | 42 - .../find_paper_and_write.bt.json | 73 ++ .../basic_subtrees/find_paper_and_write.dm | 25 +- .../basic_mobs/basic_subtrees/find_parent.dm | 31 - .../find_targets_prioritize_traits.dm | 6 - .../ai/basic_mobs/basic_subtrees/fishing.dm | 44 -- .../basic_mobs/basic_subtrees/flee_target.dm | 39 - .../basic_subtrees/generic_hunger.dm | 2 + .../basic_subtrees/generic_play_instrument.dm | 2 + .../basic_subtrees/go_for_swim.bt.json | 70 ++ .../basic_mobs/basic_subtrees/go_for_swim.dm | 68 +- .../basic_subtrees/maintain_distance.dm | 119 --- .../basic_mobs/basic_subtrees/mine_walls.dm | 68 -- .../basic_subtrees/move_to_cardinal.dm | 85 +- .../opportunistic_ventcrawler.dm | 20 - .../basic_subtrees/play_with_owners.dm | 21 - .../prepare_travel_to_destination.dm | 23 - .../basic_subtrees/random_speech_loop.bt.json | 19 + .../basic_subtrees/random_speech_loop.dm | 2 + .../basic_subtrees/ranged_skirmish.dm | 50 -- .../run_away_from_target.bt.json | 50 ++ .../basic_subtrees/run_away_from_target.dm | 8 + ...run_away_from_target_run_and_shoot.bt.json | 69 ++ .../ai/basic_mobs/basic_subtrees/run_emote.dm | 31 - .../basic_subtrees/shapechange_ambush.dm | 41 - .../basic_subtrees/simple_attack_target.dm | 33 - .../simple_find_nearest_target_to_flee.dm | 25 - .../basic_subtrees/simple_find_target.dm | 26 - .../simple_find_wounded_target.dm | 6 - .../skittish_brawler_combat.bt.json | 88 +++ .../basic_subtrees/skittish_brawler_combat.dm | 4 + .../basic_subtrees/sleep_with_no_target.dm | 35 - .../basic_subtrees/speech_subtree.dm | 242 ------ .../basic_subtrees/stare_at_thing.dm | 12 - .../basic_subtrees/target_retaliate.dm | 94 --- .../basic_subtrees/targeted_mob_ability.dm | 34 - .../teleport_away_from_target.dm | 55 -- .../basic_subtrees/tip_reaction.bt.json | 13 + .../basic_subtrees/tipped_subtree.dm | 10 - .../basic_subtrees/travel_to_point.dm | 21 - .../basic_subtrees/use_mob_ability.dm | 33 - .../ai/basic_mobs/generic_controllers.dm | 155 ++-- .../ai/basic_mobs/pet_commands/fetch.dm | 143 ---- .../pet_commands/pet_command_attack.bt.json | 48 ++ .../pet_command_attack_dog.bt.json | 48 ++ .../pet_command_attack_minebot.bt.json | 48 ++ ...t_command_attack_ranged_glockroach.bt.json | 46 ++ .../pet_commands/pet_command_beehive.bt.json | 23 + .../pet_commands/pet_command_breed.bt.json | 64 ++ .../basic_mobs/pet_commands/pet_command_bt.dm | 128 +++ .../pet_commands/pet_command_fetch.bt.json | 107 +++ .../pet_commands/pet_command_fish.bt.json | 38 + .../pet_commands/pet_command_follow.bt.json | 25 + .../pet_command_mine_walls.bt.json | 46 ++ .../pet_commands/pet_command_move_to.bt.json | 27 + .../pet_commands/pet_command_planning.dm | 14 - .../pet_command_play_dead.bt.json | 14 + .../pet_command_protect_owner.bt.json | 57 ++ ...nd_protect_owner_ranged_glockroach.bt.json | 55 ++ .../pet_commands/pet_command_scatter.bt.json | 17 + .../pet_commands/pet_command_stay.bt.json | 8 + .../pet_commands/pet_command_swirl.bt.json | 81 ++ .../pet_command_targeted_ability.bt.json | 51 ++ .../pet_command_untargeted_ability.bt.json | 17 + .../pet_commands/pet_follow_friend.dm | 16 - .../pet_commands/pet_use_targeted_ability.dm | 39 - .../ai/basic_mobs/pet_commands/play_dead.dm | 26 - .../ai/basic_mobs/simple_ability.bt.json | 14 + .../basic_mobs/simple_ability_combat.bt.json | 101 +++ .../basic_mobs/simple_ability_melee.bt.json | 14 + .../simple_ability_melee_combat.bt.json | 91 +++ .../basic_mobs/simple_ability_ranged.bt.json | 14 + .../simple_ability_ranged_combat.bt.json | 82 ++ .../simple_ability_retaliate.bt.json | 14 + .../simple_ability_retaliate_combat.bt.json | 70 ++ .../ai/basic_mobs/simple_capricious.bt.json | 14 + .../simple_capricious_combat.bt.json | 66 ++ .../ai/basic_mobs/simple_fearful.bt.json | 10 + .../basic_mobs/simple_fearful_combat.bt.json | 41 + code/datums/ai/basic_mobs/simple_goon.bt.json | 15 + .../ai/basic_mobs/simple_hostile.bt.json | 14 + .../basic_mobs/simple_hostile_combat.bt.json | 98 +++ ...mple_hostile_combat_with_retaliate.bt.json | 107 +++ .../simple_hostile_obstacles.bt.json | 49 ++ .../simple_hostile_obstacles_combat.bt.json | 109 +++ .../ai/basic_mobs/simple_ranged.bt.json | 14 + .../basic_mobs/simple_ranged_combat.bt.json | 77 ++ .../simple_ranged_retaliate.bt.json | 14 + .../simple_ranged_retaliate_combat.bt.json | 69 ++ .../ai/basic_mobs/simple_retaliate.bt.json | 14 + .../simple_retaliate_combat.bt.json | 87 +++ .../ai/basic_mobs/simple_skirmisher.bt.json | 14 + .../simple_skirmisher_combat.bt.json | 92 +++ .../ai/basic_mobs/simple_skittish.bt.json | 10 + .../basic_mobs/simple_skittish_combat.bt.json | 41 + code/datums/ai/basic_mobs/talk.bt.json | 5 + .../target_sources/_target_source.dm | 106 +++ .../target_sources/held_items_then_oview.dm | 11 + .../target_sources/held_items_typed.dm | 13 + .../target_sources/mobs_in_oview.dm | 20 + .../target_sources/near_village_humans.dm | 12 + .../target_sources/oview_single_type.dm | 58 ++ .../target_sources/oview_typed_from_bb_key.dm | 19 + .../range_turfs_typecache_visible.dm | 34 + .../basic_mobs/target_sources/slime_source.dm | 11 + .../target_sources/turfs_in_oview.dm | 23 + .../_targeting_strategy.dm | 38 +- .../targeting_strategies/accessible_cable.dm | 14 + .../targeting_strategies/ally_mob.dm | 10 + .../targeting_strategies/baby_raptor.dm | 11 + .../basic_targeting_strategy.dm | 103 ++- .../targeting_strategies/beamable_hydro.dm | 11 + .../befriendable_cultist.dm | 13 + .../targeting_strategies/cat_food.dm | 12 + .../targeting_strategies/chargeable_apc.dm | 14 + .../targeting_strategies/conscious_human.dm | 13 + .../targeting_strategies/conscious_mob.dm | 11 + .../targeting_strategies/conscious_snail.dm | 13 + .../targeting_strategies/damaged_eyes.dm | 17 + .../targeting_strategies/damaged_machine.dm | 11 + .../targeting_strategies/dead_mob.dm | 20 + .../targeting_strategies/decorated_donut.dm | 11 + .../dont_target_friends.dm | 9 +- .../targeting_strategies/drillable_ice.dm | 12 + .../targeting_strategies/empty_paper.dm | 11 + .../targeting_strategies/finished_stove.dm | 14 + .../targeting_strategies/food_or_drink.dm | 30 + .../goliath_diggable_turf.dm | 11 + .../targeting_strategies/goose_edible.dm | 11 + .../targeting_strategies/huntable.dm | 12 + .../targeting_strategies/huntable_mouse.dm | 11 + .../targeting_strategies/injured_mob.dm | 19 + .../targeting_strategies/injured_raptor.dm | 6 + .../legged_conscious_human.dm | 13 + .../targeting_strategies/living_not_dead.dm | 12 + .../targeting_strategies/non_stump_tree.dm | 10 + .../targeting_strategies/pickup_item.dm | 10 + .../targeting_strategies/playable_deer.dm | 11 + .../playable_synthesizer.dm | 12 + .../pollinatable_hydro.dm | 11 + .../targeting_strategies/raptor_trough.dm | 10 + .../targeting_strategies/slime_food.dm | 44 ++ .../targeting_strategies/sniffable_hydro.dm | 15 + .../targeting_strategies/stealable_item.dm | 11 + .../targeting_strategies/stocked_beehive.dm | 11 + .../targeting_strategies/treatable_hydro.dm | 19 + .../targeting_strategies/trough_with_ore.dm | 10 + .../targeting_strategies/unbroken_light.dm | 11 + .../targeting_strategies/uncarried_egg.dm | 10 + .../targeting_strategies/unlit_bonfire.dm | 11 + .../targeting_strategies/valid_cat_home.dm | 11 + .../targeting_strategies/valid_kitten.dm | 15 + .../targeting_strategies/walkable_turf.dm | 9 + .../targeting_strategies/water_dispenser.dm | 13 + .../targeting_strategies/with_object.dm | 12 +- .../targeting_strategies/working_machine.dm | 11 + code/datums/ai/bots/bot_decorators.dm | 45 ++ code/datums/ai/bots/bot_patrol.bt.json | 88 +++ .../ai/bots/bot_respond_to_summon.bt.json | 29 + .../ai/bots/bot_salute_authority.bt.json | 29 + code/datums/ai/bots/bot_subtrees.dm | 281 +++++++ code/datums/ai/bt_viewer.dm | 161 ++++ code/datums/ai/cursed/cursed.bt.json | 72 ++ code/datums/ai/cursed/cursed_behaviors.dm | 9 +- code/datums/ai/cursed/cursed_controller.dm | 3 +- code/datums/ai/cursed/cursed_subtrees.dm | 13 - code/datums/ai/dog/dog.bt.json | 44 ++ code/datums/ai/dog/dog_behaviors.dm | 49 -- code/datums/ai/dog/dog_bt.dm | 113 +++ code/datums/ai/dog/dog_controller.dm | 28 +- code/datums/ai/dog/dog_corgi.bt.json | 74 ++ code/datums/ai/dog/dog_harassment.bt.json | 39 + code/datums/ai/dog/dog_subtrees.dm | 38 - code/datums/ai/generic/find_and_set.dm | 223 ------ code/datums/ai/generic/generic_behaviors.dm | 377 --------- code/datums/ai/generic/generic_subtrees.dm | 98 --- .../acquire_injured_target.dm | 15 + .../ai/generic_behaviors/acquire_target.dm | 196 +++++ .../ai/generic_behaviors/ai_interact.dm | 17 + .../ai/generic_behaviors/attack_obstacles.dm | 65 ++ .../ai/generic_behaviors/battle_screech.dm | 10 + .../generic_behaviors/break_out_of_object.dm | 40 + .../ai/generic_behaviors/break_spine.dm | 42 + .../generic_behaviors/cancel_current_plan.dm | 6 + code/datums/ai/generic_behaviors/clear_key.dm | 8 + code/datums/ai/generic_behaviors/consume.dm | 43 ++ .../ai/generic_behaviors/copy_bb_key.dm | 8 + .../ai/generic_behaviors/drag_target.dm | 21 + .../generic_behaviors/drop_all_held_items.dm | 9 + .../ai/generic_behaviors/express_happiness.dm | 53 ++ .../face_target_or_face_initial.dm | 24 + code/datums/ai/generic_behaviors/fail.dm | 4 + .../find_furthest_turf_from_target.dm | 32 + .../ai/generic_behaviors/find_nearby.dm | 21 + .../find_target_facing_turf.dm | 19 + .../generic_behaviors/find_unwebbed_turf.dm | 46 ++ .../find_valid_teleport_location.dm | 28 + code/datums/ai/generic_behaviors/give.dm | 86 +++ .../ai/generic_behaviors/grab_target.dm | 33 + .../ai/generic_behaviors/heal_eye_damage.dm | 22 + .../ai/generic_behaviors/hunt_target.dm | 184 +++++ .../ai/generic_behaviors/issue_pet_command.dm | 41 + .../keep_playing_instrument.dm | 19 + .../ai/generic_behaviors/maintain_distance.dm | 94 +++ .../datums/ai/generic_behaviors/mine_walls.dm | 53 ++ .../ai/generic_behaviors/move_to_target.dm | 55 ++ .../ai/generic_behaviors/perform_emote.dm | 11 + .../generic_behaviors/pick_random_ability.dm | 27 + code/datums/ai/generic_behaviors/pick_up.dm | 28 + .../ai/generic_behaviors/play_instrument.dm | 13 + .../ai/generic_behaviors/random_walk.dm | 111 +++ code/datums/ai/generic_behaviors/resist.dm | 14 + code/datums/ai/generic_behaviors/run_emote.dm | 21 + .../ai/generic_behaviors/set_bb_cooldown.dm | 10 + .../datums/ai/generic_behaviors/set_bb_key.dm | 10 + .../ai/generic_behaviors/setup_instrument.dm | 18 + code/datums/ai/generic_behaviors/speech.dm | 184 +++++ code/datums/ai/generic_behaviors/spin_web.dm | 35 + .../ai/generic_behaviors/stop_dragging.dm | 7 + .../ai/generic_behaviors/stuff_in_disposal.dm | 37 + code/datums/ai/generic_behaviors/succeed.dm | 4 + .../ai/generic_behaviors/target_retaliate.dm | 53 ++ .../ai/generic_behaviors/use_in_hand.dm | 10 + .../ai/generic_behaviors/use_on_object.dm | 12 + .../generic_behaviors/virtual_pick_up_item.dm | 59 ++ code/datums/ai/generic_behaviors/wait.dm | 19 + .../generic_decorators/ability_available.dm | 8 + .../ai/generic_decorators/bb_key_at_least.dm | 8 + .../ai/generic_decorators/bb_key_equals.dm | 7 + .../bb_key_list_min_count.dm | 14 + .../ai/generic_decorators/bb_key_set.dm | 13 + .../ai/generic_decorators/bb_key_true.dm | 7 + .../buckle_target_dangerous.dm | 10 + .../ai/generic_decorators/can_see_target.dm | 17 + .../ai/generic_decorators/check_cooldown.dm | 28 + .../ai/generic_decorators/check_rider_stat.dm | 11 + .../container_attackable.dm | 15 + .../ai/generic_decorators/is_at_distance.dm | 21 + .../ai/generic_decorators/is_dragging.dm | 6 + .../generic_decorators/is_grabbing_target.dm | 20 + .../generic_decorators/is_holding_target.dm | 24 + .../ai/generic_decorators/is_in_vent.dm | 13 + .../generic_decorators/is_target_stunned.dm | 11 + .../ai/generic_decorators/item_inside_pawn.dm | 46 ++ .../ai/generic_decorators/key_in_typelist.dm | 30 + .../keys_different_gender.dm | 30 + .../generic_decorators/mob_stat_at_least.dm | 45 ++ .../generic_decorators/no_humans_watching.dm | 11 + .../generic_decorators/pawn_buckled_to_obj.dm | 18 + .../pawn_contained_in_obj.dm | 18 + .../pawn_farther_than_from_key.dm | 13 + .../pawn_grabbed_by_enemy.dm | 22 + .../ai/generic_decorators/pawn_has_gravity.dm | 13 + .../generic_decorators/pawn_has_trait_from.dm | 16 + .../generic_decorators/pawn_health_below.dm | 20 + .../ai/generic_decorators/pawn_inside_mob.dm | 13 + .../generic_decorators/pawn_is_restrained.dm | 14 + .../ai/generic_decorators/pawn_loc_is_type.dm | 15 + .../pawn_nutrition_below.dm | 10 + .../generic_decorators/pawn_same_z_as_key.dm | 9 + .../generic_decorators/pawn_turf_has_trait.dm | 7 + .../ai/generic_decorators/random_chance.dm | 7 + .../random_chance_from_key.dm | 10 + .../generic_decorators/target_has_reagent.dm | 44 ++ .../ai/generic_decorators/target_has_trait.dm | 43 ++ .../target_health_below_fraction.dm | 42 + .../target_holding_lit_item.dm | 21 + .../target_is_holding_item.dm | 42 + .../ai/generic_decorators/target_is_type.dm | 12 + .../ai/generic_decorators/target_legcuffed.dm | 10 + .../ai/generic_decorators/target_on_ground.dm | 45 ++ .../ai/generic_decorators/true_for_time.dm | 31 + code/datums/ai/generic_hunger.bt.json | 43 ++ .../datums/ai/generic_play_instrument.bt.json | 83 ++ .../basic_find_target.bt.json | 10 + .../ai/generic_subtrees/basic_find_target.dm | 2 + .../capricious_pick_target.bt.json | 18 + .../capricious_pick_target.dm | 4 + .../ai/generic_subtrees/climb_tree.bt.json | 50 ++ code/datums/ai/generic_subtrees/climb_tree.dm | 2 + .../ai/generic_subtrees/find_food.bt.json | 19 + code/datums/ai/generic_subtrees/find_food.dm | 2 + .../ai/generic_subtrees/find_partner.bt.json | 32 + .../ai/generic_subtrees/find_partner.dm | 2 + .../find_stealable_object.bt.json | 88 +++ .../generic_subtrees/find_stealable_object.dm | 2 + .../forage_and_retaliate.bt.json | 92 +++ .../generic_subtrees/forage_and_retaliate.dm | 2 + .../datums/ai/generic_subtrees/make_babies.dm | 2 + .../generic_subtrees/move_to_and_eat.bt.json | 36 + .../ai/generic_subtrees/move_to_and_eat.dm | 2 + .../generic_subtrees/move_to_and_hunt.bt.json | 63 ++ .../ai/generic_subtrees/move_to_and_hunt.dm | 2 + .../move_to_reinforce.bt.json | 30 + .../ai/generic_subtrees/move_to_reinforce.dm | 2 + .../pick_retaliate_target.bt.json | 10 + .../generic_subtrees/pick_retaliate_target.dm | 3 + .../ai/generic_subtrees/random_walk.bt.json | 29 + .../datums/ai/generic_subtrees/random_walk.dm | 2 + .../skittish_and_speak.bt.json | 32 + .../ai/generic_subtrees/skittish_and_speak.dm | 2 + .../generic_subtrees/steal_and_flee.bt.json | 43 ++ .../ai/generic_subtrees/steal_and_flee.dm | 2 + code/datums/ai/hauntium/haunted.bt.json | 107 +++ code/datums/ai/hauntium/haunted_bt_nodes.dm | 83 ++ code/datums/ai/hauntium/haunted_controller.dm | 3 +- code/datums/ai/hauntium/hauntium_subtrees.dm | 24 - .../ai/hunting_behavior/hunting_behaviors.dm | 170 ---- .../ai/hunting_behavior/hunting_cockroach.dm | 2 - .../ai/hunting_behavior/hunting_corpses.dm | 17 - .../ai/hunting_behavior/hunting_lights.dm | 18 - .../ai/hunting_behavior/hunting_mouse.dm | 55 -- .../ai/idle_behaviors/_idle_behavior.dm | 5 - code/datums/ai/idle_behaviors/idle_dog.dm | 21 - code/datums/ai/idle_behaviors/idle_haunted.dm | 15 - code/datums/ai/idle_behaviors/idle_monkey.dm | 37 - .../ai/idle_behaviors/idle_random_walk.dm | 86 --- code/datums/ai/learn_ai.md | 355 ++++++--- .../ai/learn_ai_images/behavior_example.png | Bin 0 -> 24461 bytes .../ai/learn_ai_images/bt_editor_example1.png | Bin 0 -> 11728 bytes .../ai/learn_ai_images/decorator_example.png | Bin 0 -> 27708 bytes .../ai/learn_ai_images/parallel_example.png | Bin 0 -> 23879 bytes .../ai/learn_ai_images/selector_example.png | Bin 0 -> 11099 bytes .../ai/learn_ai_images/sequence_example.png | Bin 0 -> 14203 bytes .../ai/learn_ai_images/subplan_example.png | Bin 0 -> 42695 bytes code/datums/ai/monkey/monkey.bt.json | 245 ++++++ code/datums/ai/monkey/monkey_behaviors.dm | 305 -------- code/datums/ai/monkey/monkey_bt_nodes.dm | 402 ++++++++++ code/datums/ai/monkey/monkey_combat.bt.json | 228 ++++++ code/datums/ai/monkey/monkey_controller.dm | 86 +-- .../ai/monkey/monkey_find_weapon.bt.json | 50 ++ .../ai/monkey/monkey_serve_food.bt.json | 132 ++++ .../ai/monkey/monkey_shenanigans.bt.json | 114 +++ code/datums/ai/monkey/monkey_subtrees.dm | 111 --- code/datums/ai/movement/_ai_movement.dm | 23 +- .../movement/ai_movement_basic_avoidance.dm | 15 +- .../ai/movement/ai_movement_complete_stop.dm | 5 +- code/datums/ai/movement/ai_movement_dumb.dm | 13 +- code/datums/ai/movement/ai_movement_jps.dm | 26 +- .../vending_machines/vending_machine.bt.json | 64 ++ .../vending_machine_behaviors.dm | 68 +- .../vending_machine_controller.dm | 25 +- .../ai/robot_customer/robot_customer.bt.json | 125 +++ .../robot_customer_behaviors.dm | 121 +-- .../robot_customer_controller.dm | 6 +- .../robot_customer/robot_customer_subtrees.dm | 23 - code/datums/brain_damage/special.dm | 8 +- code/datums/components/aggro_emote.dm | 2 +- code/datums/components/ai_has_target_timer.dm | 2 +- .../datums/components/ai_listen_to_weather.dm | 2 +- code/datums/components/appearance_on_aggro.dm | 2 +- code/datums/components/connect_range.dm | 2 + code/datums/components/pet_commands/fetch.dm | 28 +- .../components/pet_commands/pet_command.dm | 14 +- .../pet_commands/pet_commands_basic.dm | 80 +- code/datums/components/proficient_miner.dm | 17 +- code/datums/components/revenge_ability.dm | 2 +- code/datums/components/tameable.dm | 3 + code/datums/components/tree_climber.dm | 4 +- code/datums/diseases/verminous_plague.dm | 2 +- code/datums/dog_fashion.dm | 8 +- code/datums/elements/ai_flee_while_injured.dm | 2 - code/datums/elements/ai_retaliate.dm | 2 +- .../datums/elements/ai_target_damagesource.dm | 4 +- .../fields/ai_target_tracking.dm | 49 +- .../proximity_monitor/fields/timestop.dm | 4 +- .../debuffs/slime/slime_leech.dm | 3 +- .../machinery/computer/arcade/orion_event.dm | 2 +- code/game/machinery/dna_scanner.dm | 13 +- code/modules/admin/admin_verbs.dm | 103 +-- code/modules/antagonists/blob/powers.dm | 2 +- .../antagonists/heretic/heretic_knowledge.dm | 3 +- .../antagonists/netguardian.bt.json | 110 +++ .../bitrunning/antagonists/netguardian.dm | 21 +- .../virtual_domain/domains/crewman.bt.json | 118 +++ .../domains/crewman_hostile.bt.json | 118 +++ .../domains/crewman_hostile_ranged.bt.json | 129 ++++ .../domains/crewman_ranged.bt.json | 129 ++++ .../virtual_domain/domains/heretic_hunt.dm | 40 +- .../kinetic_crusher/trophies_fauna.dm | 2 +- .../kinetic_crusher/trophies_megafauna.dm | 2 +- code/modules/mob/inventory.dm | 1 + code/modules/mob/living/basic/alien/_alien.dm | 14 +- .../mob/living/basic/alien/alien.bt.json | 9 + .../mob/living/basic/alien/alien_ai.dm | 89 +-- .../living/basic/alien/basic_alien.bt.json | 60 ++ .../mob/living/basic/alien/drone.bt.json | 9 + .../living/basic/alien/lay_alien_egg.bt.json | 14 + .../basic/alien/melee_alien_combat.bt.json | 47 ++ .../basic/alien/plant_alien_weeds.bt.json | 23 + .../mob/living/basic/alien/queen.bt.json | 9 + .../basic/alien/ranged_alien_combat.bt.json | 48 ++ .../mob/living/basic/alien/sentinel.bt.json | 8 + .../mob/living/basic/blob_minions/blob_ai.dm | 27 +- .../basic/blob_minions/blob_spore.bt.json | 69 ++ .../basic/blob_minions/blob_zombie.bt.json | 42 + .../basic/blob_minions/blobbernaut.bt.json | 14 + .../blood_drunk_miner/_blood_drunk_miner.dm | 9 +- .../boss/blood_drunk_miner/blood_drunk_ai.dm | 52 +- .../blood_drunk_miner.bt.json | 45 ++ .../blood_drunk_miner_combat.bt.json | 132 ++++ code/modules/mob/living/basic/boss/boss.dm | 3 +- .../mob/living/basic/boss/thing/thing.dm | 10 +- .../mob/living/basic/boss/thing/thing_ai.dm | 67 +- .../living/basic/boss/thing/thing_aoe.bt.json | 72 ++ .../basic/boss/thing/thing_boss.bt.json | 70 ++ .../basic/boss/thing/thing_melee.bt.json | 89 +++ .../modules/mob/living/basic/bots/bot.bt.json | 22 + code/modules/mob/living/basic/bots/bot_ai.dm | 283 +------ code/modules/mob/living/basic/bots/bot_hud.dm | 7 +- .../bots/cleanbot/clean_pet_target.bt.json | 28 + .../basic/bots/cleanbot/cleanbot.bt.json | 251 ++++++ .../living/basic/bots/cleanbot/cleanbot_ai.dm | 178 +---- .../mob/living/basic/bots/dedbot.bt.json | 73 ++ code/modules/mob/living/basic/bots/dedbot.dm | 13 +- .../mob/living/basic/bots/ed209/ed209.bt.json | 132 ++++ .../mob/living/basic/bots/ed209/ed209_ai.dm | 26 +- .../living/basic/bots/ed209/ed209_nukie_ai.dm | 9 +- .../basic/bots/ed209/ed209_syndicate.bt.json | 21 + .../living/basic/bots/firebot/firebot.bt.json | 119 +++ .../living/basic/bots/firebot/firebot_ai.dm | 164 ++-- .../basic/bots/honkbots/honkbot.bt.json | 191 +++++ .../mob/living/basic/bots/honkbots/honkbot.dm | 2 +- .../living/basic/bots/honkbots/honkbot_ai.dm | 211 +++-- .../basic/bots/honkbots/honkbot_slip.bt.json | 77 ++ .../basic/bots/hygienebot/hygienebot.bt.json | 109 +++ .../basic/bots/hygienebot/hygienebot_ai.dm | 135 +--- .../living/basic/bots/medbot/medbot.bt.json | 109 +++ .../mob/living/basic/bots/medbot/medbot_ai.dm | 214 ++---- .../medbot_find_and_announce_crit.bt.json | 16 + .../bots/medbot/medbot_treat_patient.bt.json | 28 + .../living/basic/bots/mulebot/mulebot.bt.json | 103 +++ .../mob/living/basic/bots/mulebot/mulebot.dm | 2 +- .../living/basic/bots/mulebot/mulebot_ai.dm | 78 +- .../basic/bots/repairbot/repairbot.bt.json | 32 + .../basic/bots/repairbot/repairbot_ai.dm | 442 +++++------ .../bots/repairbot/repairbot_emagged.bt.json | 71 ++ .../repairbot/repairbot_find_target.bt.json | 143 ++++ .../repairbot/repairbot_repair_target.bt.json | 88 +++ .../living/basic/bots/secbot/secbot.bt.json | 85 ++ .../mob/living/basic/bots/secbot/secbot.dm | 2 +- .../mob/living/basic/bots/secbot/secbot_ai.dm | 43 +- .../living/basic/bots/secbot/super_beepsky.dm | 2 +- .../basic/bots/secbot/super_beepsky_ai.dm | 10 +- .../living/basic/bots/vibebot/vibebot.bt.json | 74 ++ .../living/basic/bots/vibebot/vibebot_ai.dm | 68 +- .../mob/living/basic/clown/clown.bt.json | 28 + .../mob/living/basic/clown/clown_ai.dm | 8 +- .../basic/cult/constructs/artificer.bt.json | 83 ++ .../basic/cult/constructs/construct_ai.dm | 39 +- .../basic/cult/constructs/juggernaut.bt.json | 5 + .../basic/cult/constructs/proteon.bt.json | 14 + .../basic/cult/constructs/wraith.bt.json | 83 ++ .../living/basic/cytology/vatbeast.bt.json | 137 ++++ .../mob/living/basic/cytology/vatbeast.dm | 26 +- .../mob/living/basic/farm_animals/bee/_bee.dm | 2 +- .../living/basic/farm_animals/bee/bee.bt.json | 86 +++ .../basic/farm_animals/bee/bee_ai_behavior.dm | 142 +--- .../basic/farm_animals/bee/bee_ai_subtree.dm | 87 +-- .../living/basic/farm_animals/bee/bee_bt.dm | 132 ++++ .../basic/farm_animals/bee/find_hive.bt.json | 43 ++ .../farm_animals/bee/pollinate_target.bt.json | 27 + .../basic/farm_animals/bee/queen_bee.bt.json | 58 ++ .../bee/transition_hive_status.bt.json | 45 ++ .../basic/farm_animals/chicken/chick.bt.json | 87 +++ .../basic/farm_animals/chicken/chick.dm | 13 +- .../farm_animals/chicken/chicken.bt.json | 5 + .../basic/farm_animals/chicken/chicken.dm | 15 +- .../mob/living/basic/farm_animals/cow/_cow.dm | 2 +- .../living/basic/farm_animals/cow/cow.bt.json | 48 ++ .../living/basic/farm_animals/cow/cow_ai.dm | 18 +- .../farm_animals/cow/cow_moonicorn.bt.json | 96 +++ .../basic/farm_animals/cow/cow_moonicorn.dm | 21 +- .../basic/farm_animals/cow/cow_wisdom.bt.json | 28 + .../basic/farm_animals/cow/cow_wisdom.dm | 11 +- .../basic/farm_animals/deer/deer.bt.json | 372 +++++++++ .../living/basic/farm_animals/deer/deer_ai.dm | 185 ++--- .../basic/farm_animals/goat/goat.bt.json | 26 + .../living/basic/farm_animals/goat/goat_ai.dm | 22 +- .../goose/forage_for_goose_food.bt.json | 11 + .../basic/farm_animals/goose/goose.bt.json | 38 + .../living/basic/farm_animals/goose/goose.dm | 9 +- .../basic/farm_animals/goose/goose_ai.dm | 83 +- .../farm_animals/goose/goose_calm.bt.json | 30 + .../farm_animals/gorilla/gorilla.bt.json | 130 ++++ .../basic/farm_animals/gorilla/gorilla_ai.dm | 17 +- .../mob/living/basic/farm_animals/pig.bt.json | 26 + .../mob/living/basic/farm_animals/pig.dm | 17 +- .../living/basic/farm_animals/pony.bt.json | 43 ++ .../mob/living/basic/farm_animals/pony.dm | 29 +- .../living/basic/farm_animals/rabbit.bt.json | 5 + .../mob/living/basic/farm_animals/rabbit.dm | 41 +- .../living/basic/farm_animals/sheep.bt.json | 5 + .../mob/living/basic/farm_animals/sheep.dm | 14 +- .../mob/living/basic/festivus_pole.bt.json | 122 +++ .../modules/mob/living/basic/festivus_pole.dm | 45 +- .../mob/living/basic/heretic/flesh_stalker.dm | 10 +- .../living/basic/heretic/raw_prophet.bt.json | 14 + .../mob/living/basic/heretic/raw_prophet.dm | 9 +- .../living/basic/heretic/rust_walker.bt.json | 126 +++ .../mob/living/basic/heretic/rust_walker.dm | 32 +- .../mob/living/basic/heretic/stalker.bt.json | 147 ++++ .../living/basic/heretic/star_gazer.bt.json | 19 + .../mob/living/basic/heretic/star_gazer.dm | 24 +- .../basic/icemoon/ice_demon/ice_demon.bt.json | 54 ++ .../ice_demon/ice_demon_afterimage.bt.json | 18 + .../basic/icemoon/ice_demon/ice_demon_ai.dm | 107 +-- .../ice_demon/ice_demon_combat.bt.json | 127 +++ .../ice_demon_flee_from_fire.bt.json | 37 + .../basic/icemoon/ice_whelp/ice_whelp.bt.json | 284 +++++++ .../basic/icemoon/ice_whelp/ice_whelp.dm | 1 - .../basic/icemoon/ice_whelp/ice_whelp_ai.dm | 152 +--- .../basic/icemoon/polar_bear/polar.bt.json | 40 + .../basic/icemoon/polar_bear/polar_bear.dm | 10 +- .../living/basic/icemoon/wolf/wolf.bt.json | 120 +++ .../mob/living/basic/icemoon/wolf/wolf_ai.dm | 23 +- .../mob/living/basic/illusion/escape.bt.json | 28 + .../mob/living/basic/illusion/illlusion_ai.dm | 28 +- .../mob/living/basic/illusion/illusion.dm | 2 +- .../living/basic/illusion/retaliate.bt.json | 28 + .../living/basic/jungle/human_trap.bt.json | 5 + .../living/basic/jungle/leaper/leaper.bt.json | 122 +++ .../living/basic/jungle/leaper/leaper_ai.dm | 35 +- .../mega_arachnid/mega_arachnid.bt.json | 96 +++ .../jungle/mega_arachnid/mega_arachnid_ai.dm | 74 +- .../mega_arachnid_combat.bt.json | 147 ++++ .../basic/jungle/seedling/seedling.bt.json | 286 +++++++ .../basic/jungle/seedling/seedling_ai.dm | 156 +--- .../jungle/seedling/seedling_meanie.bt.json | 106 +++ .../living/basic/jungle/venus_human_trap.dm | 9 +- .../basic/lavaland/basilisk/basilisk.bt.json | 116 +++ .../basic/lavaland/basilisk/basilisk.dm | 9 +- .../basic/lavaland/bileworm/bileworm.bt.json | 143 ++++ .../lavaland/bileworm/bileworm_actions.dm | 2 +- .../basic/lavaland/bileworm/bileworm_ai.dm | 56 +- .../basic/lavaland/brimdemon/brimbeam.dm | 2 +- .../lavaland/brimdemon/brimdemon.bt.json | 122 +++ .../basic/lavaland/brimdemon/brimdemon_ai.dm | 40 +- .../basic/lavaland/goldgrub/babygrub.bt.json | 93 +++ .../basic/lavaland/goldgrub/goldgrub.bt.json | 238 ++++++ .../basic/lavaland/goldgrub/goldgrub_ai.dm | 370 +++++---- .../lavaland/goldgrub/grub_eat_target.bt.json | 44 ++ .../basic/lavaland/goliath/goliath.bt.json | 171 +++++ .../basic/lavaland/goliath/goliath_ai.dm | 115 +-- .../gutlunchers/gutlunch_baby.bt.json | 81 ++ .../gutlunchers/gutlunch_milk.bt.json | 96 +++ .../gutlunchers/gutlunch_warrior.bt.json | 53 ++ .../lavaland/gutlunchers/gutlunchers_ai.dm | 141 ++-- .../basic/lavaland/hivelord/hivelord.bt.json | 5 + .../basic/lavaland/hivelord/hivelord.dm | 2 +- .../basic/lavaland/hivelord/hivelord_ai.dm | 8 +- .../lavaland/hivelord/spawn_hivelord_brood.dm | 2 +- .../basic/lavaland/legion/legion.bt.json | 102 +++ .../living/basic/lavaland/legion/legion_ai.dm | 55 +- .../lavaland/legion/legion_brood.bt.json | 81 ++ .../basic/lavaland/legion/legion_brood.dm | 2 +- .../lavaland/legion/legion_monkey.bt.json | 119 +++ .../basic/lavaland/legion/legion_monkey.dm | 12 +- .../basic/lavaland/legion/spawn_legions.dm | 2 +- .../lavaland/lobstrosity/lobstrosity.bt.json | 131 ++++ .../basic/lavaland/lobstrosity/lobstrosity.dm | 4 +- .../lavaland/lobstrosity/lobstrosity_ai.dm | 299 +------- .../lobstrosity/lobstrosity_calm.bt.json | 19 + .../lobstrosity_capricious.bt.json | 19 + .../living/basic/lavaland/mook/bard.bt.json | 9 + .../lavaland/mook/bard_find_targets.bt.json | 56 ++ .../lavaland/mook/bard_play_music.bt.json | 55 ++ .../lavaland/mook/chief_find_targets.bt.json | 68 ++ .../mook/chief_issue_commands.bt.json | 53 ++ .../mook/chief_manage_village.bt.json | 150 ++++ .../mook/generic_mook_behavior.bt.json | 178 +++++ .../basic/lavaland/mook/go_mining.bt.json | 213 +++++ .../basic/lavaland/mook/heal_injured.bt.json | 42 + .../living/basic/lavaland/mook/mook.bt.json | 9 + .../mob/living/basic/lavaland/mook/mook.dm | 9 +- .../mob/living/basic/lavaland/mook/mook_ai.dm | 389 +--------- .../mob/living/basic/lavaland/mook/mook_bt.dm | 156 ++++ .../basic/lavaland/mook/support.bt.json | 9 + .../mook/support_find_targets.bt.json | 63 ++ .../basic/lavaland/mook/tribal_chief.bt.json | 10 + .../lavaland/mook/worker_find_targets.bt.json | 72 ++ .../lavaland/node_drone/node_drone.bt.json | 73 ++ .../basic/lavaland/node_drone/node_drone.dm | 43 +- .../basic/lavaland/raptor/baby_raptor.bt.json | 108 +++ .../lavaland/raptor/care_for_young.bt.json | 50 ++ .../basic/lavaland/raptor/raptor_ai.bt.json | 221 ++++++ .../lavaland/raptor/raptor_ai_behavior.dm | 38 - .../lavaland/raptor/raptor_ai_controller.dm | 59 +- .../lavaland/raptor/raptor_ai_subtrees.dm | 79 +- .../lavaland/raptor/raptor_find_food.bt.json | 31 + .../basic/lavaland/raptor/raptor_flee.bt.json | 41 + .../raptor/raptor_heal_injured.bt.json | 36 + .../raptor/raptor_play_with_owner.bt.json | 28 + .../lavaland/raptor/raptor_trough.bt.json | 36 + .../basic/lavaland/tendril/tendril.bt.json | 124 +++ .../living/basic/lavaland/tendril/tendril.dm | 2 +- .../basic/lavaland/tendril/tendril_ai.dm | 35 +- .../basic/lavaland/watcher/watcher.bt.json | 133 ++++ .../basic/lavaland/watcher/watcher_ai.dm | 32 +- .../mob/living/basic/minebots/minebot.bt.json | 107 +++ .../mob/living/basic/minebots/minebot.dm | 6 +- .../mob/living/basic/minebots/minebot_ai.dm | 321 +++----- .../basic/minebots/minebot_combat.bt.json | 112 +++ .../basic/minebots/minebot_mining.bt.json | 193 +++++ .../mob/living/basic/pets/cat/bread_cat_ai.dm | 62 -- .../mob/living/basic/pets/cat/cat.bt.json | 275 +++++++ .../mob/living/basic/pets/cat/cat_ai.dm | 292 +------ .../living/basic/pets/cat/cat_bread.bt.json | 140 ++++ .../mob/living/basic/pets/cat/cat_bt.dm | 259 +++++++ .../living/basic/pets/cat/cat_cake.bt.json | 154 ++++ .../pets/cat/cat_decorate_donuts.bt.json | 29 + .../basic/pets/cat/cat_find_food.bt.json | 30 + .../basic/pets/cat/cat_haul_food.bt.json | 41 + .../basic/pets/cat/cat_hunt_mice.bt.json | 36 + .../basic/pets/cat/cat_reside_in_home.bt.json | 55 ++ .../basic/pets/cat/cat_turn_off_stove.bt.json | 30 + .../mob/living/basic/pets/cat/kitten.bt.json | 151 ++++ .../mob/living/basic/pets/cat/kitten_ai.dm | 67 -- .../modules/mob/living/basic/pets/dog/_dog.dm | 23 +- .../mob/living/basic/pets/dog/corgi.dm | 23 +- .../mob/living/basic/pets/dog/dog_subtypes.dm | 7 +- .../living/basic/pets/dog/guarddog.bt.json | 5 + .../modules/mob/living/basic/pets/fox.bt.json | 133 ++++ code/modules/mob/living/basic/pets/fox.dm | 17 +- .../mob/living/basic/pets/fox_docile.bt.json | 61 ++ .../basic/pets/gondolas/gondola.bt.json | 8 + .../mob/living/basic/pets/gondolas/gondola.dm | 2 +- .../mob/living/basic/pets/orbie/orbie.bt.json | 100 +++ .../mob/living/basic/pets/orbie/orbie_ai.dm | 95 +-- .../parrot/parrot_ai/_parrot_controller.dm | 37 +- .../parrot_ai/ghost_parrot_controller.dm | 37 +- .../pets/parrot/parrot_ai/parrot.bt.json | 190 +++++ .../parrot/parrot_ai/parrot_ghost.bt.json | 119 +++ .../parrot/parrot_ai/parrot_hoard.bt.json | 118 +++ .../pets/parrot/parrot_ai/parrot_hoarding.dm | 119 ++- .../pets/parrot/parrot_ai/parrot_perching.dm | 81 +- .../pets/parrot/parrot_ai/parroting_action.dm | 42 +- .../pets/parrot/parrot_ai/perching.bt.json | 85 ++ .../living/basic/pets/penguin/penguin.bt.json | 154 ++++ .../mob/living/basic/pets/penguin/penguin.dm | 3 +- .../living/basic/pets/penguin/penguin_ai.dm | 62 +- .../basic/pets/penguin/penguin_baby.bt.json | 118 +++ .../basic/pets/pet_cult/pet_cult.bt.json | 210 +++++ .../living/basic/pets/pet_cult/pet_cult_ai.dm | 208 +---- .../living/basic/pets/pet_cult/pet_cult_bt.dm | 83 ++ .../mob/living/basic/pets/sloth.bt.json | 58 ++ code/modules/mob/living/basic/pets/sloth.dm | 19 +- .../mob/living/basic/revolutionary.bt.json | 17 + .../modules/mob/living/basic/revolutionary.dm | 20 +- .../basic/ruin_defender/blob_of_flesh.dm | 6 +- .../ruin_defender/cybersun_ai_core.bt.json | 67 ++ .../basic/ruin_defender/cybersun_aicore.dm | 16 +- .../basic/ruin_defender/dark_wizard.bt.json | 90 +++ .../living/basic/ruin_defender/dark_wizard.dm | 9 +- .../mob/living/basic/ruin_defender/flesh.dm | 11 +- .../basic/ruin_defender/fleshblob.bt.json | 5 + .../basic/ruin_defender/living_floor.bt.json | 62 ++ .../basic/ruin_defender/living_floor.dm | 16 +- .../basic/ruin_defender/mad_piano.bt.json | 8 + .../living/basic/ruin_defender/mad_piano.dm | 9 +- .../living/basic/ruin_defender/mimic/mimic.dm | 10 +- .../basic/ruin_defender/mimic/mimic_ai.dm | 133 ++-- .../mimic/mimic_animator.bt.json | 153 ++++ .../ruin_defender/mimic/mimic_copy.bt.json | 96 +++ .../ruin_defender/mimic/mimic_crate.bt.json | 14 + .../ruin_defender/mimic/mimic_gun.bt.json | 130 ++++ .../basic/ruin_defender/skeleton.bt.json | 101 +++ .../living/basic/ruin_defender/skeleton.dm | 11 +- .../basic/ruin_defender/stickman.bt.json | 5 + .../living/basic/ruin_defender/stickman.dm | 17 +- .../ruin_defender/stickman_ranged.bt.json | 8 + .../basic/ruin_defender/wizard/wizard.bt.json | 107 +++ .../basic/ruin_defender/wizard/wizard_ai.dm | 39 +- .../living/basic/ruin_defender/zombie.bt.json | 28 + .../mob/living/basic/ruin_defender/zombie.dm | 26 +- .../basic/ruin_defender/zombie_stupid.bt.json | 19 + .../mob/living/basic/slime/ai/behaviours.dm | 67 +- .../mob/living/basic/slime/ai/controller.dm | 29 +- .../mob/living/basic/slime/ai/pet_command.dm | 8 +- .../slime/ai/pet_command_attack_slime.bt.json | 46 ++ .../mob/living/basic/slime/ai/slime.bt.json | 212 +++++ .../mob/living/basic/slime/ai/slime_bt.dm | 87 +++ .../mob/living/basic/slime/ai/subtrees.dm | 54 -- .../modules/mob/living/basic/slime/defense.dm | 2 +- .../mob/living/basic/snails/snail.bt.json | 59 ++ .../mob/living/basic/snails/snail_ai.dm | 57 +- .../mob/living/basic/space_fauna/ant.bt.json | 38 + .../mob/living/basic/space_fauna/ant.dm | 9 +- .../basic/space_fauna/bear/bear.bt.json | 215 ++++++ .../space_fauna/bear/bear_ai_behavior.dm | 26 +- .../basic/space_fauna/bear/bear_ai_subtree.dm | 26 +- .../space_fauna/carp/basic_carp_tree.bt.json | 109 +++ .../basic/space_fauna/carp/carp.bt.json | 8 + .../mob/living/basic/space_fauna/carp/carp.dm | 4 +- .../basic/space_fauna/carp/carp_abilities.dm | 2 +- .../basic/space_fauna/carp/carp_ai_actions.dm | 176 +++-- .../space_fauna/carp/carp_ai_migration.dm | 68 +- .../space_fauna/carp/carp_ai_rift_actions.dm | 209 +++-- .../space_fauna/carp/carp_combat.bt.json | 85 ++ .../space_fauna/carp/carp_controllers.dm | 109 +-- .../basic/space_fauna/carp/carp_flee.bt.json | 63 ++ .../space_fauna/carp/carp_migration.bt.json | 114 +++ .../space_fauna/carp/carp_passive.bt.json | 9 + .../carp/carp_passive_selection.bt.json | 25 + .../basic/space_fauna/carp/carp_pet.bt.json | 10 + .../space_fauna/carp/carp_ranged.bt.json | 8 + .../carp/carp_retaliate_selection.bt.json | 11 + .../carp/carp_target_selection.bt.json | 32 + .../basic/space_fauna/cat_butcherer.bt.json | 5 + .../living/basic/space_fauna/cat_surgeon.dm | 10 +- .../space_fauna/changeling/headslug.bt.json | 5 + .../basic/space_fauna/changeling/headslug.dm | 2 +- .../basic/space_fauna/eyeball/eyeball.bt.json | 183 +++++ .../eyeball/eyeball_ai_behavior.dm | 88 --- .../space_fauna/eyeball/eyeball_ai_subtree.dm | 31 +- .../basic/space_fauna/faithless.bt.json | 137 ++++ .../mob/living/basic/space_fauna/faithless.dm | 13 +- .../basic/space_fauna/garden_gnome.bt.json | 114 +++ .../living/basic/space_fauna/garden_gnome.dm | 9 +- .../living/basic/space_fauna/ghost.bt.json | 14 + .../mob/living/basic/space_fauna/ghost.dm | 8 +- .../basic/space_fauna/hivebot/hivebot.bt.json | 119 +++ .../space_fauna/hivebot/hivebot_behavior.dm | 68 +- .../hivebot/hivebot_mechanic.bt.json | 166 ++++ .../hivebot/hivebot_ranged.bt.json | 130 ++++ .../hivebot/hivebot_ranged_rapid.bt.json | 130 ++++ .../space_fauna/hivebot/hivebot_subtree.dm | 63 +- .../hivebot/relay_to_hive_partner.bt.json | 36 + .../basic/space_fauna/killer_tomato.bt.json | 8 + .../living/basic/space_fauna/killer_tomato.dm | 14 +- .../basic/space_fauna/lightgeist.bt.json | 5 + .../living/basic/space_fauna/lightgeist.dm | 9 +- .../meteor_heart/meteor_heart.bt.json | 73 ++ .../space_fauna/meteor_heart/meteor_heart.dm | 6 +- .../meteor_heart/meteor_heart_ai.dm | 42 +- .../living/basic/space_fauna/morph.bt.json | 5 + .../mob/living/basic/space_fauna/morph.dm | 8 +- .../living/basic/space_fauna/mushroom.bt.json | 103 +++ .../mob/living/basic/space_fauna/mushroom.dm | 15 +- .../paper_wizard/paper_wizard.bt.json | 121 +++ .../space_fauna/paper_wizard/paper_wizard.dm | 41 +- .../space_fauna/regal_rat/regal_rat.bt.json | 98 +++ .../regal_rat/regal_rat_actions.dm | 8 +- .../space_fauna/regal_rat/regal_rat_ai.dm | 29 +- .../mob/living/basic/space_fauna/roro.bt.json | 5 + .../mob/living/basic/space_fauna/roro.dm | 7 +- .../basic/space_fauna/snake/banded.bt.json | 92 +++ .../basic/space_fauna/snake/banded_snake.dm | 18 +- .../basic/space_fauna/snake/snake.bt.json | 92 +++ .../living/basic/space_fauna/snake/snake.dm | 17 +- .../basic/space_fauna/snake/snake_ai.dm | 6 - .../living/basic/space_fauna/spaceman.bt.json | 14 + .../mob/living/basic/space_fauna/spaceman.dm | 8 +- .../spider/giant_spider/giant_spider.bt.json | 133 ++++ .../spider/giant_spider/giant_spider_ai.dm | 56 +- .../giant_spider/giant_spider_pest.bt.json | 150 ++++ .../giant_spider_retaliate.bt.json | 78 ++ .../giant_spider/giant_spider_subtrees.dm | 89 --- .../giant_spider/giant_spider_weak.bt.json | 121 +++ .../spider/spiderlings/spiderling.bt.json | 69 ++ .../spider/spiderlings/spiderling.dm | 18 +- .../spider/young_spider/young_spider.bt.json | 149 ++++ .../spider/young_spider/young_spider.dm | 19 +- .../basic/space_fauna/statue/mannequin.dm | 43 +- .../statue/stares_at_people.bt.json | 44 ++ .../basic/space_fauna/statue/statue.bt.json | 125 +++ .../living/basic/space_fauna/statue/statue.dm | 10 +- .../statue/suspicious_mannequin.bt.json | 114 +++ .../space_fauna/supermatter_spider.bt.json | 8 + .../basic/space_fauna/supermatter_spider.dm | 20 +- .../space_fauna/wumborian_fugu/inflation.dm | 4 +- .../wumborian_fugu/wumborian_ai.dm | 23 +- .../wumborian_fugu/wumborian_fugu.bt.json | 86 +++ .../mob/living/basic/stoats/stoat.bt.json | 123 +++ .../mob/living/basic/stoats/stoat_ai.dm | 24 +- .../mob/living/basic/trader/trader.bt.json | 154 ++++ .../modules/mob/living/basic/trader/trader.dm | 2 +- .../mob/living/basic/trader/trader_ai.dm | 88 +-- code/modules/mob/living/basic/tree.bt.json | 8 + code/modules/mob/living/basic/tree.dm | 12 +- .../mob/living/basic/trooper/burst.bt.json | 9 + .../mob/living/basic/trooper/nanotrasen.dm | 2 +- .../mob/living/basic/trooper/peaceful.bt.json | 138 ++++ .../basic/trooper/peaceful_burst.bt.json | 140 ++++ .../mob/living/basic/trooper/ranged.bt.json | 10 + .../living/basic/trooper/shotgunner.bt.json | 10 + .../mob/living/basic/trooper/trooper.bt.json | 118 +++ .../mob/living/basic/trooper/trooper_ai.dm | 117 ++- .../basic/trooper/trooper_ranged.bt.json | 137 ++++ .../mob/living/basic/turtle/turtle.bt.json | 169 ++++ .../modules/mob/living/basic/turtle/turtle.dm | 12 +- .../mob/living/basic/turtle/turtle_ai.dm | 76 +- .../mob/living/basic/vermin/axolotl.bt.json | 5 + .../mob/living/basic/vermin/axolotl.dm | 2 +- .../mob/living/basic/vermin/butterfly.bt.json | 5 + .../mob/living/basic/vermin/butterfly.dm | 3 +- .../basic/vermin/cockroach/cockroach.bt.json | 80 ++ .../vermin/cockroach/cockroach_aggro.bt.json | 145 ++++ .../basic/vermin/cockroach/cockroach_ai.dm | 54 +- .../cockroach/cockroach_glockroach.bt.json | 143 ++++ .../cockroach/cockroach_mobroach.bt.json | 143 ++++ .../mob/living/basic/vermin/crab.bt.json | 126 +++ code/modules/mob/living/basic/vermin/crab.dm | 11 +- .../mob/living/basic/vermin/eat_cable.bt.json | 58 ++ .../living/basic/vermin/eat_cheese.bt.json | 51 ++ .../mob/living/basic/vermin/frog.bt.json | 60 ++ code/modules/mob/living/basic/vermin/frog.dm | 28 +- .../basic/vermin/frog_engage_target.bt.json | 90 +++ .../mob/living/basic/vermin/lizard.bt.json | 35 + .../modules/mob/living/basic/vermin/lizard.dm | 6 +- .../basic/vermin/mothroach/mothroach.bt.json | 81 ++ .../basic/vermin/mothroach/mothroach_ai.dm | 21 +- .../mob/living/basic/vermin/mouse.bt.json | 189 +++++ code/modules/mob/living/basic/vermin/mouse.dm | 57 +- .../mob/living/basic/vermin/mouse_rat.bt.json | 217 ++++++ .../vermin/play_instrument_on_floor.bt.json | 78 ++ .../mob/living/basic/vermin/space_bat.bt.json | 5 + .../mob/living/basic/vermin/space_bat.dm | 7 +- .../living/basic/vermin/suicide_frog.bt.json | 71 ++ .../mob/living/basic/vermin/trash.bt.json | 61 ++ code/modules/mob/living/living.dm | 11 +- .../simple_animal/bot/bot_announcement.dm | 2 +- .../hostile/megafauna/demonic_frost_miner.dm | 2 +- .../hostile/megafauna/hierophant.dm | 2 +- code/modules/mob/mob.dm | 3 - code/modules/paperwork/paper.dm | 2 + code/modules/projectiles/guns/magic/staff.dm | 2 +- .../guns/magic/wands/wand_rebel.dm | 2 +- .../xenobiology/crossbreeding/_mobs.dm | 8 +- code/modules/unit_tests/_unit_tests.dm | 1 - .../ensure_subtree_operational_datum.dm | 64 -- code/modules/unit_tests/mouse_bite_cable.dm | 35 +- tgstation.dme | 278 +++++-- .../interfaces/BehaviorTreeViewer/index.tsx | 642 ++++++++++++++++ .../interfaces/BehaviorTreeViewer/types.ts | 41 + tools/build/build.ts | 14 + tools/build_bt.py | 349 +++++++++ tools/ci/run_server.sh | 1 + tools/deploy.sh | 2 + 1272 files changed, 37929 insertions(+), 13503 deletions(-) create mode 100644 .github/workflows/check_bt_compiled.yml create mode 100644 build/behavior_trees/datums/ai/babies/make_babies.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/bane/bane.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/basic_ai_behaviors/consider_venting.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/basic_subtrees/escape_captivity.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/basic_subtrees/escape_captivity_pacifist.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/basic_subtrees/find_paper_and_write.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/basic_subtrees/go_for_swim.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/basic_subtrees/random_speech_loop.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/basic_subtrees/run_away_from_target.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/basic_subtrees/run_away_from_target_run_and_shoot.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/basic_subtrees/skittish_brawler_combat.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/basic_subtrees/tip_reaction.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_attack.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_attack_dog.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_attack_minebot.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_attack_ranged_glockroach.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_beehive.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_breed.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_fetch.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_fish.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_follow.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_mine_walls.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_move_to.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_play_dead.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_protect_owner.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_protect_owner_ranged_glockroach.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_scatter.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_stay.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_swirl.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_targeted_ability.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_untargeted_ability.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/simple_ability.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/simple_ability_combat.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/simple_ability_melee.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/simple_ability_melee_combat.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/simple_ability_ranged.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/simple_ability_ranged_combat.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/simple_ability_retaliate.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/simple_ability_retaliate_combat.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/simple_capricious.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/simple_capricious_combat.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/simple_fearful.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/simple_fearful_combat.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/simple_goon.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/simple_hostile.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/simple_hostile_combat.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/simple_hostile_combat_with_retaliate.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/simple_hostile_obstacles.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/simple_hostile_obstacles_combat.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/simple_ranged.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/simple_ranged_combat.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/simple_ranged_retaliate.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/simple_ranged_retaliate_combat.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/simple_retaliate.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/simple_retaliate_combat.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/simple_skirmisher.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/simple_skirmisher_combat.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/simple_skittish.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/simple_skittish_combat.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/basic_mobs/talk.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/bots/bot_patrol.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/bots/bot_respond_to_summon.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/bots/bot_salute_authority.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/cursed/cursed.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/dog/dog.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/dog/dog_corgi.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/dog/dog_harassment.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/generic_hunger.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/generic_play_instrument.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/generic_subtrees/basic_find_target.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/generic_subtrees/capricious_pick_target.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/generic_subtrees/climb_tree.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/generic_subtrees/find_food.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/generic_subtrees/find_partner.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/generic_subtrees/find_stealable_object.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/generic_subtrees/forage_and_retaliate.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/generic_subtrees/move_to_and_eat.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/generic_subtrees/move_to_and_hunt.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/generic_subtrees/move_to_reinforce.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/generic_subtrees/pick_retaliate_target.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/generic_subtrees/random_walk.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/generic_subtrees/skittish_and_speak.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/generic_subtrees/steal_and_flee.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/hauntium/haunted.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/monkey/monkey.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/monkey/monkey_combat.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/monkey/monkey_find_weapon.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/monkey/monkey_serve_food.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/monkey/monkey_shenanigans.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/objects/vending_machines/vending_machine.bt.compiled.json create mode 100644 build/behavior_trees/datums/ai/robot_customer/robot_customer.bt.compiled.json create mode 100644 build/behavior_trees/modules/bitrunning/antagonists/netguardian.bt.compiled.json create mode 100644 build/behavior_trees/modules/bitrunning/virtual_domain/domains/crewman.bt.compiled.json create mode 100644 build/behavior_trees/modules/bitrunning/virtual_domain/domains/crewman_hostile.bt.compiled.json create mode 100644 build/behavior_trees/modules/bitrunning/virtual_domain/domains/crewman_hostile_ranged.bt.compiled.json create mode 100644 build/behavior_trees/modules/bitrunning/virtual_domain/domains/crewman_ranged.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/alien/alien.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/alien/basic_alien.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/alien/drone.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/alien/lay_alien_egg.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/alien/melee_alien_combat.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/alien/plant_alien_weeds.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/alien/queen.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/alien/ranged_alien_combat.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/alien/sentinel.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/blob_minions/blob_spore.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/blob_minions/blob_zombie.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/blob_minions/blobbernaut.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/boss/blood_drunk_miner/blood_drunk_miner.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/boss/blood_drunk_miner/blood_drunk_miner_combat.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/boss/thing/thing_aoe.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/boss/thing/thing_boss.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/boss/thing/thing_melee.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/bots/bot.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/bots/cleanbot/clean_pet_target.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/bots/cleanbot/cleanbot.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/bots/dedbot.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/bots/ed209/ed209.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/bots/ed209/ed209_syndicate.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/bots/firebot/firebot.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/bots/honkbots/honkbot.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/bots/honkbots/honkbot_slip.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/bots/hygienebot/hygienebot.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/bots/medbot/medbot.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/bots/medbot/medbot_find_and_announce_crit.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/bots/medbot/medbot_treat_patient.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/bots/mulebot/mulebot.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/bots/repairbot/repairbot.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/bots/repairbot/repairbot_emagged.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/bots/repairbot/repairbot_find_target.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/bots/repairbot/repairbot_repair_target.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/bots/secbot/secbot.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/bots/vibebot/vibebot.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/clown/clown.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/cult/constructs/artificer.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/cult/constructs/juggernaut.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/cult/constructs/proteon.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/cult/constructs/wraith.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/cytology/vatbeast.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/farm_animals/bee/bee.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/farm_animals/bee/find_hive.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/farm_animals/bee/pollinate_target.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/farm_animals/bee/queen_bee.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/farm_animals/bee/transition_hive_status.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/farm_animals/chicken/chick.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/farm_animals/chicken/chicken.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/farm_animals/cow/cow.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/farm_animals/cow/cow_moonicorn.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/farm_animals/cow/cow_wisdom.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/farm_animals/deer/deer.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/farm_animals/goat/goat.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/farm_animals/goose/forage_for_goose_food.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/farm_animals/goose/goose.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/farm_animals/goose/goose_calm.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/farm_animals/gorilla/gorilla.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/farm_animals/pig.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/farm_animals/pony.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/farm_animals/rabbit.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/farm_animals/sheep.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/festivus_pole.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/heretic/raw_prophet.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/heretic/rust_walker.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/heretic/stalker.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/heretic/star_gazer.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/icemoon/ice_demon/ice_demon.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/icemoon/ice_demon/ice_demon_afterimage.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/icemoon/ice_demon/ice_demon_combat.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/icemoon/ice_demon/ice_demon_flee_from_fire.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/icemoon/ice_whelp/ice_whelp.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/icemoon/polar_bear/polar.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/icemoon/wolf/wolf.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/illusion/escape.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/illusion/retaliate.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/jungle/human_trap.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/jungle/leaper/leaper.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/jungle/mega_arachnid/mega_arachnid.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/jungle/mega_arachnid/mega_arachnid_combat.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/jungle/seedling/seedling.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/jungle/seedling/seedling_meanie.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/lavaland/basilisk/basilisk.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/lavaland/bileworm/bileworm.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/lavaland/brimdemon/brimdemon.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/lavaland/goldgrub/babygrub.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/lavaland/goldgrub/goldgrub.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/lavaland/goldgrub/grub_eat_target.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/lavaland/goliath/goliath.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/lavaland/gutlunchers/gutlunch_baby.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/lavaland/gutlunchers/gutlunch_milk.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/lavaland/gutlunchers/gutlunch_warrior.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/lavaland/hivelord/hivelord.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/lavaland/legion/legion.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/lavaland/legion/legion_brood.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/lavaland/legion/legion_monkey.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity_calm.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity_capricious.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/lavaland/mook/bard.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/lavaland/mook/bard_find_targets.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/lavaland/mook/bard_play_music.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/lavaland/mook/chief_find_targets.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/lavaland/mook/chief_issue_commands.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/lavaland/mook/chief_manage_village.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/lavaland/mook/generic_mook_behavior.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/lavaland/mook/go_mining.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/lavaland/mook/heal_injured.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/lavaland/mook/mook.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/lavaland/mook/support.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/lavaland/mook/support_find_targets.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/lavaland/mook/tribal_chief.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/lavaland/mook/worker_find_targets.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/lavaland/node_drone/node_drone.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/lavaland/raptor/baby_raptor.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/lavaland/raptor/care_for_young.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/lavaland/raptor/raptor_ai.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/lavaland/raptor/raptor_find_food.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/lavaland/raptor/raptor_flee.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/lavaland/raptor/raptor_heal_injured.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/lavaland/raptor/raptor_play_with_owner.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/lavaland/raptor/raptor_trough.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/lavaland/tendril/tendril.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/lavaland/watcher/watcher.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/minebots/minebot.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/minebots/minebot_combat.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/minebots/minebot_mining.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/pets/cat/cat.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/pets/cat/cat_bread.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/pets/cat/cat_cake.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/pets/cat/cat_decorate_donuts.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/pets/cat/cat_find_food.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/pets/cat/cat_haul_food.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/pets/cat/cat_hunt_mice.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/pets/cat/cat_reside_in_home.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/pets/cat/cat_turn_off_stove.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/pets/cat/kitten.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/pets/dog/guarddog.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/pets/fox.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/pets/fox_docile.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/pets/gondolas/gondola.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/pets/orbie/orbie.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/pets/parrot/parrot_ai/parrot.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/pets/parrot/parrot_ai/parrot_ghost.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/pets/parrot/parrot_ai/parrot_hoard.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/pets/parrot/parrot_ai/perching.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/pets/penguin/penguin.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/pets/penguin/penguin_baby.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/pets/pet_cult/pet_cult.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/pets/sloth.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/revolutionary.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/ruin_defender/cybersun_ai_core.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/ruin_defender/dark_wizard.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/ruin_defender/fleshblob.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/ruin_defender/living_floor.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/ruin_defender/mad_piano.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/ruin_defender/mimic/mimic_animator.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/ruin_defender/mimic/mimic_copy.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/ruin_defender/mimic/mimic_crate.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/ruin_defender/mimic/mimic_gun.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/ruin_defender/skeleton.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/ruin_defender/stickman.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/ruin_defender/stickman_ranged.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/ruin_defender/wizard/wizard.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/ruin_defender/zombie.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/ruin_defender/zombie_stupid.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/slime/ai/pet_command_attack_slime.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/slime/ai/slime.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/snails/snail.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/space_fauna/ant.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/space_fauna/bear/bear.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/space_fauna/carp/basic_carp_tree.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/space_fauna/carp/carp.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/space_fauna/carp/carp_combat.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/space_fauna/carp/carp_flee.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/space_fauna/carp/carp_migration.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/space_fauna/carp/carp_passive.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/space_fauna/carp/carp_passive_selection.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/space_fauna/carp/carp_pet.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/space_fauna/carp/carp_ranged.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/space_fauna/carp/carp_retaliate_selection.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/space_fauna/carp/carp_target_selection.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/space_fauna/cat_butcherer.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/space_fauna/changeling/headslug.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/space_fauna/eyeball/eyeball.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/space_fauna/faithless.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/space_fauna/garden_gnome.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/space_fauna/ghost.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/space_fauna/hivebot/hivebot.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/space_fauna/hivebot/hivebot_mechanic.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/space_fauna/hivebot/hivebot_ranged.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/space_fauna/hivebot/hivebot_ranged_rapid.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/space_fauna/hivebot/relay_to_hive_partner.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/space_fauna/killer_tomato.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/space_fauna/lightgeist.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/space_fauna/meteor_heart/meteor_heart.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/space_fauna/morph.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/space_fauna/mushroom.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/space_fauna/paper_wizard/paper_wizard.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/space_fauna/regal_rat/regal_rat.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/space_fauna/roro.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/space_fauna/snake/banded.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/space_fauna/snake/snake.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/space_fauna/spaceman.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider_pest.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider_retaliate.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider_weak.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/space_fauna/spider/spiderlings/spiderling.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/space_fauna/spider/young_spider/young_spider.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/space_fauna/statue/stares_at_people.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/space_fauna/statue/statue.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/space_fauna/statue/suspicious_mannequin.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/space_fauna/supermatter_spider.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/space_fauna/wumborian_fugu/wumborian_fugu.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/stoats/stoat.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/trader/trader.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/tree.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/trooper/burst.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/trooper/peaceful.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/trooper/peaceful_burst.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/trooper/ranged.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/trooper/shotgunner.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/trooper/trooper.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/trooper/trooper_ranged.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/turtle/turtle.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/vermin/axolotl.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/vermin/butterfly.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/vermin/cockroach/cockroach.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/vermin/cockroach/cockroach_aggro.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/vermin/cockroach/cockroach_glockroach.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/vermin/cockroach/cockroach_mobroach.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/vermin/crab.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/vermin/eat_cable.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/vermin/eat_cheese.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/vermin/frog.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/vermin/frog_engage_target.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/vermin/lizard.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/vermin/mothroach/mothroach.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/vermin/mouse.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/vermin/mouse_rat.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/vermin/play_instrument_on_floor.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/vermin/space_bat.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/vermin/suicide_frog.bt.compiled.json create mode 100644 build/behavior_trees/modules/mob/living/basic/vermin/trash.bt.compiled.json create mode 100644 code/__DEFINES/ai/behavior_trees.dm delete mode 100644 code/_globalvars/lists/basic_ai.dm create mode 100644 code/controllers/subsystem/ai_controllers_low_priority.dm delete mode 100644 code/controllers/subsystem/ai_idle_controllers.dm delete mode 100644 code/controllers/subsystem/processing/ai_behaviors.dm delete mode 100644 code/controllers/subsystem/processing/ai_idle_behaviors.dm delete mode 100644 code/controllers/subsystem/unplanned_ai_idle_controllers.dm delete mode 100644 code/controllers/subsystem/unplanned_controllers.dm create mode 100644 code/datums/ai/_ai_bt_composites.dm create mode 100644 code/datums/ai/_ai_bt_decorators.dm create mode 100644 code/datums/ai/_ai_bt_node.dm create mode 100644 code/datums/ai/_ai_bt_subtree.dm delete mode 100644 code/datums/ai/_ai_planning_subtree.dm delete mode 100644 code/datums/ai/babies/babies_subtrees.dm create mode 100644 code/datums/ai/babies/make_babies.bt.json create mode 100644 code/datums/ai/bane/bane.bt.json delete mode 100644 code/datums/ai/bane/bane_behaviors.dm delete mode 100644 code/datums/ai/bane/bane_subtrees.dm create mode 100644 code/datums/ai/basic_mobs/basic_ai_behaviors/call_reinforcements.dm delete mode 100644 code/datums/ai/basic_mobs/basic_ai_behaviors/clear_key.dm delete mode 100644 code/datums/ai/basic_mobs/basic_ai_behaviors/climb_tree.dm create mode 100644 code/datums/ai/basic_mobs/basic_ai_behaviors/consider_venting.bt.json delete mode 100644 code/datums/ai/basic_mobs/basic_ai_behaviors/emote_with_target.dm create mode 100644 code/datums/ai/basic_mobs/basic_ai_behaviors/find_flee_location.dm delete mode 100644 code/datums/ai/basic_mobs/basic_ai_behaviors/interact_with_target.dm delete mode 100644 code/datums/ai/basic_mobs/basic_ai_behaviors/pick_up_item.dm create mode 100644 code/datums/ai/basic_mobs/basic_ai_behaviors/play_dead.dm delete mode 100644 code/datums/ai/basic_mobs/basic_ai_behaviors/pull_target.dm delete mode 100644 code/datums/ai/basic_mobs/basic_ai_behaviors/run_away_from_target.dm delete mode 100644 code/datums/ai/basic_mobs/basic_ai_behaviors/set_travel_destination.dm delete mode 100644 code/datums/ai/basic_mobs/basic_ai_behaviors/step_towards_turf.dm delete mode 100644 code/datums/ai/basic_mobs/basic_ai_behaviors/travel_towards.dm delete mode 100644 code/datums/ai/basic_mobs/basic_ai_behaviors/unbuckle_mob.dm create mode 100644 code/datums/ai/basic_mobs/basic_ai_behaviors/use_mob_ability.dm delete mode 100644 code/datums/ai/basic_mobs/basic_ai_behaviors/wounded_targeting.dm delete mode 100644 code/datums/ai/basic_mobs/basic_subtrees/attack_adjacent_target.dm delete mode 100644 code/datums/ai/basic_mobs/basic_subtrees/attack_obstacle_in_path.dm delete mode 100644 code/datums/ai/basic_mobs/basic_subtrees/call_reinforcements.dm delete mode 100644 code/datums/ai/basic_mobs/basic_subtrees/climb_tree.dm delete mode 100644 code/datums/ai/basic_mobs/basic_subtrees/drag_items.dm create mode 100644 code/datums/ai/basic_mobs/basic_subtrees/escape_captivity.bt.json create mode 100644 code/datums/ai/basic_mobs/basic_subtrees/escape_captivity_pacifist.bt.json delete mode 100644 code/datums/ai/basic_mobs/basic_subtrees/express_happiness.dm delete mode 100644 code/datums/ai/basic_mobs/basic_subtrees/find_food.dm create mode 100644 code/datums/ai/basic_mobs/basic_subtrees/find_paper_and_write.bt.json delete mode 100644 code/datums/ai/basic_mobs/basic_subtrees/find_parent.dm delete mode 100644 code/datums/ai/basic_mobs/basic_subtrees/find_targets_prioritize_traits.dm delete mode 100644 code/datums/ai/basic_mobs/basic_subtrees/fishing.dm delete mode 100644 code/datums/ai/basic_mobs/basic_subtrees/flee_target.dm create mode 100644 code/datums/ai/basic_mobs/basic_subtrees/generic_hunger.dm create mode 100644 code/datums/ai/basic_mobs/basic_subtrees/generic_play_instrument.dm create mode 100644 code/datums/ai/basic_mobs/basic_subtrees/go_for_swim.bt.json delete mode 100644 code/datums/ai/basic_mobs/basic_subtrees/maintain_distance.dm delete mode 100644 code/datums/ai/basic_mobs/basic_subtrees/mine_walls.dm delete mode 100644 code/datums/ai/basic_mobs/basic_subtrees/opportunistic_ventcrawler.dm delete mode 100644 code/datums/ai/basic_mobs/basic_subtrees/play_with_owners.dm delete mode 100644 code/datums/ai/basic_mobs/basic_subtrees/prepare_travel_to_destination.dm create mode 100644 code/datums/ai/basic_mobs/basic_subtrees/random_speech_loop.bt.json create mode 100644 code/datums/ai/basic_mobs/basic_subtrees/random_speech_loop.dm delete mode 100644 code/datums/ai/basic_mobs/basic_subtrees/ranged_skirmish.dm create mode 100644 code/datums/ai/basic_mobs/basic_subtrees/run_away_from_target.bt.json create mode 100644 code/datums/ai/basic_mobs/basic_subtrees/run_away_from_target.dm create mode 100644 code/datums/ai/basic_mobs/basic_subtrees/run_away_from_target_run_and_shoot.bt.json delete mode 100644 code/datums/ai/basic_mobs/basic_subtrees/run_emote.dm delete mode 100644 code/datums/ai/basic_mobs/basic_subtrees/shapechange_ambush.dm delete mode 100644 code/datums/ai/basic_mobs/basic_subtrees/simple_attack_target.dm delete mode 100644 code/datums/ai/basic_mobs/basic_subtrees/simple_find_nearest_target_to_flee.dm delete mode 100644 code/datums/ai/basic_mobs/basic_subtrees/simple_find_target.dm delete mode 100644 code/datums/ai/basic_mobs/basic_subtrees/simple_find_wounded_target.dm create mode 100644 code/datums/ai/basic_mobs/basic_subtrees/skittish_brawler_combat.bt.json create mode 100644 code/datums/ai/basic_mobs/basic_subtrees/skittish_brawler_combat.dm delete mode 100644 code/datums/ai/basic_mobs/basic_subtrees/sleep_with_no_target.dm delete mode 100644 code/datums/ai/basic_mobs/basic_subtrees/speech_subtree.dm delete mode 100644 code/datums/ai/basic_mobs/basic_subtrees/stare_at_thing.dm delete mode 100644 code/datums/ai/basic_mobs/basic_subtrees/target_retaliate.dm delete mode 100644 code/datums/ai/basic_mobs/basic_subtrees/targeted_mob_ability.dm delete mode 100644 code/datums/ai/basic_mobs/basic_subtrees/teleport_away_from_target.dm create mode 100644 code/datums/ai/basic_mobs/basic_subtrees/tip_reaction.bt.json delete mode 100644 code/datums/ai/basic_mobs/basic_subtrees/tipped_subtree.dm delete mode 100644 code/datums/ai/basic_mobs/basic_subtrees/travel_to_point.dm delete mode 100644 code/datums/ai/basic_mobs/basic_subtrees/use_mob_ability.dm delete mode 100644 code/datums/ai/basic_mobs/pet_commands/fetch.dm create mode 100644 code/datums/ai/basic_mobs/pet_commands/pet_command_attack.bt.json create mode 100644 code/datums/ai/basic_mobs/pet_commands/pet_command_attack_dog.bt.json create mode 100644 code/datums/ai/basic_mobs/pet_commands/pet_command_attack_minebot.bt.json create mode 100644 code/datums/ai/basic_mobs/pet_commands/pet_command_attack_ranged_glockroach.bt.json create mode 100644 code/datums/ai/basic_mobs/pet_commands/pet_command_beehive.bt.json create mode 100644 code/datums/ai/basic_mobs/pet_commands/pet_command_breed.bt.json create mode 100644 code/datums/ai/basic_mobs/pet_commands/pet_command_bt.dm create mode 100644 code/datums/ai/basic_mobs/pet_commands/pet_command_fetch.bt.json create mode 100644 code/datums/ai/basic_mobs/pet_commands/pet_command_fish.bt.json create mode 100644 code/datums/ai/basic_mobs/pet_commands/pet_command_follow.bt.json create mode 100644 code/datums/ai/basic_mobs/pet_commands/pet_command_mine_walls.bt.json create mode 100644 code/datums/ai/basic_mobs/pet_commands/pet_command_move_to.bt.json delete mode 100644 code/datums/ai/basic_mobs/pet_commands/pet_command_planning.dm create mode 100644 code/datums/ai/basic_mobs/pet_commands/pet_command_play_dead.bt.json create mode 100644 code/datums/ai/basic_mobs/pet_commands/pet_command_protect_owner.bt.json create mode 100644 code/datums/ai/basic_mobs/pet_commands/pet_command_protect_owner_ranged_glockroach.bt.json create mode 100644 code/datums/ai/basic_mobs/pet_commands/pet_command_scatter.bt.json create mode 100644 code/datums/ai/basic_mobs/pet_commands/pet_command_stay.bt.json create mode 100644 code/datums/ai/basic_mobs/pet_commands/pet_command_swirl.bt.json create mode 100644 code/datums/ai/basic_mobs/pet_commands/pet_command_targeted_ability.bt.json create mode 100644 code/datums/ai/basic_mobs/pet_commands/pet_command_untargeted_ability.bt.json delete mode 100644 code/datums/ai/basic_mobs/pet_commands/pet_follow_friend.dm delete mode 100644 code/datums/ai/basic_mobs/pet_commands/play_dead.dm create mode 100644 code/datums/ai/basic_mobs/simple_ability.bt.json create mode 100644 code/datums/ai/basic_mobs/simple_ability_combat.bt.json create mode 100644 code/datums/ai/basic_mobs/simple_ability_melee.bt.json create mode 100644 code/datums/ai/basic_mobs/simple_ability_melee_combat.bt.json create mode 100644 code/datums/ai/basic_mobs/simple_ability_ranged.bt.json create mode 100644 code/datums/ai/basic_mobs/simple_ability_ranged_combat.bt.json create mode 100644 code/datums/ai/basic_mobs/simple_ability_retaliate.bt.json create mode 100644 code/datums/ai/basic_mobs/simple_ability_retaliate_combat.bt.json create mode 100644 code/datums/ai/basic_mobs/simple_capricious.bt.json create mode 100644 code/datums/ai/basic_mobs/simple_capricious_combat.bt.json create mode 100644 code/datums/ai/basic_mobs/simple_fearful.bt.json create mode 100644 code/datums/ai/basic_mobs/simple_fearful_combat.bt.json create mode 100644 code/datums/ai/basic_mobs/simple_goon.bt.json create mode 100644 code/datums/ai/basic_mobs/simple_hostile.bt.json create mode 100644 code/datums/ai/basic_mobs/simple_hostile_combat.bt.json create mode 100644 code/datums/ai/basic_mobs/simple_hostile_combat_with_retaliate.bt.json create mode 100644 code/datums/ai/basic_mobs/simple_hostile_obstacles.bt.json create mode 100644 code/datums/ai/basic_mobs/simple_hostile_obstacles_combat.bt.json create mode 100644 code/datums/ai/basic_mobs/simple_ranged.bt.json create mode 100644 code/datums/ai/basic_mobs/simple_ranged_combat.bt.json create mode 100644 code/datums/ai/basic_mobs/simple_ranged_retaliate.bt.json create mode 100644 code/datums/ai/basic_mobs/simple_ranged_retaliate_combat.bt.json create mode 100644 code/datums/ai/basic_mobs/simple_retaliate.bt.json create mode 100644 code/datums/ai/basic_mobs/simple_retaliate_combat.bt.json create mode 100644 code/datums/ai/basic_mobs/simple_skirmisher.bt.json create mode 100644 code/datums/ai/basic_mobs/simple_skirmisher_combat.bt.json create mode 100644 code/datums/ai/basic_mobs/simple_skittish.bt.json create mode 100644 code/datums/ai/basic_mobs/simple_skittish_combat.bt.json create mode 100644 code/datums/ai/basic_mobs/talk.bt.json create mode 100644 code/datums/ai/basic_mobs/target_sources/_target_source.dm create mode 100644 code/datums/ai/basic_mobs/target_sources/held_items_then_oview.dm create mode 100644 code/datums/ai/basic_mobs/target_sources/held_items_typed.dm create mode 100644 code/datums/ai/basic_mobs/target_sources/mobs_in_oview.dm create mode 100644 code/datums/ai/basic_mobs/target_sources/near_village_humans.dm create mode 100644 code/datums/ai/basic_mobs/target_sources/oview_single_type.dm create mode 100644 code/datums/ai/basic_mobs/target_sources/oview_typed_from_bb_key.dm create mode 100644 code/datums/ai/basic_mobs/target_sources/range_turfs_typecache_visible.dm create mode 100644 code/datums/ai/basic_mobs/target_sources/slime_source.dm create mode 100644 code/datums/ai/basic_mobs/target_sources/turfs_in_oview.dm create mode 100644 code/datums/ai/basic_mobs/targeting_strategies/accessible_cable.dm create mode 100644 code/datums/ai/basic_mobs/targeting_strategies/ally_mob.dm create mode 100644 code/datums/ai/basic_mobs/targeting_strategies/baby_raptor.dm create mode 100644 code/datums/ai/basic_mobs/targeting_strategies/beamable_hydro.dm create mode 100644 code/datums/ai/basic_mobs/targeting_strategies/befriendable_cultist.dm create mode 100644 code/datums/ai/basic_mobs/targeting_strategies/cat_food.dm create mode 100644 code/datums/ai/basic_mobs/targeting_strategies/chargeable_apc.dm create mode 100644 code/datums/ai/basic_mobs/targeting_strategies/conscious_human.dm create mode 100644 code/datums/ai/basic_mobs/targeting_strategies/conscious_mob.dm create mode 100644 code/datums/ai/basic_mobs/targeting_strategies/conscious_snail.dm create mode 100644 code/datums/ai/basic_mobs/targeting_strategies/damaged_eyes.dm create mode 100644 code/datums/ai/basic_mobs/targeting_strategies/damaged_machine.dm create mode 100644 code/datums/ai/basic_mobs/targeting_strategies/dead_mob.dm create mode 100644 code/datums/ai/basic_mobs/targeting_strategies/decorated_donut.dm create mode 100644 code/datums/ai/basic_mobs/targeting_strategies/drillable_ice.dm create mode 100644 code/datums/ai/basic_mobs/targeting_strategies/empty_paper.dm create mode 100644 code/datums/ai/basic_mobs/targeting_strategies/finished_stove.dm create mode 100644 code/datums/ai/basic_mobs/targeting_strategies/food_or_drink.dm create mode 100644 code/datums/ai/basic_mobs/targeting_strategies/goliath_diggable_turf.dm create mode 100644 code/datums/ai/basic_mobs/targeting_strategies/goose_edible.dm create mode 100644 code/datums/ai/basic_mobs/targeting_strategies/huntable.dm create mode 100644 code/datums/ai/basic_mobs/targeting_strategies/huntable_mouse.dm create mode 100644 code/datums/ai/basic_mobs/targeting_strategies/injured_mob.dm create mode 100644 code/datums/ai/basic_mobs/targeting_strategies/injured_raptor.dm create mode 100644 code/datums/ai/basic_mobs/targeting_strategies/legged_conscious_human.dm create mode 100644 code/datums/ai/basic_mobs/targeting_strategies/living_not_dead.dm create mode 100644 code/datums/ai/basic_mobs/targeting_strategies/non_stump_tree.dm create mode 100644 code/datums/ai/basic_mobs/targeting_strategies/pickup_item.dm create mode 100644 code/datums/ai/basic_mobs/targeting_strategies/playable_deer.dm create mode 100644 code/datums/ai/basic_mobs/targeting_strategies/playable_synthesizer.dm create mode 100644 code/datums/ai/basic_mobs/targeting_strategies/pollinatable_hydro.dm create mode 100644 code/datums/ai/basic_mobs/targeting_strategies/raptor_trough.dm create mode 100644 code/datums/ai/basic_mobs/targeting_strategies/slime_food.dm create mode 100644 code/datums/ai/basic_mobs/targeting_strategies/sniffable_hydro.dm create mode 100644 code/datums/ai/basic_mobs/targeting_strategies/stealable_item.dm create mode 100644 code/datums/ai/basic_mobs/targeting_strategies/stocked_beehive.dm create mode 100644 code/datums/ai/basic_mobs/targeting_strategies/treatable_hydro.dm create mode 100644 code/datums/ai/basic_mobs/targeting_strategies/trough_with_ore.dm create mode 100644 code/datums/ai/basic_mobs/targeting_strategies/unbroken_light.dm create mode 100644 code/datums/ai/basic_mobs/targeting_strategies/uncarried_egg.dm create mode 100644 code/datums/ai/basic_mobs/targeting_strategies/unlit_bonfire.dm create mode 100644 code/datums/ai/basic_mobs/targeting_strategies/valid_cat_home.dm create mode 100644 code/datums/ai/basic_mobs/targeting_strategies/valid_kitten.dm create mode 100644 code/datums/ai/basic_mobs/targeting_strategies/walkable_turf.dm create mode 100644 code/datums/ai/basic_mobs/targeting_strategies/water_dispenser.dm create mode 100644 code/datums/ai/basic_mobs/targeting_strategies/working_machine.dm create mode 100644 code/datums/ai/bots/bot_decorators.dm create mode 100644 code/datums/ai/bots/bot_patrol.bt.json create mode 100644 code/datums/ai/bots/bot_respond_to_summon.bt.json create mode 100644 code/datums/ai/bots/bot_salute_authority.bt.json create mode 100644 code/datums/ai/bots/bot_subtrees.dm create mode 100644 code/datums/ai/bt_viewer.dm create mode 100644 code/datums/ai/cursed/cursed.bt.json delete mode 100644 code/datums/ai/cursed/cursed_subtrees.dm create mode 100644 code/datums/ai/dog/dog.bt.json delete mode 100644 code/datums/ai/dog/dog_behaviors.dm create mode 100644 code/datums/ai/dog/dog_bt.dm create mode 100644 code/datums/ai/dog/dog_corgi.bt.json create mode 100644 code/datums/ai/dog/dog_harassment.bt.json delete mode 100644 code/datums/ai/dog/dog_subtrees.dm delete mode 100644 code/datums/ai/generic/find_and_set.dm delete mode 100644 code/datums/ai/generic/generic_behaviors.dm delete mode 100644 code/datums/ai/generic/generic_subtrees.dm create mode 100644 code/datums/ai/generic_behaviors/acquire_injured_target.dm create mode 100644 code/datums/ai/generic_behaviors/acquire_target.dm create mode 100644 code/datums/ai/generic_behaviors/ai_interact.dm create mode 100644 code/datums/ai/generic_behaviors/attack_obstacles.dm create mode 100644 code/datums/ai/generic_behaviors/battle_screech.dm create mode 100644 code/datums/ai/generic_behaviors/break_out_of_object.dm create mode 100644 code/datums/ai/generic_behaviors/break_spine.dm create mode 100644 code/datums/ai/generic_behaviors/cancel_current_plan.dm create mode 100644 code/datums/ai/generic_behaviors/clear_key.dm create mode 100644 code/datums/ai/generic_behaviors/consume.dm create mode 100644 code/datums/ai/generic_behaviors/copy_bb_key.dm create mode 100644 code/datums/ai/generic_behaviors/drag_target.dm create mode 100644 code/datums/ai/generic_behaviors/drop_all_held_items.dm create mode 100644 code/datums/ai/generic_behaviors/express_happiness.dm create mode 100644 code/datums/ai/generic_behaviors/face_target_or_face_initial.dm create mode 100644 code/datums/ai/generic_behaviors/fail.dm create mode 100644 code/datums/ai/generic_behaviors/find_furthest_turf_from_target.dm create mode 100644 code/datums/ai/generic_behaviors/find_nearby.dm create mode 100644 code/datums/ai/generic_behaviors/find_target_facing_turf.dm create mode 100644 code/datums/ai/generic_behaviors/find_unwebbed_turf.dm create mode 100644 code/datums/ai/generic_behaviors/find_valid_teleport_location.dm create mode 100644 code/datums/ai/generic_behaviors/give.dm create mode 100644 code/datums/ai/generic_behaviors/grab_target.dm create mode 100644 code/datums/ai/generic_behaviors/heal_eye_damage.dm create mode 100644 code/datums/ai/generic_behaviors/hunt_target.dm create mode 100644 code/datums/ai/generic_behaviors/issue_pet_command.dm create mode 100644 code/datums/ai/generic_behaviors/keep_playing_instrument.dm create mode 100644 code/datums/ai/generic_behaviors/maintain_distance.dm create mode 100644 code/datums/ai/generic_behaviors/mine_walls.dm create mode 100644 code/datums/ai/generic_behaviors/move_to_target.dm create mode 100644 code/datums/ai/generic_behaviors/perform_emote.dm create mode 100644 code/datums/ai/generic_behaviors/pick_random_ability.dm create mode 100644 code/datums/ai/generic_behaviors/pick_up.dm create mode 100644 code/datums/ai/generic_behaviors/play_instrument.dm create mode 100644 code/datums/ai/generic_behaviors/random_walk.dm create mode 100644 code/datums/ai/generic_behaviors/resist.dm create mode 100644 code/datums/ai/generic_behaviors/run_emote.dm create mode 100644 code/datums/ai/generic_behaviors/set_bb_cooldown.dm create mode 100644 code/datums/ai/generic_behaviors/set_bb_key.dm create mode 100644 code/datums/ai/generic_behaviors/setup_instrument.dm create mode 100644 code/datums/ai/generic_behaviors/speech.dm create mode 100644 code/datums/ai/generic_behaviors/spin_web.dm create mode 100644 code/datums/ai/generic_behaviors/stop_dragging.dm create mode 100644 code/datums/ai/generic_behaviors/stuff_in_disposal.dm create mode 100644 code/datums/ai/generic_behaviors/succeed.dm create mode 100644 code/datums/ai/generic_behaviors/target_retaliate.dm create mode 100644 code/datums/ai/generic_behaviors/use_in_hand.dm create mode 100644 code/datums/ai/generic_behaviors/use_on_object.dm create mode 100644 code/datums/ai/generic_behaviors/virtual_pick_up_item.dm create mode 100644 code/datums/ai/generic_behaviors/wait.dm create mode 100644 code/datums/ai/generic_decorators/ability_available.dm create mode 100644 code/datums/ai/generic_decorators/bb_key_at_least.dm create mode 100644 code/datums/ai/generic_decorators/bb_key_equals.dm create mode 100644 code/datums/ai/generic_decorators/bb_key_list_min_count.dm create mode 100644 code/datums/ai/generic_decorators/bb_key_set.dm create mode 100644 code/datums/ai/generic_decorators/bb_key_true.dm create mode 100644 code/datums/ai/generic_decorators/buckle_target_dangerous.dm create mode 100644 code/datums/ai/generic_decorators/can_see_target.dm create mode 100644 code/datums/ai/generic_decorators/check_cooldown.dm create mode 100644 code/datums/ai/generic_decorators/check_rider_stat.dm create mode 100644 code/datums/ai/generic_decorators/container_attackable.dm create mode 100644 code/datums/ai/generic_decorators/is_at_distance.dm create mode 100644 code/datums/ai/generic_decorators/is_dragging.dm create mode 100644 code/datums/ai/generic_decorators/is_grabbing_target.dm create mode 100644 code/datums/ai/generic_decorators/is_holding_target.dm create mode 100644 code/datums/ai/generic_decorators/is_in_vent.dm create mode 100644 code/datums/ai/generic_decorators/is_target_stunned.dm create mode 100644 code/datums/ai/generic_decorators/item_inside_pawn.dm create mode 100644 code/datums/ai/generic_decorators/key_in_typelist.dm create mode 100644 code/datums/ai/generic_decorators/keys_different_gender.dm create mode 100644 code/datums/ai/generic_decorators/mob_stat_at_least.dm create mode 100644 code/datums/ai/generic_decorators/no_humans_watching.dm create mode 100644 code/datums/ai/generic_decorators/pawn_buckled_to_obj.dm create mode 100644 code/datums/ai/generic_decorators/pawn_contained_in_obj.dm create mode 100644 code/datums/ai/generic_decorators/pawn_farther_than_from_key.dm create mode 100644 code/datums/ai/generic_decorators/pawn_grabbed_by_enemy.dm create mode 100644 code/datums/ai/generic_decorators/pawn_has_gravity.dm create mode 100644 code/datums/ai/generic_decorators/pawn_has_trait_from.dm create mode 100644 code/datums/ai/generic_decorators/pawn_health_below.dm create mode 100644 code/datums/ai/generic_decorators/pawn_inside_mob.dm create mode 100644 code/datums/ai/generic_decorators/pawn_is_restrained.dm create mode 100644 code/datums/ai/generic_decorators/pawn_loc_is_type.dm create mode 100644 code/datums/ai/generic_decorators/pawn_nutrition_below.dm create mode 100644 code/datums/ai/generic_decorators/pawn_same_z_as_key.dm create mode 100644 code/datums/ai/generic_decorators/pawn_turf_has_trait.dm create mode 100644 code/datums/ai/generic_decorators/random_chance.dm create mode 100644 code/datums/ai/generic_decorators/random_chance_from_key.dm create mode 100644 code/datums/ai/generic_decorators/target_has_reagent.dm create mode 100644 code/datums/ai/generic_decorators/target_has_trait.dm create mode 100644 code/datums/ai/generic_decorators/target_health_below_fraction.dm create mode 100644 code/datums/ai/generic_decorators/target_holding_lit_item.dm create mode 100644 code/datums/ai/generic_decorators/target_is_holding_item.dm create mode 100644 code/datums/ai/generic_decorators/target_is_type.dm create mode 100644 code/datums/ai/generic_decorators/target_legcuffed.dm create mode 100644 code/datums/ai/generic_decorators/target_on_ground.dm create mode 100644 code/datums/ai/generic_decorators/true_for_time.dm create mode 100644 code/datums/ai/generic_hunger.bt.json create mode 100644 code/datums/ai/generic_play_instrument.bt.json create mode 100644 code/datums/ai/generic_subtrees/basic_find_target.bt.json create mode 100644 code/datums/ai/generic_subtrees/basic_find_target.dm create mode 100644 code/datums/ai/generic_subtrees/capricious_pick_target.bt.json create mode 100644 code/datums/ai/generic_subtrees/capricious_pick_target.dm create mode 100644 code/datums/ai/generic_subtrees/climb_tree.bt.json create mode 100644 code/datums/ai/generic_subtrees/climb_tree.dm create mode 100644 code/datums/ai/generic_subtrees/find_food.bt.json create mode 100644 code/datums/ai/generic_subtrees/find_food.dm create mode 100644 code/datums/ai/generic_subtrees/find_partner.bt.json create mode 100644 code/datums/ai/generic_subtrees/find_partner.dm create mode 100644 code/datums/ai/generic_subtrees/find_stealable_object.bt.json create mode 100644 code/datums/ai/generic_subtrees/find_stealable_object.dm create mode 100644 code/datums/ai/generic_subtrees/forage_and_retaliate.bt.json create mode 100644 code/datums/ai/generic_subtrees/forage_and_retaliate.dm create mode 100644 code/datums/ai/generic_subtrees/make_babies.dm create mode 100644 code/datums/ai/generic_subtrees/move_to_and_eat.bt.json create mode 100644 code/datums/ai/generic_subtrees/move_to_and_eat.dm create mode 100644 code/datums/ai/generic_subtrees/move_to_and_hunt.bt.json create mode 100644 code/datums/ai/generic_subtrees/move_to_and_hunt.dm create mode 100644 code/datums/ai/generic_subtrees/move_to_reinforce.bt.json create mode 100644 code/datums/ai/generic_subtrees/move_to_reinforce.dm create mode 100644 code/datums/ai/generic_subtrees/pick_retaliate_target.bt.json create mode 100644 code/datums/ai/generic_subtrees/pick_retaliate_target.dm create mode 100644 code/datums/ai/generic_subtrees/random_walk.bt.json create mode 100644 code/datums/ai/generic_subtrees/random_walk.dm create mode 100644 code/datums/ai/generic_subtrees/skittish_and_speak.bt.json create mode 100644 code/datums/ai/generic_subtrees/skittish_and_speak.dm create mode 100644 code/datums/ai/generic_subtrees/steal_and_flee.bt.json create mode 100644 code/datums/ai/generic_subtrees/steal_and_flee.dm create mode 100644 code/datums/ai/hauntium/haunted.bt.json create mode 100644 code/datums/ai/hauntium/haunted_bt_nodes.dm delete mode 100644 code/datums/ai/hauntium/hauntium_subtrees.dm delete mode 100644 code/datums/ai/hunting_behavior/hunting_behaviors.dm delete mode 100644 code/datums/ai/hunting_behavior/hunting_cockroach.dm delete mode 100644 code/datums/ai/hunting_behavior/hunting_corpses.dm delete mode 100644 code/datums/ai/hunting_behavior/hunting_lights.dm delete mode 100644 code/datums/ai/hunting_behavior/hunting_mouse.dm delete mode 100644 code/datums/ai/idle_behaviors/_idle_behavior.dm delete mode 100644 code/datums/ai/idle_behaviors/idle_dog.dm delete mode 100644 code/datums/ai/idle_behaviors/idle_haunted.dm delete mode 100644 code/datums/ai/idle_behaviors/idle_monkey.dm delete mode 100644 code/datums/ai/idle_behaviors/idle_random_walk.dm create mode 100644 code/datums/ai/learn_ai_images/behavior_example.png create mode 100644 code/datums/ai/learn_ai_images/bt_editor_example1.png create mode 100644 code/datums/ai/learn_ai_images/decorator_example.png create mode 100644 code/datums/ai/learn_ai_images/parallel_example.png create mode 100644 code/datums/ai/learn_ai_images/selector_example.png create mode 100644 code/datums/ai/learn_ai_images/sequence_example.png create mode 100644 code/datums/ai/learn_ai_images/subplan_example.png create mode 100644 code/datums/ai/monkey/monkey.bt.json delete mode 100644 code/datums/ai/monkey/monkey_behaviors.dm create mode 100644 code/datums/ai/monkey/monkey_bt_nodes.dm create mode 100644 code/datums/ai/monkey/monkey_combat.bt.json create mode 100644 code/datums/ai/monkey/monkey_find_weapon.bt.json create mode 100644 code/datums/ai/monkey/monkey_serve_food.bt.json create mode 100644 code/datums/ai/monkey/monkey_shenanigans.bt.json create mode 100644 code/datums/ai/objects/vending_machines/vending_machine.bt.json create mode 100644 code/datums/ai/robot_customer/robot_customer.bt.json delete mode 100644 code/datums/ai/robot_customer/robot_customer_subtrees.dm create mode 100644 code/modules/bitrunning/antagonists/netguardian.bt.json create mode 100644 code/modules/bitrunning/virtual_domain/domains/crewman.bt.json create mode 100644 code/modules/bitrunning/virtual_domain/domains/crewman_hostile.bt.json create mode 100644 code/modules/bitrunning/virtual_domain/domains/crewman_hostile_ranged.bt.json create mode 100644 code/modules/bitrunning/virtual_domain/domains/crewman_ranged.bt.json create mode 100644 code/modules/mob/living/basic/alien/alien.bt.json create mode 100644 code/modules/mob/living/basic/alien/basic_alien.bt.json create mode 100644 code/modules/mob/living/basic/alien/drone.bt.json create mode 100644 code/modules/mob/living/basic/alien/lay_alien_egg.bt.json create mode 100644 code/modules/mob/living/basic/alien/melee_alien_combat.bt.json create mode 100644 code/modules/mob/living/basic/alien/plant_alien_weeds.bt.json create mode 100644 code/modules/mob/living/basic/alien/queen.bt.json create mode 100644 code/modules/mob/living/basic/alien/ranged_alien_combat.bt.json create mode 100644 code/modules/mob/living/basic/alien/sentinel.bt.json create mode 100644 code/modules/mob/living/basic/blob_minions/blob_spore.bt.json create mode 100644 code/modules/mob/living/basic/blob_minions/blob_zombie.bt.json create mode 100644 code/modules/mob/living/basic/blob_minions/blobbernaut.bt.json create mode 100644 code/modules/mob/living/basic/boss/blood_drunk_miner/blood_drunk_miner.bt.json create mode 100644 code/modules/mob/living/basic/boss/blood_drunk_miner/blood_drunk_miner_combat.bt.json create mode 100644 code/modules/mob/living/basic/boss/thing/thing_aoe.bt.json create mode 100644 code/modules/mob/living/basic/boss/thing/thing_boss.bt.json create mode 100644 code/modules/mob/living/basic/boss/thing/thing_melee.bt.json create mode 100644 code/modules/mob/living/basic/bots/bot.bt.json create mode 100644 code/modules/mob/living/basic/bots/cleanbot/clean_pet_target.bt.json create mode 100644 code/modules/mob/living/basic/bots/cleanbot/cleanbot.bt.json create mode 100644 code/modules/mob/living/basic/bots/dedbot.bt.json create mode 100644 code/modules/mob/living/basic/bots/ed209/ed209.bt.json create mode 100644 code/modules/mob/living/basic/bots/ed209/ed209_syndicate.bt.json create mode 100644 code/modules/mob/living/basic/bots/firebot/firebot.bt.json create mode 100644 code/modules/mob/living/basic/bots/honkbots/honkbot.bt.json create mode 100644 code/modules/mob/living/basic/bots/honkbots/honkbot_slip.bt.json create mode 100644 code/modules/mob/living/basic/bots/hygienebot/hygienebot.bt.json create mode 100644 code/modules/mob/living/basic/bots/medbot/medbot.bt.json create mode 100644 code/modules/mob/living/basic/bots/medbot/medbot_find_and_announce_crit.bt.json create mode 100644 code/modules/mob/living/basic/bots/medbot/medbot_treat_patient.bt.json create mode 100644 code/modules/mob/living/basic/bots/mulebot/mulebot.bt.json create mode 100644 code/modules/mob/living/basic/bots/repairbot/repairbot.bt.json create mode 100644 code/modules/mob/living/basic/bots/repairbot/repairbot_emagged.bt.json create mode 100644 code/modules/mob/living/basic/bots/repairbot/repairbot_find_target.bt.json create mode 100644 code/modules/mob/living/basic/bots/repairbot/repairbot_repair_target.bt.json create mode 100644 code/modules/mob/living/basic/bots/secbot/secbot.bt.json create mode 100644 code/modules/mob/living/basic/bots/vibebot/vibebot.bt.json create mode 100644 code/modules/mob/living/basic/clown/clown.bt.json create mode 100644 code/modules/mob/living/basic/cult/constructs/artificer.bt.json create mode 100644 code/modules/mob/living/basic/cult/constructs/juggernaut.bt.json create mode 100644 code/modules/mob/living/basic/cult/constructs/proteon.bt.json create mode 100644 code/modules/mob/living/basic/cult/constructs/wraith.bt.json create mode 100644 code/modules/mob/living/basic/cytology/vatbeast.bt.json create mode 100644 code/modules/mob/living/basic/farm_animals/bee/bee.bt.json create mode 100644 code/modules/mob/living/basic/farm_animals/bee/bee_bt.dm create mode 100644 code/modules/mob/living/basic/farm_animals/bee/find_hive.bt.json create mode 100644 code/modules/mob/living/basic/farm_animals/bee/pollinate_target.bt.json create mode 100644 code/modules/mob/living/basic/farm_animals/bee/queen_bee.bt.json create mode 100644 code/modules/mob/living/basic/farm_animals/bee/transition_hive_status.bt.json create mode 100644 code/modules/mob/living/basic/farm_animals/chicken/chick.bt.json create mode 100644 code/modules/mob/living/basic/farm_animals/chicken/chicken.bt.json create mode 100644 code/modules/mob/living/basic/farm_animals/cow/cow.bt.json create mode 100644 code/modules/mob/living/basic/farm_animals/cow/cow_moonicorn.bt.json create mode 100644 code/modules/mob/living/basic/farm_animals/cow/cow_wisdom.bt.json create mode 100644 code/modules/mob/living/basic/farm_animals/deer/deer.bt.json create mode 100644 code/modules/mob/living/basic/farm_animals/goat/goat.bt.json create mode 100644 code/modules/mob/living/basic/farm_animals/goose/forage_for_goose_food.bt.json create mode 100644 code/modules/mob/living/basic/farm_animals/goose/goose.bt.json create mode 100644 code/modules/mob/living/basic/farm_animals/goose/goose_calm.bt.json create mode 100644 code/modules/mob/living/basic/farm_animals/gorilla/gorilla.bt.json create mode 100644 code/modules/mob/living/basic/farm_animals/pig.bt.json create mode 100644 code/modules/mob/living/basic/farm_animals/pony.bt.json create mode 100644 code/modules/mob/living/basic/farm_animals/rabbit.bt.json create mode 100644 code/modules/mob/living/basic/farm_animals/sheep.bt.json create mode 100644 code/modules/mob/living/basic/festivus_pole.bt.json create mode 100644 code/modules/mob/living/basic/heretic/raw_prophet.bt.json create mode 100644 code/modules/mob/living/basic/heretic/rust_walker.bt.json create mode 100644 code/modules/mob/living/basic/heretic/stalker.bt.json create mode 100644 code/modules/mob/living/basic/heretic/star_gazer.bt.json create mode 100644 code/modules/mob/living/basic/icemoon/ice_demon/ice_demon.bt.json create mode 100644 code/modules/mob/living/basic/icemoon/ice_demon/ice_demon_afterimage.bt.json create mode 100644 code/modules/mob/living/basic/icemoon/ice_demon/ice_demon_combat.bt.json create mode 100644 code/modules/mob/living/basic/icemoon/ice_demon/ice_demon_flee_from_fire.bt.json create mode 100644 code/modules/mob/living/basic/icemoon/ice_whelp/ice_whelp.bt.json create mode 100644 code/modules/mob/living/basic/icemoon/polar_bear/polar.bt.json create mode 100644 code/modules/mob/living/basic/icemoon/wolf/wolf.bt.json create mode 100644 code/modules/mob/living/basic/illusion/escape.bt.json create mode 100644 code/modules/mob/living/basic/illusion/retaliate.bt.json create mode 100644 code/modules/mob/living/basic/jungle/human_trap.bt.json create mode 100644 code/modules/mob/living/basic/jungle/leaper/leaper.bt.json create mode 100644 code/modules/mob/living/basic/jungle/mega_arachnid/mega_arachnid.bt.json create mode 100644 code/modules/mob/living/basic/jungle/mega_arachnid/mega_arachnid_combat.bt.json create mode 100644 code/modules/mob/living/basic/jungle/seedling/seedling.bt.json create mode 100644 code/modules/mob/living/basic/jungle/seedling/seedling_meanie.bt.json create mode 100644 code/modules/mob/living/basic/lavaland/basilisk/basilisk.bt.json create mode 100644 code/modules/mob/living/basic/lavaland/bileworm/bileworm.bt.json create mode 100644 code/modules/mob/living/basic/lavaland/brimdemon/brimdemon.bt.json create mode 100644 code/modules/mob/living/basic/lavaland/goldgrub/babygrub.bt.json create mode 100644 code/modules/mob/living/basic/lavaland/goldgrub/goldgrub.bt.json create mode 100644 code/modules/mob/living/basic/lavaland/goldgrub/grub_eat_target.bt.json create mode 100644 code/modules/mob/living/basic/lavaland/goliath/goliath.bt.json create mode 100644 code/modules/mob/living/basic/lavaland/gutlunchers/gutlunch_baby.bt.json create mode 100644 code/modules/mob/living/basic/lavaland/gutlunchers/gutlunch_milk.bt.json create mode 100644 code/modules/mob/living/basic/lavaland/gutlunchers/gutlunch_warrior.bt.json create mode 100644 code/modules/mob/living/basic/lavaland/hivelord/hivelord.bt.json create mode 100644 code/modules/mob/living/basic/lavaland/legion/legion.bt.json create mode 100644 code/modules/mob/living/basic/lavaland/legion/legion_brood.bt.json create mode 100644 code/modules/mob/living/basic/lavaland/legion/legion_monkey.bt.json create mode 100644 code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity.bt.json create mode 100644 code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity_calm.bt.json create mode 100644 code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity_capricious.bt.json create mode 100644 code/modules/mob/living/basic/lavaland/mook/bard.bt.json create mode 100644 code/modules/mob/living/basic/lavaland/mook/bard_find_targets.bt.json create mode 100644 code/modules/mob/living/basic/lavaland/mook/bard_play_music.bt.json create mode 100644 code/modules/mob/living/basic/lavaland/mook/chief_find_targets.bt.json create mode 100644 code/modules/mob/living/basic/lavaland/mook/chief_issue_commands.bt.json create mode 100644 code/modules/mob/living/basic/lavaland/mook/chief_manage_village.bt.json create mode 100644 code/modules/mob/living/basic/lavaland/mook/generic_mook_behavior.bt.json create mode 100644 code/modules/mob/living/basic/lavaland/mook/go_mining.bt.json create mode 100644 code/modules/mob/living/basic/lavaland/mook/heal_injured.bt.json create mode 100644 code/modules/mob/living/basic/lavaland/mook/mook.bt.json create mode 100644 code/modules/mob/living/basic/lavaland/mook/mook_bt.dm create mode 100644 code/modules/mob/living/basic/lavaland/mook/support.bt.json create mode 100644 code/modules/mob/living/basic/lavaland/mook/support_find_targets.bt.json create mode 100644 code/modules/mob/living/basic/lavaland/mook/tribal_chief.bt.json create mode 100644 code/modules/mob/living/basic/lavaland/mook/worker_find_targets.bt.json create mode 100644 code/modules/mob/living/basic/lavaland/node_drone/node_drone.bt.json create mode 100644 code/modules/mob/living/basic/lavaland/raptor/baby_raptor.bt.json create mode 100644 code/modules/mob/living/basic/lavaland/raptor/care_for_young.bt.json create mode 100644 code/modules/mob/living/basic/lavaland/raptor/raptor_ai.bt.json delete mode 100644 code/modules/mob/living/basic/lavaland/raptor/raptor_ai_behavior.dm create mode 100644 code/modules/mob/living/basic/lavaland/raptor/raptor_find_food.bt.json create mode 100644 code/modules/mob/living/basic/lavaland/raptor/raptor_flee.bt.json create mode 100644 code/modules/mob/living/basic/lavaland/raptor/raptor_heal_injured.bt.json create mode 100644 code/modules/mob/living/basic/lavaland/raptor/raptor_play_with_owner.bt.json create mode 100644 code/modules/mob/living/basic/lavaland/raptor/raptor_trough.bt.json create mode 100644 code/modules/mob/living/basic/lavaland/tendril/tendril.bt.json create mode 100644 code/modules/mob/living/basic/lavaland/watcher/watcher.bt.json create mode 100644 code/modules/mob/living/basic/minebots/minebot.bt.json create mode 100644 code/modules/mob/living/basic/minebots/minebot_combat.bt.json create mode 100644 code/modules/mob/living/basic/minebots/minebot_mining.bt.json delete mode 100644 code/modules/mob/living/basic/pets/cat/bread_cat_ai.dm create mode 100644 code/modules/mob/living/basic/pets/cat/cat.bt.json create mode 100644 code/modules/mob/living/basic/pets/cat/cat_bread.bt.json create mode 100644 code/modules/mob/living/basic/pets/cat/cat_bt.dm create mode 100644 code/modules/mob/living/basic/pets/cat/cat_cake.bt.json create mode 100644 code/modules/mob/living/basic/pets/cat/cat_decorate_donuts.bt.json create mode 100644 code/modules/mob/living/basic/pets/cat/cat_find_food.bt.json create mode 100644 code/modules/mob/living/basic/pets/cat/cat_haul_food.bt.json create mode 100644 code/modules/mob/living/basic/pets/cat/cat_hunt_mice.bt.json create mode 100644 code/modules/mob/living/basic/pets/cat/cat_reside_in_home.bt.json create mode 100644 code/modules/mob/living/basic/pets/cat/cat_turn_off_stove.bt.json create mode 100644 code/modules/mob/living/basic/pets/cat/kitten.bt.json delete mode 100644 code/modules/mob/living/basic/pets/cat/kitten_ai.dm create mode 100644 code/modules/mob/living/basic/pets/dog/guarddog.bt.json create mode 100644 code/modules/mob/living/basic/pets/fox.bt.json create mode 100644 code/modules/mob/living/basic/pets/fox_docile.bt.json create mode 100644 code/modules/mob/living/basic/pets/gondolas/gondola.bt.json create mode 100644 code/modules/mob/living/basic/pets/orbie/orbie.bt.json create mode 100644 code/modules/mob/living/basic/pets/parrot/parrot_ai/parrot.bt.json create mode 100644 code/modules/mob/living/basic/pets/parrot/parrot_ai/parrot_ghost.bt.json create mode 100644 code/modules/mob/living/basic/pets/parrot/parrot_ai/parrot_hoard.bt.json create mode 100644 code/modules/mob/living/basic/pets/parrot/parrot_ai/perching.bt.json create mode 100644 code/modules/mob/living/basic/pets/penguin/penguin.bt.json create mode 100644 code/modules/mob/living/basic/pets/penguin/penguin_baby.bt.json create mode 100644 code/modules/mob/living/basic/pets/pet_cult/pet_cult.bt.json create mode 100644 code/modules/mob/living/basic/pets/pet_cult/pet_cult_bt.dm create mode 100644 code/modules/mob/living/basic/pets/sloth.bt.json create mode 100644 code/modules/mob/living/basic/revolutionary.bt.json create mode 100644 code/modules/mob/living/basic/ruin_defender/cybersun_ai_core.bt.json create mode 100644 code/modules/mob/living/basic/ruin_defender/dark_wizard.bt.json create mode 100644 code/modules/mob/living/basic/ruin_defender/fleshblob.bt.json create mode 100644 code/modules/mob/living/basic/ruin_defender/living_floor.bt.json create mode 100644 code/modules/mob/living/basic/ruin_defender/mad_piano.bt.json create mode 100644 code/modules/mob/living/basic/ruin_defender/mimic/mimic_animator.bt.json create mode 100644 code/modules/mob/living/basic/ruin_defender/mimic/mimic_copy.bt.json create mode 100644 code/modules/mob/living/basic/ruin_defender/mimic/mimic_crate.bt.json create mode 100644 code/modules/mob/living/basic/ruin_defender/mimic/mimic_gun.bt.json create mode 100644 code/modules/mob/living/basic/ruin_defender/skeleton.bt.json create mode 100644 code/modules/mob/living/basic/ruin_defender/stickman.bt.json create mode 100644 code/modules/mob/living/basic/ruin_defender/stickman_ranged.bt.json create mode 100644 code/modules/mob/living/basic/ruin_defender/wizard/wizard.bt.json create mode 100644 code/modules/mob/living/basic/ruin_defender/zombie.bt.json create mode 100644 code/modules/mob/living/basic/ruin_defender/zombie_stupid.bt.json create mode 100644 code/modules/mob/living/basic/slime/ai/pet_command_attack_slime.bt.json create mode 100644 code/modules/mob/living/basic/slime/ai/slime.bt.json create mode 100644 code/modules/mob/living/basic/slime/ai/slime_bt.dm delete mode 100644 code/modules/mob/living/basic/slime/ai/subtrees.dm create mode 100644 code/modules/mob/living/basic/snails/snail.bt.json create mode 100644 code/modules/mob/living/basic/space_fauna/ant.bt.json create mode 100644 code/modules/mob/living/basic/space_fauna/bear/bear.bt.json create mode 100644 code/modules/mob/living/basic/space_fauna/carp/basic_carp_tree.bt.json create mode 100644 code/modules/mob/living/basic/space_fauna/carp/carp.bt.json create mode 100644 code/modules/mob/living/basic/space_fauna/carp/carp_combat.bt.json create mode 100644 code/modules/mob/living/basic/space_fauna/carp/carp_flee.bt.json create mode 100644 code/modules/mob/living/basic/space_fauna/carp/carp_migration.bt.json create mode 100644 code/modules/mob/living/basic/space_fauna/carp/carp_passive.bt.json create mode 100644 code/modules/mob/living/basic/space_fauna/carp/carp_passive_selection.bt.json create mode 100644 code/modules/mob/living/basic/space_fauna/carp/carp_pet.bt.json create mode 100644 code/modules/mob/living/basic/space_fauna/carp/carp_ranged.bt.json create mode 100644 code/modules/mob/living/basic/space_fauna/carp/carp_retaliate_selection.bt.json create mode 100644 code/modules/mob/living/basic/space_fauna/carp/carp_target_selection.bt.json create mode 100644 code/modules/mob/living/basic/space_fauna/cat_butcherer.bt.json create mode 100644 code/modules/mob/living/basic/space_fauna/changeling/headslug.bt.json create mode 100644 code/modules/mob/living/basic/space_fauna/eyeball/eyeball.bt.json delete mode 100644 code/modules/mob/living/basic/space_fauna/eyeball/eyeball_ai_behavior.dm create mode 100644 code/modules/mob/living/basic/space_fauna/faithless.bt.json create mode 100644 code/modules/mob/living/basic/space_fauna/garden_gnome.bt.json create mode 100644 code/modules/mob/living/basic/space_fauna/ghost.bt.json create mode 100644 code/modules/mob/living/basic/space_fauna/hivebot/hivebot.bt.json create mode 100644 code/modules/mob/living/basic/space_fauna/hivebot/hivebot_mechanic.bt.json create mode 100644 code/modules/mob/living/basic/space_fauna/hivebot/hivebot_ranged.bt.json create mode 100644 code/modules/mob/living/basic/space_fauna/hivebot/hivebot_ranged_rapid.bt.json create mode 100644 code/modules/mob/living/basic/space_fauna/hivebot/relay_to_hive_partner.bt.json create mode 100644 code/modules/mob/living/basic/space_fauna/killer_tomato.bt.json create mode 100644 code/modules/mob/living/basic/space_fauna/lightgeist.bt.json create mode 100644 code/modules/mob/living/basic/space_fauna/meteor_heart/meteor_heart.bt.json create mode 100644 code/modules/mob/living/basic/space_fauna/morph.bt.json create mode 100644 code/modules/mob/living/basic/space_fauna/mushroom.bt.json create mode 100644 code/modules/mob/living/basic/space_fauna/paper_wizard/paper_wizard.bt.json create mode 100644 code/modules/mob/living/basic/space_fauna/regal_rat/regal_rat.bt.json create mode 100644 code/modules/mob/living/basic/space_fauna/roro.bt.json create mode 100644 code/modules/mob/living/basic/space_fauna/snake/banded.bt.json create mode 100644 code/modules/mob/living/basic/space_fauna/snake/snake.bt.json delete mode 100644 code/modules/mob/living/basic/space_fauna/snake/snake_ai.dm create mode 100644 code/modules/mob/living/basic/space_fauna/spaceman.bt.json create mode 100644 code/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider.bt.json create mode 100644 code/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider_pest.bt.json create mode 100644 code/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider_retaliate.bt.json delete mode 100644 code/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider_subtrees.dm create mode 100644 code/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider_weak.bt.json create mode 100644 code/modules/mob/living/basic/space_fauna/spider/spiderlings/spiderling.bt.json create mode 100644 code/modules/mob/living/basic/space_fauna/spider/young_spider/young_spider.bt.json create mode 100644 code/modules/mob/living/basic/space_fauna/statue/stares_at_people.bt.json create mode 100644 code/modules/mob/living/basic/space_fauna/statue/statue.bt.json create mode 100644 code/modules/mob/living/basic/space_fauna/statue/suspicious_mannequin.bt.json create mode 100644 code/modules/mob/living/basic/space_fauna/supermatter_spider.bt.json create mode 100644 code/modules/mob/living/basic/space_fauna/wumborian_fugu/wumborian_fugu.bt.json create mode 100644 code/modules/mob/living/basic/stoats/stoat.bt.json create mode 100644 code/modules/mob/living/basic/trader/trader.bt.json create mode 100644 code/modules/mob/living/basic/tree.bt.json create mode 100644 code/modules/mob/living/basic/trooper/burst.bt.json create mode 100644 code/modules/mob/living/basic/trooper/peaceful.bt.json create mode 100644 code/modules/mob/living/basic/trooper/peaceful_burst.bt.json create mode 100644 code/modules/mob/living/basic/trooper/ranged.bt.json create mode 100644 code/modules/mob/living/basic/trooper/shotgunner.bt.json create mode 100644 code/modules/mob/living/basic/trooper/trooper.bt.json create mode 100644 code/modules/mob/living/basic/trooper/trooper_ranged.bt.json create mode 100644 code/modules/mob/living/basic/turtle/turtle.bt.json create mode 100644 code/modules/mob/living/basic/vermin/axolotl.bt.json create mode 100644 code/modules/mob/living/basic/vermin/butterfly.bt.json create mode 100644 code/modules/mob/living/basic/vermin/cockroach/cockroach.bt.json create mode 100644 code/modules/mob/living/basic/vermin/cockroach/cockroach_aggro.bt.json create mode 100644 code/modules/mob/living/basic/vermin/cockroach/cockroach_glockroach.bt.json create mode 100644 code/modules/mob/living/basic/vermin/cockroach/cockroach_mobroach.bt.json create mode 100644 code/modules/mob/living/basic/vermin/crab.bt.json create mode 100644 code/modules/mob/living/basic/vermin/eat_cable.bt.json create mode 100644 code/modules/mob/living/basic/vermin/eat_cheese.bt.json create mode 100644 code/modules/mob/living/basic/vermin/frog.bt.json create mode 100644 code/modules/mob/living/basic/vermin/frog_engage_target.bt.json create mode 100644 code/modules/mob/living/basic/vermin/lizard.bt.json create mode 100644 code/modules/mob/living/basic/vermin/mothroach/mothroach.bt.json create mode 100644 code/modules/mob/living/basic/vermin/mouse.bt.json create mode 100644 code/modules/mob/living/basic/vermin/mouse_rat.bt.json create mode 100644 code/modules/mob/living/basic/vermin/play_instrument_on_floor.bt.json create mode 100644 code/modules/mob/living/basic/vermin/space_bat.bt.json create mode 100644 code/modules/mob/living/basic/vermin/suicide_frog.bt.json create mode 100644 code/modules/mob/living/basic/vermin/trash.bt.json delete mode 100644 code/modules/unit_tests/ensure_subtree_operational_datum.dm create mode 100644 tgui/packages/tgui/interfaces/BehaviorTreeViewer/index.tsx create mode 100644 tgui/packages/tgui/interfaces/BehaviorTreeViewer/types.ts create mode 100644 tools/build_bt.py diff --git a/.github/workflows/check_bt_compiled.yml b/.github/workflows/check_bt_compiled.yml new file mode 100644 index 00000000000..16cc1f167d5 --- /dev/null +++ b/.github/workflows/check_bt_compiled.yml @@ -0,0 +1,24 @@ +name: Check BT Compiled JSON + +on: + pull_request: + paths: + - "code/**/*.bt.json" + - "code/_generated/behavior_trees/**" + - "code/__DEFINES/**" + - "tools/build_bt.py" + +jobs: + check_bt_compiled: + name: Verify BT compiled JSON is up to date + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v7 + + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: "3.x" + + - name: Check compiled BT files are up to date + run: python tools/build_bt.py --check diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 4f751c350b9..a276a21e1a4 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -7,6 +7,7 @@ "Donkie.vscode-tgstation-test-adapter", "anturk.dmi-editor", "esbenp.prettier-vscode", - "biomejs.biome" + "biomejs.biome", + "BehaviorTreeG.behaviortreeg" ] } diff --git a/.vscode/tasks.json b/.vscode/tasks.json index aa32b74939a..eda5a2557f5 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -1,6 +1,13 @@ { "version": "2.0.0", "tasks": [ + { + "type": "shell", + "command": "python tools/build_bt.py", + "group": "build", + "presentation": { "reveal": "silent", "panel": "shared" }, + "label": "Compile BT Trees" + }, { "type": "process", "command": "tools/build/build.sh", @@ -17,7 +24,7 @@ "kind": "build", "isDefault": true }, - "dependsOn": "dm: reparse", + "dependsOn": ["dm: reparse", "Compile BT Trees"], "label": "Build All" }, { diff --git a/build/behavior_trees/datums/ai/babies/make_babies.bt.compiled.json b/build/behavior_trees/datums/ai/babies/make_babies.bt.compiled.json new file mode 100644 index 00000000000..d4788d520ca --- /dev/null +++ b/build/behavior_trees/datums/ai/babies/make_babies.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_babies_target","required_dist":1,"finish_on_arrival":true},{"type":"/datum/bt_node/ai_behavior/make_babies","target_key":"BB_babies_target","child_types_key":"BB_babies_child"}]}],"observer_abort":3,"key":"BB_babies_target"} diff --git a/build/behavior_trees/datums/ai/bane/bane.bt.compiled.json b/build/behavior_trees/datums/ai/bane/bane.bt.compiled.json new file mode 100644 index 00000000000..4984062567c --- /dev/null +++ b/build/behavior_trees/datums/ai/bane/bane.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_bane_batman","required_dist":1},{"type":"/datum/bt_node/ai_behavior/break_spine/bane","target_key":"BB_bane_batman"}]}],"key":"BB_bane_batman"},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_bane_batman","target_source":"/datum/target_source/oview_single_type/living_mob","targeting_strategy":"/datum/targeting_strategy/conscious_mob","vision_range":7}]} diff --git a/build/behavior_trees/datums/ai/basic_mobs/basic_ai_behaviors/consider_venting.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/basic_ai_behaviors/consider_venting.bt.compiled.json new file mode 100644 index 00000000000..13ab7c35500 --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/basic_ai_behaviors/consider_venting.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/is_in_vent","children":[{"type":"/datum/bt_node/ai_behavior/exit_vent"}]},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/decorator/cooldown","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_entry_vent_target"},{"type":"/datum/bt_node/ai_behavior/enter_vent"}]}],"cooldown_key":"Venting Cooldown","cooldown_duration":"BB_ventcrawl_cooldown"}],"observer_abort":3,"key":"BB_entry_vent_target"}]} diff --git a/build/behavior_trees/datums/ai/basic_mobs/basic_subtrees/escape_captivity.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/basic_subtrees/escape_captivity.bt.compiled.json new file mode 100644 index 00000000000..43697a9ca3a --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/basic_subtrees/escape_captivity.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/pawn_buckled_to_obj","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/buckle_target_dangerous","children":[{"type":"/datum/bt_node/ai_behavior/break_out_of_object/from_bb","target_key":"BB_basic_mob_escape_target"}]},{"type":"/datum/bt_node/ai_behavior/resist"}]}],"observer_abort":2},{"type":"/datum/bt_node/decorator/pawn_contained_in_obj","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/container_attackable","children":[{"type":"/datum/bt_node/ai_behavior/break_out_of_object/from_bb","target_key":"BB_basic_mob_escape_target"}]},{"type":"/datum/bt_node/ai_behavior/resist"}]}],"observer_abort":2},{"type":"/datum/bt_node/decorator/pawn_grabbed_by_enemy","children":[{"type":"/datum/bt_node/ai_behavior/resist"}],"observer_abort":2},{"type":"/datum/bt_node/decorator/pawn_is_restrained","children":[{"type":"/datum/bt_node/ai_behavior/resist"}],"observer_abort":2}]} diff --git a/build/behavior_trees/datums/ai/basic_mobs/basic_subtrees/escape_captivity_pacifist.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/basic_subtrees/escape_captivity_pacifist.bt.compiled.json new file mode 100644 index 00000000000..c5f1fb7297a --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/basic_subtrees/escape_captivity_pacifist.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/pawn_buckled_to_obj","children":[{"type":"/datum/bt_node/ai_behavior/resist"}],"observer_abort":2},{"type":"/datum/bt_node/decorator/pawn_contained_in_obj","children":[{"type":"/datum/bt_node/ai_behavior/resist"}],"observer_abort":2},{"type":"/datum/bt_node/decorator/pawn_grabbed_by_enemy","children":[{"type":"/datum/bt_node/ai_behavior/resist"}],"observer_abort":2},{"type":"/datum/bt_node/decorator/pawn_is_restrained","children":[{"type":"/datum/bt_node/ai_behavior/resist"}],"observer_abort":2}]} diff --git a/build/behavior_trees/datums/ai/basic_mobs/basic_subtrees/find_paper_and_write.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/basic_subtrees/find_paper_and_write.bt.compiled.json new file mode 100644 index 00000000000..d0785f788d0 --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/basic_subtrees/find_paper_and_write.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/write_on_paper","paper_key":"BB_SIMPLE_CARRY_ITEM","writing_list_key":"BB_writing_list"}],"observer_abort":1,"key":"BB_SIMPLE_CARRY_ITEM"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/attack_obstructions","time_between_perform":4.0,"target_key":"BB_found_paper","can_attack_turfs":true,"can_attack_dense_objects":true},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_found_paper","required_dist":0}]},{"type":"/datum/bt_node/ai_behavior/pick_up_item_virtual","target_key":"BB_found_paper","storage_key":"BB_SIMPLE_CARRY_ITEM"}]}],"success_policy":1,"failure_policy":1}],"observer_abort":2,"key":"BB_found_paper"}]} diff --git a/build/behavior_trees/datums/ai/basic_mobs/basic_subtrees/go_for_swim.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/basic_subtrees/go_for_swim.bt.compiled.json new file mode 100644 index 00000000000..8f225ec19aa --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/basic_subtrees/go_for_swim.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"swim_alternate_turf","required_dist":0,"finish_on_arrival":true}],"observer_abort":3,"key":"swim_alternate_turf"},{"type":"/datum/bt_node/ai_behavior/set_bb_cooldown","cooldown_key":"key_swimmer_cooldown","cooldown_duration":300},{"type":"/datum/bt_node/ai_behavior/wait","duration":300},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"swim_alternate_turf","targeting_strategy":"/datum/targeting_strategy/walkable_turf","target_source":"/datum/target_source/oview_land_turfs"},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"swim_alternate_turf","required_dist":0,"finish_on_arrival":true}]},{"type":"/datum/bt_node/ai_behavior/swim_splash"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/datums/ai/basic_mobs/basic_subtrees/random_speech_loop.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/basic_subtrees/random_speech_loop.bt.compiled.json new file mode 100644 index 00000000000..54f984a3930 --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/basic_subtrees/random_speech_loop.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/subplan","children":[{"type":"$bqdqne64"}],"__bindings":{"bqdqne64":{"label":"speech_behavior","default":"/datum/bt_node/ai_behavior/random_speech_blackboard"}},"success_policy":1,"failure_policy":1,"loop_delay":10} diff --git a/build/behavior_trees/datums/ai/basic_mobs/basic_subtrees/run_away_from_target.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/basic_subtrees/run_away_from_target.bt.compiled.json new file mode 100644 index 00000000000..50b8c06f73f --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/basic_subtrees/run_away_from_target.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/find_flee_location","target_key":"$byk9gqj4","hiding_location_key":"Current Target Hiding Location","destination_key":"BB_flee_location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_flee_location","required_dist":0,"finish_on_arrival":true}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"key":"BB_basic_stop_fleeing","invert":true,"observer_abort":3,"__bindings":{"byk9gqj4":{"label":"target_key","default":"Current Target"}}} diff --git a/build/behavior_trees/datums/ai/basic_mobs/basic_subtrees/run_away_from_target_run_and_shoot.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/basic_subtrees/run_away_from_target_run_and_shoot.bt.compiled.json new file mode 100644 index 00000000000..84bba04be6c --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/basic_subtrees/run_away_from_target_run_and_shoot.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/find_flee_location","target_key":"Current Target","hiding_location_key":"Current Target Hiding Location","destination_key":"BB_flee_location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_ranged_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_flee_location","required_dist":0,"finish_on_arrival":true}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"key":"BB_basic_stop_fleeing","invert":true,"observer_abort":3} diff --git a/build/behavior_trees/datums/ai/basic_mobs/basic_subtrees/skittish_brawler_combat.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/basic_subtrees/skittish_brawler_combat.bt.compiled.json new file mode 100644 index 00000000000..2b42b931e7b --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/basic_subtrees/skittish_brawler_combat.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/is_at_distance","children":[{"type":"/datum/bt_node/subtree/run_away_from_target"}],"target_key":"Current Target","maximum_distance":9},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}]}],"key":"Current Target","observer_abort":1},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list/nearest","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/datums/ai/basic_mobs/basic_subtrees/tip_reaction.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/basic_subtrees/tip_reaction.bt.compiled.json new file mode 100644 index 00000000000..421d1326ffb --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/basic_subtrees/tip_reaction.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/bb_key_true","children":[{"type":"/datum/bt_node/ai_behavior/tipped_reaction"}],"key":"BB_basic_tip_reacting","observer_abort":2} diff --git a/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_attack.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_attack.bt.compiled.json new file mode 100644 index 00000000000..4e3709d18c0 --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_attack.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"BB_current_pet_target","targeting_strategy":"BB_pet_targeting","hiding_location_key":"BB_pet_attack_hiding_location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_current_pet_target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"key":"BB_current_pet_target","observer_abort":3}]} diff --git a/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_attack_dog.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_attack_dog.bt.compiled.json new file mode 100644 index 00000000000..6b9268de1e2 --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_attack_dog.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack/dog","target_key":"BB_current_pet_target","targeting_strategy":"BB_pet_targeting","hiding_location_key":"BB_pet_attack_hiding_location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_current_pet_target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"key":"BB_current_pet_target","observer_abort":3}]} diff --git a/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_attack_minebot.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_attack_minebot.bt.compiled.json new file mode 100644 index 00000000000..fcc3c749241 --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_attack_minebot.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_ranged_attack/minebot","target_key":"BB_current_pet_target","targeting_strategy":"BB_pet_targeting","hiding_location_key":"BB_pet_attack_hiding_location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_current_pet_target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"key":"BB_current_pet_target","observer_abort":3}]} diff --git a/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_attack_ranged_glockroach.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_attack_ranged_glockroach.bt.compiled.json new file mode 100644 index 00000000000..3e8b1d36b36 --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_attack_ranged_glockroach.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_ranged_attack/glockroach","target_key":"BB_current_pet_target","targeting_strategy":"BB_pet_targeting","hiding_location_key":"BB_pet_attack_hiding_location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/maintain_distance","target_key":"Current Target"}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"key":"BB_current_pet_target","observer_abort":3}]} diff --git a/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_beehive.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_beehive.bt.compiled.json new file mode 100644 index 00000000000..114dd3ec43f --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_beehive.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_current_home","required_dist":1,"finish_on_arrival":false},{"type":"/datum/bt_node/ai_behavior/enter_exit_hive"},{"type":"/datum/bt_node/ai_behavior/clear_pet_command"}]} diff --git a/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_breed.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_breed.bt.compiled.json new file mode 100644 index 00000000000..fdd18ae5418 --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_breed.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/decorator/key_in_typelist","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_current_pet_target","required_dist":1,"finish_on_arrival":true},{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/keys_different_gender","children":[{"type":"/datum/bt_node/ai_behavior/perform_emote","emote":"Seems confused"}],"invert":true,"key_a":"Literally me","key_b":"BB_current_pet_target"},{"type":"/datum/bt_node/ai_behavior/ai_interact","target_key":"BB_current_pet_target","combat_mode":false}]},{"type":"/datum/bt_node/ai_behavior/clear_pet_command"}]}],"key":"BB_current_pet_target","typelist_key":"BB_babies_partner"}],"key":"BB_current_pet_target","observer_abort":3} diff --git a/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_fetch.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_fetch.bt.compiled.json new file mode 100644 index 00000000000..7c7d8905d13 --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_fetch.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/forget_failed_fetches"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_current_pet_target","required_dist":1,"finish_on_arrival":true},{"type":"/datum/bt_node/ai_behavior/fetch_seek","target_key":"BB_current_pet_target"},{"type":"/datum/bt_node/ai_behavior/pick_up_item_virtual","target_key":"BB_current_pet_target","storage_key":"BB_SIMPLE_CARRY_ITEM"}]}],"key":"BB_current_pet_target","observer_abort":3}]},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_FETCH_DELIVER_TO","required_dist":1,"finish_on_arrival":true},{"type":"/datum/bt_node/ai_behavior/pass_item_virtual","delivery_key":"BB_FETCH_DELIVER_TO","storage_key":"BB_SIMPLE_CARRY_ITEM"},{"type":"/datum/bt_node/ai_behavior/clear_pet_command"}]}],"key":"BB_FETCH_DELIVER_TO","observer_abort":3}],"key":"BB_SIMPLE_CARRY_ITEM","observer_abort":3},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/wait"}],"observer_abort":3,"invert":true,"key":"BB_current_pet_target"}]} diff --git a/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_fish.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_fish.bt.compiled.json new file mode 100644 index 00000000000..7483308b665 --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_fish.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_current_pet_target","required_dist":1,"finish_on_arrival":false},{"type":"/datum/bt_node/ai_behavior/ai_interact","target_key":"BB_current_pet_target","combat_mode":false}]}],"success_policy":1,"failure_policy":0}],"key":"BB_current_pet_target","observer_abort":3} diff --git a/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_follow.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_follow.bt.compiled.json new file mode 100644 index 00000000000..7c56af271fc --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_follow.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_current_pet_target","required_dist":1,"finish_on_arrival":true}],"success_policy":1,"failure_policy":0}],"key":"BB_current_pet_target","observer_abort":3} diff --git a/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_mine_walls.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_mine_walls.bt.compiled.json new file mode 100644 index 00000000000..24ce4902b7b --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_mine_walls.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_current_pet_target","required_dist":1,"finish_on_arrival":false},{"type":"/datum/bt_node/ai_behavior/mine_wall","target_key":"BB_current_pet_target"}]}],"key":"BB_current_pet_target","observer_abort":3},{"type":"/datum/bt_node/ai_behavior/find_mineral_wall","target_key":"BB_current_pet_target"},{"type":"/datum/bt_node/ai_behavior/clear_pet_command"}]} diff --git a/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_move_to.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_move_to.bt.compiled.json new file mode 100644 index 00000000000..0099a4de932 --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_move_to.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_current_pet_target","required_dist":0,"finish_on_arrival":true},{"type":"/datum/bt_node/ai_behavior/clear_pet_command"}]}],"key":"BB_current_pet_target","observer_abort":3} diff --git a/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_play_dead.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_play_dead.bt.compiled.json new file mode 100644 index 00000000000..169d6c0c5df --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_play_dead.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/play_dead"},{"type":"/datum/bt_node/ai_behavior/clear_pet_command"}]} diff --git a/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_protect_owner.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_protect_owner.bt.compiled.json new file mode 100644 index 00000000000..97379ae486d --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_protect_owner.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/protect_owner_check"},{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"BB_current_pet_target","targeting_strategy":"BB_pet_targeting","hiding_location_key":"BB_pet_attack_hiding_location"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_current_pet_target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"key":"BB_current_pet_target","observer_abort":3}]} diff --git a/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_protect_owner_ranged_glockroach.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_protect_owner_ranged_glockroach.bt.compiled.json new file mode 100644 index 00000000000..74c77d8f597 --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_protect_owner_ranged_glockroach.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/protect_owner_check"},{"type":"/datum/bt_node/ai_behavior/basic_ranged_attack/glockroach","target_key":"BB_current_pet_target","targeting_strategy":"BB_pet_targeting","hiding_location_key":"BB_pet_attack_hiding_location"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/maintain_distance","target_key":"Current Target"}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"key":"BB_current_pet_target","observer_abort":3}]} diff --git a/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_scatter.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_scatter.bt.compiled.json new file mode 100644 index 00000000000..bcea164844b --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_scatter.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/subtree/run_away_from_target","bindings":{"byk9gqj4":"BB_current_pet_target"}},{"type":"/datum/bt_node/ai_behavior/clear_pet_command"}]} diff --git a/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_stay.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_stay.bt.compiled.json new file mode 100644 index 00000000000..0f54e4df546 --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_stay.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/ai_behavior/wait","duration":0} diff --git a/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_swirl.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_swirl.bt.compiled.json new file mode 100644 index 00000000000..e961a273341 --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_swirl.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"BB_current_pet_target","targeting_strategy":"BB_pet_targeting","hiding_location_key":"BB_pet_attack_hiding_location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_current_pet_target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"key":"BB_current_pet_target","observer_abort":3},{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/swirl_around_target"},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_swirl_turf","required_dist":0,"finish_on_arrival":true}]}]}],"success_policy":1,"failure_policy":1}],"key":"BB_swarm_target","observer_abort":3} diff --git a/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_targeted_ability.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_targeted_ability.bt.compiled.json new file mode 100644 index 00000000000..832baf02654 --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_targeted_ability.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability","ability_key":"BB_TARGETED_action","target_key":"BB_current_pet_target"},{"type":"/datum/bt_node/ai_behavior/clear_pet_command"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_current_pet_target","required_dist":1,"finish_on_arrival":false}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"key":"BB_current_pet_target","observer_abort":3} diff --git a/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_untargeted_ability.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_untargeted_ability.bt.compiled.json new file mode 100644 index 00000000000..53f2f98fbce --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/pet_commands/pet_command_untargeted_ability.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/use_mob_ability","ability_key":"BB_pet_active_ability"},{"type":"/datum/bt_node/ai_behavior/clear_pet_command"}]} diff --git a/build/behavior_trees/datums/ai/basic_mobs/simple_ability.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/simple_ability.bt.compiled.json new file mode 100644 index 00000000000..cc9a591751e --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/simple_ability.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/subtree/simple_ability_combat"}]} diff --git a/build/behavior_trees/datums/ai/basic_mobs/simple_ability_combat.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/simple_ability_combat.bt.compiled.json new file mode 100644 index 00000000000..d1bc58148b4 --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/simple_ability_combat.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/decorator/cooldown","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability","ability_key":"BB_wizard_targeted_spell","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability","ability_key":"BB_wizard_secondary_spell","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability","ability_key":"BB_wizard_blink_spell","target_key":"Current Target"}]}],"observer_abort":2,"cooldown_key":"BB_wizard_spell_cooldown","cooldown_duration":10}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":3,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"invert":false,"key":"Current Target"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/datums/ai/basic_mobs/simple_ability_melee.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/simple_ability_melee.bt.compiled.json new file mode 100644 index 00000000000..a3512dbdaa4 --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/simple_ability_melee.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/subtree/simple_ability_melee_combat"}]} diff --git a/build/behavior_trees/datums/ai/basic_mobs/simple_ability_melee_combat.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/simple_ability_melee_combat.bt.compiled.json new file mode 100644 index 00000000000..c45a6bf99cc --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/simple_ability_melee_combat.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/attack_obstructions","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability","ability_key":"BB_TARGETED_action","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/datums/ai/basic_mobs/simple_ability_ranged.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/simple_ability_ranged.bt.compiled.json new file mode 100644 index 00000000000..abae9529636 --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/simple_ability_ranged.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/subtree/simple_ability_ranged_combat"}]} diff --git a/build/behavior_trees/datums/ai/basic_mobs/simple_ability_ranged_combat.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/simple_ability_ranged_combat.bt.compiled.json new file mode 100644 index 00000000000..3d639c95fad --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/simple_ability_ranged_combat.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability","ability_key":"BB_TARGETED_action","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/basic_ranged_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/maintain_distance","target_key":"Current Target"}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/datums/ai/basic_mobs/simple_ability_retaliate.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/simple_ability_retaliate.bt.compiled.json new file mode 100644 index 00000000000..44dfc11a204 --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/simple_ability_retaliate.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/subtree/simple_ability_retaliate_combat"}]} diff --git a/build/behavior_trees/datums/ai/basic_mobs/simple_ability_retaliate_combat.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/simple_ability_retaliate_combat.bt.compiled.json new file mode 100644 index 00000000000..8084930f482 --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/simple_ability_retaliate_combat.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability","ability_key":"BB_TARGETED_action","target_key":"Current Target"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":3,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"BB_basic_mob_shitlist"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/datums/ai/basic_mobs/simple_capricious.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/simple_capricious.bt.compiled.json new file mode 100644 index 00000000000..b1fed32e89a --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/simple_capricious.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/subtree/simple_capricious_combat"}]} diff --git a/build/behavior_trees/datums/ai/basic_mobs/simple_capricious_combat.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/simple_capricious_combat.bt.compiled.json new file mode 100644 index 00000000000..e6a1c4c95d0 --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/simple_capricious_combat.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"BB_basic_mob_shitlist"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/subtree/capricious_pick_target"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/datums/ai/basic_mobs/simple_fearful.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/simple_fearful.bt.compiled.json new file mode 100644 index 00000000000..fe0b4f4783e --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/simple_fearful.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/simple_fearful_combat"}]} diff --git a/build/behavior_trees/datums/ai/basic_mobs/simple_fearful_combat.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/simple_fearful_combat.bt.compiled.json new file mode 100644 index 00000000000..335e6be9889 --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/simple_fearful_combat.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/subtree/run_away_from_target"}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets/nearest","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true},{"type":"/datum/bt_node/subtree/random_walk"}]} diff --git a/build/behavior_trees/datums/ai/basic_mobs/simple_goon.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/simple_goon.bt.compiled.json new file mode 100644 index 00000000000..339d5cc3687 --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/simple_goon.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/subtree","override_id":"pet_command"}]} diff --git a/build/behavior_trees/datums/ai/basic_mobs/simple_hostile.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/simple_hostile.bt.compiled.json new file mode 100644 index 00000000000..c99cae59bca --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/simple_hostile.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/subtree/simple_hostile_combat"}]} diff --git a/build/behavior_trees/datums/ai/basic_mobs/simple_hostile_combat.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/simple_hostile_combat.bt.compiled.json new file mode 100644 index 00000000000..98eedae8058 --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/simple_hostile_combat.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true},{"type":"$bbrbyj7y"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/random_walk","bindings":{"bf07i8ep":"$bp3p5vvb"}}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"__bindings":{"bbrbyj7y":{"label":"combat_speech_behavior","default":""},"bp3p5vvb":{"label":"walk_chance","default":25}},"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/datums/ai/basic_mobs/simple_hostile_combat_with_retaliate.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/simple_hostile_combat_with_retaliate.bt.compiled.json new file mode 100644 index 00000000000..74f3e952fc8 --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/simple_hostile_combat_with_retaliate.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/attack_obstructions","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}]}],"success_policy":0,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}]},{"type":"$bhvqd9fh"}],"__bindings":{"bhvqd9fh":{"label":"speech loop subtree","default":"/datum/bt_node/subtree"}},"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/datums/ai/basic_mobs/simple_hostile_obstacles.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/simple_hostile_obstacles.bt.compiled.json new file mode 100644 index 00000000000..70dffa4e2a6 --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/simple_hostile_obstacles.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/subtree/run_away_from_target"},{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack"}],"success_policy":1,"failure_policy":1,"loop_delay":10}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true},{"type":"/datum/bt_node/subtree/pick_retaliate_target"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/datums/ai/basic_mobs/simple_hostile_obstacles_combat.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/simple_hostile_obstacles_combat.bt.compiled.json new file mode 100644 index 00000000000..ace2dc0e2df --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/simple_hostile_obstacles_combat.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/attack_obstructions","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}]}],"success_policy":0,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false},{"type":"$b95z0f0c"}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"$by235tlw"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"$bqul7l8t"}],"__bindings":{"b95z0f0c":{"label":"while_attacking_subtree","default":""},"by235tlw":{"label":"cant_attack_subtree","default":""},"bqul7l8t":{"label":"speech_subtree","default":"/datum/bt_node/subtree"}},"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/datums/ai/basic_mobs/simple_ranged.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/simple_ranged.bt.compiled.json new file mode 100644 index 00000000000..39ce20f83c6 --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/simple_ranged.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/subtree/simple_ranged_combat"}]} diff --git a/build/behavior_trees/datums/ai/basic_mobs/simple_ranged_combat.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/simple_ranged_combat.bt.compiled.json new file mode 100644 index 00000000000..d5b107b7d30 --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/simple_ranged_combat.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_ranged_attack","time_between_perform":6.0,"target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/maintain_distance","target_key":"Current Target","approach_movement_type":"/datum/ai_movement/basic_avoidance"}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"$bsjjwub2"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"__bindings":{"bsjjwub2":{"label":"idle_behavior","default":"/datum/bt_node/subtree/random_walk"}},"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/datums/ai/basic_mobs/simple_ranged_retaliate.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/simple_ranged_retaliate.bt.compiled.json new file mode 100644 index 00000000000..ab4d970ead9 --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/simple_ranged_retaliate.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/subtree/simple_ranged_retaliate_combat"}]} diff --git a/build/behavior_trees/datums/ai/basic_mobs/simple_ranged_retaliate_combat.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/simple_ranged_retaliate_combat.bt.compiled.json new file mode 100644 index 00000000000..8b83bd02e12 --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/simple_ranged_retaliate_combat.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_ranged_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/maintain_distance","target_key":"Current Target"}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"key":"Current Target","observer_abort":1},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/datums/ai/basic_mobs/simple_retaliate.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/simple_retaliate.bt.compiled.json new file mode 100644 index 00000000000..15c3371800e --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/simple_retaliate.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/subtree/simple_retaliate_combat"}]} diff --git a/build/behavior_trees/datums/ai/basic_mobs/simple_retaliate_combat.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/simple_retaliate_combat.bt.compiled.json new file mode 100644 index 00000000000..69e93aa3570 --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/simple_retaliate_combat.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/random_walk"}]}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true},{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location","check_faction":"$b2jvnm5d"}],"__bindings":{"b2jvnm5d":{"label":"check_faction","default":0}},"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/datums/ai/basic_mobs/simple_skirmisher.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/simple_skirmisher.bt.compiled.json new file mode 100644 index 00000000000..7d76a8bf6c3 --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/simple_skirmisher.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/subtree/simple_skirmisher_combat"}]} diff --git a/build/behavior_trees/datums/ai/basic_mobs/simple_skirmisher_combat.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/simple_skirmisher_combat.bt.compiled.json new file mode 100644 index 00000000000..44403a4ba80 --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/simple_skirmisher_combat.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/attack_obstructions","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/ai_behavior/basic_ranged_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/datums/ai/basic_mobs/simple_skittish.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/simple_skittish.bt.compiled.json new file mode 100644 index 00000000000..1232523f9f7 --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/simple_skittish.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/simple_skittish_combat"}]} diff --git a/build/behavior_trees/datums/ai/basic_mobs/simple_skittish_combat.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/simple_skittish_combat.bt.compiled.json new file mode 100644 index 00000000000..a57400770d3 --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/simple_skittish_combat.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/subtree/run_away_from_target"}],"key":"Current Target","observer_abort":1},{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list/nearest","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true},{"type":"/datum/bt_node/subtree/random_walk"}]} diff --git a/build/behavior_trees/datums/ai/basic_mobs/talk.bt.compiled.json b/build/behavior_trees/datums/ai/basic_mobs/talk.bt.compiled.json new file mode 100644 index 00000000000..7f8b669005c --- /dev/null +++ b/build/behavior_trees/datums/ai/basic_mobs/talk.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/subtree/random_speech_loop"} diff --git a/build/behavior_trees/datums/ai/bots/bot_patrol.bt.compiled.json b/build/behavior_trees/datums/ai/bots/bot_patrol.bt.compiled.json new file mode 100644 index 00000000000..a192bb7c18f --- /dev/null +++ b/build/behavior_trees/datums/ai/bots/bot_patrol.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/key_off_cooldown","children":[{"type":"/datum/bt_node/decorator/bot_mode_flag","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/find_next_beacon_target","target_key":"beacon_target"}],"key":"previous_beacon_target"},{"type":"/datum/bt_node/ai_behavior/find_first_beacon_target","target_key":"beacon_target"}]},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"beacon_target","required_dist":0,"finish_on_arrival":true},{"type":"/datum/bt_node/ai_behavior/arrive_at_beacon","target_key":"beacon_target"}]}],"key":"beacon_target"}]}],"observer_abort":2,"flag":2}],"observer_abort":2,"cooldown_key":"bot_beacon_cooldown"},{"type":"/datum/bt_node/ai_behavior/wait","duration":10}]} diff --git a/build/behavior_trees/datums/ai/bots/bot_respond_to_summon.bt.compiled.json b/build/behavior_trees/datums/ai/bots/bot_respond_to_summon.bt.compiled.json new file mode 100644 index 00000000000..7c68c683504 --- /dev/null +++ b/build/behavior_trees/datums/ai/bots/bot_respond_to_summon.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"bot_summon_target","required_dist":0,"finish_on_arrival":true},{"type":"/datum/bt_node/ai_behavior/complete_summon_travel","target_key":"bot_summon_target"}]}],"key":"bot_summon_target"} diff --git a/build/behavior_trees/datums/ai/bots/bot_salute_authority.bt.compiled.json b/build/behavior_trees/datums/ai/bots/bot_salute_authority.bt.compiled.json new file mode 100644 index 00000000000..d2af26740eb --- /dev/null +++ b/build/behavior_trees/datums/ai/bots/bot_salute_authority.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/cooldown","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/find_valid_authority","target_key":"salute_target"},{"type":"/datum/bt_node/ai_behavior/salute_authority","target_key":"salute_target","salute_keys":"salute_messages"}]}],"cooldown_key":"salute_cooldown","cooldown_duration":600} diff --git a/build/behavior_trees/datums/ai/cursed/cursed.bt.compiled.json b/build/behavior_trees/datums/ai/cursed/cursed.bt.compiled.json new file mode 100644 index 00000000000..c5851cf082c --- /dev/null +++ b/build/behavior_trees/datums/ai/cursed/cursed.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/throw_attack/haunted","target_key":"BB_haunt_target","throw_count_key":"BB_haunted_throw_attempt_count","haunt_list_key":"BB_to_haunt_list"}],"success_policy":1,"failure_policy":0,"loop_delay":20},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_haunt_target","required_dist":3,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"BB_haunt_target"},{"type":"/datum/bt_node/ai_behavior/idle_ghost_item"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_haunt_target","target_source":"/datum/target_source/oview_single_type/carbon_mob","targeting_strategy":"/datum/targeting_strategy/anything","vision_range":7}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true} diff --git a/build/behavior_trees/datums/ai/dog/dog.bt.compiled.json b/build/behavior_trees/datums/ai/dog/dog.bt.compiled.json new file mode 100644 index 00000000000..fc346204445 --- /dev/null +++ b/build/behavior_trees/datums/ai/dog/dog.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree","override_id":"pet_command"},{"type":"/datum/bt_node/subtree/dog_harassment"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/idle_dog"}],"success_policy":1,"failure_policy":0},{"type":"/datum/bt_node/subtree/random_speech_loop"},{"type":"/datum/bt_node/subtree/random_speech_loop"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/datums/ai/dog/dog_corgi.bt.compiled.json b/build/behavior_trees/datums/ai/dog/dog_corgi.bt.compiled.json new file mode 100644 index 00000000000..475f8dcb836 --- /dev/null +++ b/build/behavior_trees/datums/ai/dog/dog_corgi.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree","override_id":"pet_command"},{"type":"/datum/bt_node/subtree/dog_harassment"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/subtree/run_away_from_target"}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/make_babies"},{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/idle_dog"}],"success_policy":1,"failure_policy":0}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/subtree/find_partner"},{"type":"/datum/bt_node/subtree/random_speech_loop"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/datums/ai/dog/dog_harassment.bt.compiled.json b/build/behavior_trees/datums/ai/dog/dog_harassment.bt.compiled.json new file mode 100644 index 00000000000..00d36999427 --- /dev/null +++ b/build/behavior_trees/datums/ai/dog/dog_harassment.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/random_chance","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/find_hated_dog_target","target_key":"BB_DOG_HARASS_TARGET","targeting_strategy":"BB_pet_targeting"},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_DOG_HARASS_TARGET","required_dist":1,"finish_on_arrival":true},{"type":"/datum/bt_node/ai_behavior/basic_melee_attack/dog","target_key":"BB_DOG_HARASS_TARGET","targeting_strategy":"BB_pet_targeting","hiding_location_key":"Current Target Hiding Location"}]}],"chance":0.1} diff --git a/build/behavior_trees/datums/ai/generic_hunger.bt.compiled.json b/build/behavior_trees/datums/ai/generic_hunger.bt.compiled.json new file mode 100644 index 00000000000..06cf4dc331d --- /dev/null +++ b/build/behavior_trees/datums/ai/generic_hunger.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/consume","target_key":"bb_food_target","hunger_timer_key":"BB_NEXT_HUNGRY"},{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"bb_food_target","required_dist":1,"finish_on_arrival":true},{"type":"/datum/bt_node/ai_behavior/pick_up","target_key":"bb_food_target","drop_held":true}]}]}],"key":"bb_food_target"} diff --git a/build/behavior_trees/datums/ai/generic_play_instrument.bt.compiled.json b/build/behavior_trees/datums/ai/generic_play_instrument.bt.compiled.json new file mode 100644 index 00000000000..c3b4514f642 --- /dev/null +++ b/build/behavior_trees/datums/ai/generic_play_instrument.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_true","children":[{"type":"/datum/bt_node/decorator/is_holding_target","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_SONG_INSTRUMENT"},{"type":"/datum/bt_node/ai_behavior/pick_up","target_key":"BB_SONG_INSTRUMENT"}]}],"observer_abort":2,"invert":true,"key":"BB_SONG_INSTRUMENT"}],"invert":true,"key":"Has its instrument up its ass"},{"type":"/datum/bt_node/ai_behavior/keep_playing_instrument","song_instrument_key":"BB_SONG_INSTRUMENT"},{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/setup_instrument","song_instrument_key":"BB_SONG_INSTRUMENT","song_lines_key":"song_lines"},{"type":"/datum/bt_node/ai_behavior/play_instrument","song_instrument_key":"BB_SONG_INSTRUMENT","volume":"$bulu13xf"}]}]}],"key":"BB_SONG_INSTRUMENT","__bindings":{"bulu13xf":{"label":"volume","default":50}}} diff --git a/build/behavior_trees/datums/ai/generic_subtrees/basic_find_target.bt.compiled.json b/build/behavior_trees/datums/ai/generic_subtrees/basic_find_target.bt.compiled.json new file mode 100644 index 00000000000..c0c7d81a31e --- /dev/null +++ b/build/behavior_trees/datums/ai/generic_subtrees/basic_find_target.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"} diff --git a/build/behavior_trees/datums/ai/generic_subtrees/capricious_pick_target.bt.compiled.json b/build/behavior_trees/datums/ai/generic_subtrees/capricious_pick_target.bt.compiled.json new file mode 100644 index 00000000000..700ad03cb5d --- /dev/null +++ b/build/behavior_trees/datums/ai/generic_subtrees/capricious_pick_target.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/capricious_retaliate","targeting_strategy":"targeting_strategy","ignore_faction":true},{"type":"/datum/bt_node/subtree/pick_retaliate_target"}]} diff --git a/build/behavior_trees/datums/ai/generic_subtrees/climb_tree.bt.compiled.json b/build/behavior_trees/datums/ai/generic_subtrees/climb_tree.bt.compiled.json new file mode 100644 index 00000000000..8fc223ff1b8 --- /dev/null +++ b/build/behavior_trees/datums/ai/generic_subtrees/climb_tree.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/cooldown","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_climbed_tree"},{"type":"/datum/bt_node/ai_behavior/ai_interact","target_key":"BB_climbed_tree"},{"type":"/datum/bt_node/ai_behavior/clear_key","key":"BB_climbed_tree"}]}],"observer_abort":3,"key":"BB_climbed_tree"}],"observer_abort":2,"cooldown_key":"Tree Climbing Cooldown","cooldown_duration":"$bb0lcfol","__bindings":{"bb0lcfol":{"label":"tree_climbing_cooldown","default":300}}} diff --git a/build/behavior_trees/datums/ai/generic_subtrees/find_food.bt.compiled.json b/build/behavior_trees/datums/ai/generic_subtrees/find_food.bt.compiled.json new file mode 100644 index 00000000000..ab6b56f72f5 --- /dev/null +++ b/build/behavior_trees/datums/ai/generic_subtrees/find_food.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/key_off_cooldown","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target","target_key":"BB_TARGET_FOOD","targeting_strategy":"/datum/targeting_strategy/pickup_item","target_source":"/datum/target_source/oview_typed/from_bb_key/basic_foods","revalidation_mode":1}],"observer_abort":1,"cooldown_key":"BB_next_food_eat"} diff --git a/build/behavior_trees/datums/ai/generic_subtrees/find_partner.bt.compiled.json b/build/behavior_trees/datums/ai/generic_subtrees/find_partner.bt.compiled.json new file mode 100644 index 00000000000..1edff728d4e --- /dev/null +++ b/build/behavior_trees/datums/ai/generic_subtrees/find_partner.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/decorator/key_off_cooldown","children":[{"type":"/datum/bt_node/ai_behavior/find_partner","target_key":"BB_babies_target","partner_types_key":"BB_babies_partner","child_types_key":"BB_babies_child"}],"cooldown_key":"BB_partner_search_timeout"}],"key":"BB_breed_ready"}],"observer_abort":2,"key":"can we fuck?"} diff --git a/build/behavior_trees/datums/ai/generic_subtrees/find_stealable_object.bt.compiled.json b/build/behavior_trees/datums/ai/generic_subtrees/find_stealable_object.bt.compiled.json new file mode 100644 index 00000000000..d19c824e1cb --- /dev/null +++ b/build/behavior_trees/datums/ai/generic_subtrees/find_stealable_object.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/is_dragging","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/random_chance_from_key","children":[{"type":"/datum/bt_node/ai_behavior/stop_dragging"}],"chance_key":"guilty_concious_rate"},{"type":"/datum/bt_node/ai_behavior/succeed"}]}]},{"type":"/datum/bt_node/decorator/bb_key_true","children":[{"type":"/datum/bt_node/decorator/random_chance_from_key","children":[{"type":"/datum/bt_node/ai_behavior/set_bb_key","target_key":"BB_WANTS_TO_COMMIT_THEFT","value":true}],"chance_key":"steal_chance"}],"invert":true,"key":"BB_WANTS_TO_COMMIT_THEFT"},{"type":"/datum/bt_node/decorator/bb_key_true","children":[{"type":"/datum/bt_node/ai_behavior/succeed"}],"invert":true,"key":"BB_WANTS_TO_COMMIT_THEFT"},{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"item_to_steal","targeting_strategy":"/datum/targeting_strategy/pickup_item/stealable_item","target_source":"/datum/target_source/oview","must_be_reachable":true},{"type":"/datum/bt_node/ai_behavior/set_bb_key","target_key":"BB_WANTS_TO_COMMIT_THEFT","value":false}]}]} diff --git a/build/behavior_trees/datums/ai/generic_subtrees/forage_and_retaliate.bt.compiled.json b/build/behavior_trees/datums/ai/generic_subtrees/forage_and_retaliate.bt.compiled.json new file mode 100644 index 00000000000..2b9df392144 --- /dev/null +++ b/build/behavior_trees/datums/ai/generic_subtrees/forage_and_retaliate.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"key":"BB_basic_mob_shitlist","observer_abort":1},{"type":"/datum/bt_node/subtree/move_to_and_eat"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/subtree/random_walk"}],"key":"BB_disable_idle","invert":true}]},{"type":"$z4n9bk7p"},{"type":"$q8w3rtv1"}],"__bindings":{"q8w3rtv1":{"label":"Food Finder","default":"/datum/bt_node/subtree/find_food"},"z4n9bk7p":{"label":"Retaliate Manager","default":"/datum/bt_node/subtree/capricious_pick_target"}},"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/datums/ai/generic_subtrees/move_to_and_eat.bt.compiled.json b/build/behavior_trees/datums/ai/generic_subtrees/move_to_and_eat.bt.compiled.json new file mode 100644 index 00000000000..1eb1dcf2626 --- /dev/null +++ b/build/behavior_trees/datums/ai/generic_subtrees/move_to_and_eat.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_TARGET_FOOD","required_dist":1},{"type":"/datum/bt_node/ai_behavior/perform_emote","emote":"eats up happily!"},{"type":"/datum/bt_node/ai_behavior/ai_interact","target_key":"BB_TARGET_FOOD"}]}],"observer_abort":3,"key":"BB_TARGET_FOOD"} diff --git a/build/behavior_trees/datums/ai/generic_subtrees/move_to_and_hunt.bt.compiled.json b/build/behavior_trees/datums/ai/generic_subtrees/move_to_and_hunt.bt.compiled.json new file mode 100644 index 00000000000..b1a430d4014 --- /dev/null +++ b/build/behavior_trees/datums/ai/generic_subtrees/move_to_and_hunt.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"$b3y599q4","required_dist":1},{"type":"/datum/bt_node/ai_behavior/hunt_target/interact_with_target","target_key":"$brrasnah","always_reset_target":"$b3cnse9r","hunt_cooldown":"$bqwjf4id","cooldown_key":"$bm3y5m55","behavior_combat_mode":"$bd1towgc"}]}],"observer_abort":3,"key":"$bvtz06kb","__bindings":{"bvtz06kb":{"label":"hunting_target","default":"BB_low_priority_hunting_target"},"b3y599q4":{"label":"hunting_target","default":"BB_low_priority_hunting_target"},"brrasnah":{"label":"hunting_target","default":"BB_low_priority_hunting_target"},"bqwjf4id":{"label":"hunt_cooldown","default":50},"bm3y5m55":{"label":"cooldown_key","default":""},"bd1towgc":{"label":"behavior_combat_mode","default":1},"b3cnse9r":{"label":"always_reset_target","default":1}}} diff --git a/build/behavior_trees/datums/ai/generic_subtrees/move_to_reinforce.bt.compiled.json b/build/behavior_trees/datums/ai/generic_subtrees/move_to_reinforce.bt.compiled.json new file mode 100644 index 00000000000..486a465bbc0 --- /dev/null +++ b/build/behavior_trees/datums/ai/generic_subtrees/move_to_reinforce.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_basic_mob_reinforcement_target","required_dist":0,"finish_on_arrival":false},{"type":"/datum/bt_node/ai_behavior/clear_key","key":"BB_basic_mob_reinforcement_target"}]}],"observer_abort":2,"key":"BB_basic_mob_reinforcement_target"} diff --git a/build/behavior_trees/datums/ai/generic_subtrees/pick_retaliate_target.bt.compiled.json b/build/behavior_trees/datums/ai/generic_subtrees/pick_retaliate_target.bt.compiled.json new file mode 100644 index 00000000000..ad3e6b31cb1 --- /dev/null +++ b/build/behavior_trees/datums/ai/generic_subtrees/pick_retaliate_target.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"} diff --git a/build/behavior_trees/datums/ai/generic_subtrees/random_walk.bt.compiled.json b/build/behavior_trees/datums/ai/generic_subtrees/random_walk.bt.compiled.json new file mode 100644 index 00000000000..82e2292b6d1 --- /dev/null +++ b/build/behavior_trees/datums/ai/generic_subtrees/random_walk.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/true_for_time","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/idle_random_walk","walk_chance":"$bf07i8ep"}],"success_policy":1,"failure_policy":0,"loop_delay":15.0}],"duration":50,"__bindings":{"bf07i8ep":{"label":"walk_chance","default":25}}} diff --git a/build/behavior_trees/datums/ai/generic_subtrees/skittish_and_speak.bt.compiled.json b/build/behavior_trees/datums/ai/generic_subtrees/skittish_and_speak.bt.compiled.json new file mode 100644 index 00000000000..908394cb4b4 --- /dev/null +++ b/build/behavior_trees/datums/ai/generic_subtrees/skittish_and_speak.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/subtree/simple_skittish_combat"}],"success_policy":1,"failure_policy":1},{"type":"$f17iiafz"}],"__bindings":{"f17iiafz":{"label":"Speech Behavior","default":"/datum/bt_node/ai_behavior/random_speech_blackboard"}},"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/datums/ai/generic_subtrees/steal_and_flee.bt.compiled.json b/build/behavior_trees/datums/ai/generic_subtrees/steal_and_flee.bt.compiled.json new file mode 100644 index 00000000000..65ded70c2de --- /dev/null +++ b/build/behavior_trees/datums/ai/generic_subtrees/steal_and_flee.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"item_to_steal"},{"type":"/datum/bt_node/ai_behavior/drag_target","target_key":"item_to_steal"},{"type":"/datum/bt_node/ai_behavior/copy_bb_key","dest_key":"BB_LAST_STOLEN_ITEM","source_key":"item_to_steal"},{"type":"/datum/bt_node/ai_behavior/clear_key","key":"item_to_steal"}]}],"observer_abort":3,"key":"item_to_steal"} diff --git a/build/behavior_trees/datums/ai/hauntium/haunted.bt.compiled.json b/build/behavior_trees/datums/ai/hauntium/haunted.bt.compiled.json new file mode 100644 index 00000000000..c661f45cbaa --- /dev/null +++ b/build/behavior_trees/datums/ai/hauntium/haunted.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/item_being_held","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/wait","duration":0}],"key":"BB_likes_equipper","observer_abort":1},{"type":"/datum/bt_node/ai_behavior/item_escape_grasp"}]}],"observer_abort":3},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/throw_attack/haunted","target_key":"BB_haunt_target","throw_count_key":"BB_haunted_throw_attempt_count","haunt_list_key":"BB_to_haunt_list"}],"success_policy":1,"failure_policy":0,"loop_delay":20},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_haunt_target","required_dist":3,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"BB_haunt_target"},{"type":"/datum/bt_node/ai_behavior/idle_ghost_item"}]},{"type":"/datum/bt_node/ai_behavior/pick_haunt_target","target_key":"BB_haunt_target","haunt_list_key":"BB_to_haunt_list"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/datums/ai/monkey/monkey.bt.compiled.json b/build/behavior_trees/datums/ai/monkey/monkey.bt.compiled.json new file mode 100644 index 00000000000..6a0c024cb9a --- /dev/null +++ b/build/behavior_trees/datums/ai/monkey/monkey.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/subtree/monkey_combat"},{"type":"/datum/bt_node/subtree/monkey_serve_food"},{"type":"/datum/bt_node/subtree/generic_hunger"},{"type":"/datum/bt_node/subtree/generic_play_instrument"},{"type":"/datum/bt_node/subtree/monkey_shenanigans"},{"type":"/datum/bt_node/ai_behavior/monkey_idle"}]},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/monkey_set_combat_target","attack_target_key":"Current Target","enemies_key":"BB_monkey_enemies"},{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_true","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_monkey_current_press_target","targeting_strategy":"/datum/targeting_strategy/anything","target_source":"/datum/target_source/monkey_press_target","vision_range":2}],"key":"Monkeys wants to press something"},{"type":"/datum/bt_node/decorator/bb_key_true","children":[{"type":"/datum/bt_node/decorator/random_chance","children":[{"type":"/datum/bt_node/ai_behavior/set_bb_key","target_key":"Monkeys wants to press something","value":true}],"chance":0.2}],"invert":true,"key":"Monkeys wants to press something"},{"type":"/datum/bt_node/ai_behavior/succeed"}]},{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/target_is_holding_item","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_monkey_current_give_target","targeting_strategy":"/datum/targeting_strategy/anything","target_source":"/datum/target_source/oview_single_type/human_mob","vision_range":2}],"key":"Literally me"},{"type":"/datum/bt_node/decorator/target_is_holding_item","children":[{"type":"/datum/bt_node/ai_behavior/clear_key","key":"BB_monkey_current_give_target"}],"invert":true,"key":"Literally me"},{"type":"/datum/bt_node/ai_behavior/succeed"}]},{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/is_holding_target","children":[{"type":"/datum/bt_node/ai_behavior/succeed"}],"key":"BB_SONG_INSTRUMENT"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_SONG_INSTRUMENT","targeting_strategy":"/datum/targeting_strategy/pickup_item","target_source":"/datum/target_source/held_items_typed/instrument"}],"key":"BB_monkey_tamed"},{"type":"/datum/bt_node/ai_behavior/succeed"}]},{"type":"/datum/bt_node/decorator/pawn_nutrition_below","children":[{"type":"/datum/bt_node/decorator/key_off_cooldown","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"bb_food_target","targeting_strategy":"/datum/targeting_strategy/pickup_item/food_or_drink","target_source":"/datum/target_source/held_items_then_oview","vision_range":2}],"cooldown_key":"BB_NEXT_HUNGRY"}]},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target/monkey_find_weapon","target_key":"BB_monkey_pickuptarget","target_source":"/datum/target_source/monkey_weapon_upgrade","targeting_strategy":"/datum/targeting_strategy/monkey_weapon_upgrade","vision_range":5}],"invert":true,"key":"BB_monkey_tamed"}]}]}],"key":"Current Target","invert":true},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target/monkey_find_weapon","target_key":"BB_monkey_pickuptarget","target_source":"/datum/target_source/monkey_weapon_upgrade","targeting_strategy":"/datum/targeting_strategy/monkey_weapon_upgrade","vision_range":5}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/datums/ai/monkey/monkey_combat.bt.compiled.json b/build/behavior_trees/datums/ai/monkey/monkey_combat.bt.compiled.json new file mode 100644 index 00000000000..391d5f70aa5 --- /dev/null +++ b/build/behavior_trees/datums/ai/monkey/monkey_combat.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/is_at_distance","children":[{"type":"/datum/bt_node/ai_behavior/set_bb_key","target_key":"Current Target"}],"observer_abort":2,"invert":true,"target_key":"Current Target","maximum_distance":10,"require_reach":false},{"type":"/datum/bt_node/decorator/pawn_health_below","children":[{"type":"/datum/bt_node/subtree/run_away_from_target"}],"observer_abort":3,"health_threshold":40},{"type":"/datum/bt_node/subtree/monkey_find_weapon"},{"type":"/datum/bt_node/decorator/mob_stat_at_least","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_monkey_target_disposal","target_source":"/datum/target_source/oview_single_type/disposal_unit","targeting_strategy":"/datum/targeting_strategy/anything","vision_range":9},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":true},{"type":"/datum/bt_node/ai_behavior/grab_target","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_monkey_target_disposal","required_dist":1,"finish_on_arrival":true},{"type":"/datum/bt_node/ai_behavior/stuff_in_disposal","attack_target_key":"Current Target","disposal_target_key":"BB_monkey_target_disposal"},{"type":"/datum/bt_node/ai_behavior/set_bb_key","target_key":"Current Target"}]},{"type":"/datum/bt_node/ai_behavior/set_bb_key","target_key":"Current Target"}]}],"observer_abort":2,"invert":false,"key":"Current Target","min_stat":1},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/monkey_attack_mob","target_key":"Current Target"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}]},{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/decorator/cooldown","children":[{"type":"/datum/bt_node/ai_behavior/recruit_monkeys","target_key":"Current Target"}],"cooldown_key":"BB_monkey_recruit_cooldown","cooldown_duration":10}],"success_policy":1,"failure_policy":1,"loop_delay":50},{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/decorator/cooldown","children":[{"type":"/datum/bt_node/decorator/random_chance","children":[{"type":"/datum/bt_node/ai_behavior/battle_screech/monkey"}],"chance":0.25}],"cooldown_key":"Battle Screech Cooldown","cooldown_duration":5}],"success_policy":1,"failure_policy":1,"loop_delay":10},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target/monkey_find_weapon","time_between_perform":0,"target_key":"BB_monkey_pickuptarget","targeting_strategy":"/datum/targeting_strategy/monkey_weapon_upgrade","target_source":"/datum/target_source/monkey_weapon_upgrade","vision_range":5}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":2.0,"finish_on_primary":true}],"key":"Current Target","observer_abort":2} diff --git a/build/behavior_trees/datums/ai/monkey/monkey_find_weapon.bt.compiled.json b/build/behavior_trees/datums/ai/monkey/monkey_find_weapon.bt.compiled.json new file mode 100644 index 00000000000..a9d52200bef --- /dev/null +++ b/build/behavior_trees/datums/ai/monkey/monkey_find_weapon.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_monkey_pickuptarget","required_dist":1,"finish_on_arrival":true,"movement_type":"/datum/ai_movement/jps"},{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/monkey_equip/pickpocket","target_key":"BB_monkey_pickuptarget"}],"key":"BB_monkey_pickup_is_pickpocket"},{"type":"/datum/bt_node/ai_behavior/monkey_equip/ground","target_key":"BB_monkey_pickuptarget"}]}]}],"observer_abort":3,"key":"BB_monkey_pickuptarget"} diff --git a/build/behavior_trees/datums/ai/monkey/monkey_serve_food.bt.compiled.json b/build/behavior_trees/datums/ai/monkey/monkey_serve_food.bt.compiled.json new file mode 100644 index 00000000000..9fe06ea5404 --- /dev/null +++ b/build/behavior_trees/datums/ai/monkey/monkey_serve_food.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/decorator/cooldown","children":[{"type":"/datum/bt_node/ai_behavior/monkey_find_patrons","patrons_key":"BB_monkey_patrons_nearby","give_target_key":"BB_monkey_current_give_target"}],"cooldown_key":"Monkeys can look for patron","cooldown_duration":10,"lock_on_succeed":false},{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/is_holding_target","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_monkey_current_give_target","required_dist":1,"finish_on_arrival":true,"movement_type":"/datum/ai_movement/jps"},{"type":"/datum/bt_node/ai_behavior/give","target_key":"BB_monkey_current_give_target"}]}],"key":"BB_monkey_current_served_item"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_monkey_current_served_item","targeting_strategy":"/datum/targeting_strategy/pickup_item/food_or_drink/include_drinks","target_source":"/datum/target_source/held_items_then_oview","vision_range":2,"must_be_reachable":true},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_monkey_current_served_item","required_dist":1,"finish_on_arrival":true,"movement_type":"/datum/ai_movement/jps"},{"type":"/datum/bt_node/ai_behavior/pick_up","target_key":"BB_monkey_current_served_item","drop_held":true}]}],"invert":false,"key":"BB_monkey_current_served_item"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_monkey_current_served_item","targeting_strategy":"/datum/targeting_strategy/pickup_item/food_or_drink/include_drinks","target_source":"/datum/target_source/held_items_then_oview","vision_range":2,"must_be_reachable":true}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]}]}],"key":"BB_monkey_tamed"} diff --git a/build/behavior_trees/datums/ai/monkey/monkey_shenanigans.bt.compiled.json b/build/behavior_trees/datums/ai/monkey/monkey_shenanigans.bt.compiled.json new file mode 100644 index 00000000000..e8beeaa4625 --- /dev/null +++ b/build/behavior_trees/datums/ai/monkey/monkey_shenanigans.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/target_is_holding_item","children":[{"type":"/datum/bt_node/decorator/random_chance","children":[{"type":"/datum/bt_node/ai_behavior/use_in_hand"}],"chance":0.05}],"key":"Literally me"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_monkey_current_press_target","required_dist":1,"finish_on_arrival":true,"movement_type":"/datum/ai_movement/jps"},{"type":"/datum/bt_node/ai_behavior/use_on_object","target_key":"BB_monkey_current_press_target"},{"type":"/datum/bt_node/ai_behavior/set_bb_key","target_key":"BB_monkey_current_press_target"},{"type":"/datum/bt_node/ai_behavior/clear_key","key":"Monkeys wants to press something"}]}],"key":"BB_monkey_current_press_target"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/decorator/random_chance_from_key","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_monkey_current_give_target","required_dist":1,"finish_on_arrival":true,"movement_type":"/datum/ai_movement/jps"},{"type":"/datum/bt_node/ai_behavior/give","target_key":"BB_monkey_current_give_target"}]}],"chance_key":"BB_monkey_give_chance"}],"key":"BB_monkey_current_give_target"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/subtree/monkey_find_weapon"}],"key":"BB_monkey_tamed"}]} diff --git a/build/behavior_trees/datums/ai/objects/vending_machines/vending_machine.bt.compiled.json b/build/behavior_trees/datums/ai/objects/vending_machines/vending_machine.bt.compiled.json new file mode 100644 index 00000000000..dde504f3c33 --- /dev/null +++ b/build/behavior_trees/datums/ai/objects/vending_machines/vending_machine.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/vending_is_tilted","children":[{"type":"/datum/bt_node/decorator/key_off_cooldown","children":[{"type":"/datum/bt_node/ai_behavior/vendor_rise_up"}],"cooldown_key":"BB_vending_untilt_cooldown"}]},{"type":"/datum/bt_node/decorator/vending_is_tilted","children":[{"type":"/datum/bt_node/decorator/key_off_cooldown","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/find_vendor_target","target_key":"BB_vending_current_target","vision_range":7},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_vending_current_target","required_dist":1,"finish_on_arrival":true},{"type":"/datum/bt_node/ai_behavior/vendor_crush","target_key":"BB_vending_current_target"}]}],"cooldown_key":"BB_vending_tilt_cooldown"}],"invert":true}]} diff --git a/build/behavior_trees/datums/ai/robot_customer/robot_customer.bt.compiled.json b/build/behavior_trees/datums/ai/robot_customer/robot_customer.bt.compiled.json new file mode 100644 index 00000000000..55b0e771a31 --- /dev/null +++ b/build/behavior_trees/datums/ai/robot_customer/robot_customer.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/robot_customer/find_exit_portal"},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_customer_exit_portal","required_dist":0},{"type":"/datum/bt_node/ai_behavior/robot_customer/leave_venue"}]}],"key":"BB_customer_leaving","observer_abort":2},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_customer_current_target","required_dist":1},{"type":"/datum/bt_node/ai_behavior/break_spine","target_key":"BB_customer_current_target"}]}],"key":"BB_customer_current_target","observer_abort":2},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/decorator/cooldown","children":[{"type":"/datum/bt_node/ai_behavior/robot_customer/find_seat"}],"cooldown_key":"BB_customer_find_seat_cooldown","cooldown_duration":80,"lock_on_succeed":false}],"key":"BB_customer_my_seat","invert":true},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_customer_my_seat","required_dist":0},{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/robot_customer/order_food"}],"key":"BB_customer_current_order","invert":true},{"type":"/datum/bt_node/ai_behavior/robot_customer/wait_for_food"}]}]}],"key":"BB_customer_my_seat"}]} diff --git a/build/behavior_trees/modules/bitrunning/antagonists/netguardian.bt.compiled.json b/build/behavior_trees/modules/bitrunning/antagonists/netguardian.bt.compiled.json new file mode 100644 index 00000000000..c43cd3695dc --- /dev/null +++ b/build/behavior_trees/modules/bitrunning/antagonists/netguardian.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability","ability_key":"netguardian_rocket","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/basic_ranged_attack/avoid_friendly_fire","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location","time_between_perform":10},{"type":"/datum/bt_node/ai_behavior/attack_obstructions","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets/most_wounded","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/bitrunning/virtual_domain/domains/crewman.bt.compiled.json b/build/behavior_trees/modules/bitrunning/virtual_domain/domains/crewman.bt.compiled.json new file mode 100644 index 00000000000..694c63452df --- /dev/null +++ b/build/behavior_trees/modules/bitrunning/virtual_domain/domains/crewman.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_equals","children":[{"type":"/datum/bt_node/decorator/cooldown","children":[{"type":"/datum/bt_node/ai_behavior/call_reinforcements"}],"observer_abort":2,"cooldown_key":"BB_basic_mob_reinforcements_cooldown","cooldown_duration":300,"lock_on_succeed":true}],"key":"BB_calls_reinforcements","value":true},{"type":"/datum/bt_node/ai_behavior/attack_obstructions","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/move_to_reinforce"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/bitrunning/virtual_domain/domains/crewman_hostile.bt.compiled.json b/build/behavior_trees/modules/bitrunning/virtual_domain/domains/crewman_hostile.bt.compiled.json new file mode 100644 index 00000000000..644c7eefb55 --- /dev/null +++ b/build/behavior_trees/modules/bitrunning/virtual_domain/domains/crewman_hostile.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_equals","children":[{"type":"/datum/bt_node/decorator/cooldown","children":[{"type":"/datum/bt_node/ai_behavior/call_reinforcements"}],"observer_abort":2,"cooldown_key":"BB_basic_mob_reinforcements_cooldown","cooldown_duration":300,"lock_on_succeed":true}],"key":"BB_calls_reinforcements","value":true},{"type":"/datum/bt_node/ai_behavior/attack_obstructions","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/move_to_reinforce"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/bitrunning/virtual_domain/domains/crewman_hostile_ranged.bt.compiled.json b/build/behavior_trees/modules/bitrunning/virtual_domain/domains/crewman_hostile_ranged.bt.compiled.json new file mode 100644 index 00000000000..c05b838e02b --- /dev/null +++ b/build/behavior_trees/modules/bitrunning/virtual_domain/domains/crewman_hostile_ranged.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_equals","children":[{"type":"/datum/bt_node/decorator/cooldown","children":[{"type":"/datum/bt_node/ai_behavior/call_reinforcements"}],"observer_abort":2,"cooldown_key":"BB_basic_mob_reinforcements_cooldown","cooldown_duration":300,"lock_on_succeed":true}],"key":"BB_calls_reinforcements","value":true},{"type":"/datum/bt_node/ai_behavior/attack_obstructions","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/basic_ranged_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/ai_behavior/succeed"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/maintain_distance","target_key":"Current Target"}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/move_to_reinforce"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/bitrunning/virtual_domain/domains/crewman_ranged.bt.compiled.json b/build/behavior_trees/modules/bitrunning/virtual_domain/domains/crewman_ranged.bt.compiled.json new file mode 100644 index 00000000000..48e54e5e9c8 --- /dev/null +++ b/build/behavior_trees/modules/bitrunning/virtual_domain/domains/crewman_ranged.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_equals","children":[{"type":"/datum/bt_node/decorator/cooldown","children":[{"type":"/datum/bt_node/ai_behavior/call_reinforcements"}],"observer_abort":2,"cooldown_key":"BB_basic_mob_reinforcements_cooldown","cooldown_duration":300,"lock_on_succeed":true}],"key":"BB_calls_reinforcements","value":true},{"type":"/datum/bt_node/ai_behavior/attack_obstructions","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/basic_ranged_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/ai_behavior/succeed"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/maintain_distance","target_key":"Current Target"}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/move_to_reinforce"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/alien/alien.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/alien/alien.bt.compiled.json new file mode 100644 index 00000000000..55d0d44b70e --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/alien/alien.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/subtree/basic_alien","bindings":{"buewuqr6":"/datum/bt_node/subtree/melee_alien_combat","bkzbx73d":"/datum/bt_node/subtree/lay_alien_egg"}} diff --git a/build/behavior_trees/modules/mob/living/basic/alien/basic_alien.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/alien/basic_alien.bt.compiled.json new file mode 100644 index 00000000000..1b0824cb991 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/alien/basic_alien.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"$buewuqr6"}],"observer_abort":3,"key":"Current Target"},{"type":"$bkzbx73d"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}],"__bindings":{"buewuqr6":{"label":"Combat Subtree","default":"/datum/bt_node/subtree"},"bkzbx73d":{"label":"Idle Behavior","default":"/datum/bt_node/subtree/random_walk"}}} diff --git a/build/behavior_trees/modules/mob/living/basic/alien/drone.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/alien/drone.bt.compiled.json new file mode 100644 index 00000000000..d3adf7228ef --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/alien/drone.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/subtree/basic_alien","bindings":{"buewuqr6":"/datum/bt_node/subtree/melee_alien_combat","bkzbx73d":"/datum/bt_node/ai_behavior/plant_alien_weeds"}} diff --git a/build/behavior_trees/modules/mob/living/basic/alien/lay_alien_egg.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/alien/lay_alien_egg.bt.compiled.json new file mode 100644 index 00000000000..18caaa98520 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/alien/lay_alien_egg.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/cooldown","children":[{"type":"/datum/bt_node/ai_behavior/lay_alien_egg"}],"observer_abort":2,"cooldown_key":"BB_ALIEN_PLANT_COOLDOWN","cooldown_duration":300} diff --git a/build/behavior_trees/modules/mob/living/basic/alien/melee_alien_combat.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/alien/melee_alien_combat.bt.compiled.json new file mode 100644 index 00000000000..7fe5ec46d8a --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/alien/melee_alien_combat.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/attack_obstructions","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true} diff --git a/build/behavior_trees/modules/mob/living/basic/alien/plant_alien_weeds.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/alien/plant_alien_weeds.bt.compiled.json new file mode 100644 index 00000000000..17a0c23cebb --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/alien/plant_alien_weeds.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/cooldown","children":[{"type":"/datum/bt_node/ai_behavior/plant_alien_weeds"}],"observer_abort":2,"cooldown_key":"BB_ALIEN_PLANT_COOLDOWN","cooldown_duration":300},{"type":"/datum/bt_node/ai_behavior/idle_random_walk"}]} diff --git a/build/behavior_trees/modules/mob/living/basic/alien/queen.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/alien/queen.bt.compiled.json new file mode 100644 index 00000000000..eaf72e8cb15 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/alien/queen.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/subtree/basic_alien","bindings":{"buewuqr6":"/datum/bt_node/subtree/ranged_alien_combat","bkzbx73d":"/datum/bt_node/ai_behavior/lay_alien_egg"}} diff --git a/build/behavior_trees/modules/mob/living/basic/alien/ranged_alien_combat.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/alien/ranged_alien_combat.bt.compiled.json new file mode 100644 index 00000000000..2924f00ec15 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/alien/ranged_alien_combat.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/attack_obstructions","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/basic_ranged_attack","time_between_perform":30,"target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location","avoid_friendly_fire":true}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/maintain_distance","target_key":"Current Target","approach_movement_type":"/datum/ai_movement/basic_avoidance"}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true} diff --git a/build/behavior_trees/modules/mob/living/basic/alien/sentinel.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/alien/sentinel.bt.compiled.json new file mode 100644 index 00000000000..65bf067bf1c --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/alien/sentinel.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/subtree/basic_alien","bindings":{"buewuqr6":"/datum/bt_node/subtree/ranged_alien_combat"}} diff --git a/build/behavior_trees/modules/mob/living/basic/blob_minions/blob_spore.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/blob_minions/blob_spore.bt.compiled.json new file mode 100644 index 00000000000..9e00321a543 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/blob_minions/blob_spore.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/subtree/move_to_and_hunt","bindings":{"bvtz06kb":"BB_current_hunting_target","b3y599q4":"BB_current_hunting_target","brrasnah":"BB_current_hunting_target"}},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_travel_destination","required_dist":0},{"type":"/datum/bt_node/ai_behavior/clear_key","key":"BB_travel_destination"}]}],"observer_abort":3,"key":"BB_travel_destination"},{"type":"/datum/bt_node/subtree/simple_hostile_obstacles_combat"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_current_hunting_target","targeting_strategy":"/datum/targeting_strategy/dead_mob","target_source":"/datum/target_source/oview_single_type/human_mob"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true} diff --git a/build/behavior_trees/modules/mob/living/basic/blob_minions/blob_zombie.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/blob_minions/blob_zombie.bt.compiled.json new file mode 100644 index 00000000000..04e9fa2cdc2 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/blob_minions/blob_zombie.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_travel_destination","required_dist":0},{"type":"/datum/bt_node/ai_behavior/clear_key","key":"BB_travel_destination"}]}],"observer_abort":3,"key":"BB_travel_destination"},{"type":"/datum/bt_node/subtree/simple_hostile_obstacles_combat"}]} diff --git a/build/behavior_trees/modules/mob/living/basic/blob_minions/blobbernaut.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/blob_minions/blobbernaut.bt.compiled.json new file mode 100644 index 00000000000..2d83f85aebc --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/blob_minions/blobbernaut.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/subtree/simple_hostile_obstacles_combat"}]} diff --git a/build/behavior_trees/modules/mob/living/basic/boss/blood_drunk_miner/blood_drunk_miner.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/boss/blood_drunk_miner/blood_drunk_miner.bt.compiled.json new file mode 100644 index 00000000000..89cd89c9ea7 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/boss/blood_drunk_miner/blood_drunk_miner.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/blood_drunk_miner_combat"},{"type":"/datum/bt_node/ai_behavior/wait","duration":0}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/boss/blood_drunk_miner/blood_drunk_miner_combat.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/boss/blood_drunk_miner/blood_drunk_miner_combat.bt.compiled.json new file mode 100644 index 00000000000..d1701ec34c4 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/boss/blood_drunk_miner/blood_drunk_miner_combat.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/attack_obstructions","target_key":"Current Target"},{"type":"/datum/bt_node/decorator/mob_stat_at_least","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability","ability_key":"BB_bdm_kinetic_accelerator_ability","target_key":"Current Target","maximum_distance":3},{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability","ability_key":"BB_bdm_transform_weapon_ability","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/succeed"}]}]}],"key":"Current Target"},{"type":"/datum/bt_node/decorator/is_at_distance","children":[{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability","ability_key":"BB_bdm_dash_attack_ability","target_key":"Current Target"}],"invert":false,"target_key":"Current Target","min_distance":3},{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability","ability_key":"BB_bdm_transform_weapon_ability","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/succeed"}]}]}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"} diff --git a/build/behavior_trees/modules/mob/living/basic/boss/thing/thing_aoe.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/boss/thing/thing_aoe.bt.compiled.json new file mode 100644 index 00000000000..e7dd1dadd49 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/boss/thing/thing_aoe.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/bb_key_true","children":[{"type":"/datum/bt_node/decorator/bb_key_true","children":[{"type":"/datum/bt_node/decorator/pawn_has_trait_from","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/set_bb_key","target_key":"BB_THETHING_MELEEMODE","value":true},{"type":"/datum/bt_node/ai_behavior/pick_random_ability","ability_keys":["BB_THETHING_DECIMATE","BB_THETHING_BIGTENDRILS","BB_THETHING_CARDTENDRILS","BB_THETHING_ACIDSPIT"],"last_used_key":"BB_THETHING_LASTAOE","result_key":"BB_generic_action"},{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability","ability_key":"BB_generic_action","target_key":"Current Target"},{"type":"/datum/bt_node/decorator/random_chance","children":[{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability","ability_key":"BB_THETHING_CHARGE","target_key":"Current Target"}],"chance":0.6}]}],"observer_abort":1,"invert":true,"trait":"immobilized","source":"megafauna"}],"invert":true,"key":"BB_THETHING_MELEEMODE"}],"invert":true,"key":"BB_THETHING_NOAOE"} diff --git a/build/behavior_trees/modules/mob/living/basic/boss/thing/thing_boss.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/boss/thing/thing_boss.bt.compiled.json new file mode 100644 index 00000000000..a85c8dc2f81 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/boss/thing/thing_boss.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/thing_aoe"},{"type":"/datum/bt_node/subtree/thing_melee"}]}],"success_policy":1,"failure_policy":1}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/wait","duration":0}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location","vision_range":6}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/boss/thing/thing_melee.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/boss/thing/thing_melee.bt.compiled.json new file mode 100644 index 00000000000..dc2607af78c --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/boss/thing/thing_melee.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/true_for_time","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/set_bb_key","target_key":"BB_THETHING_MELEEMODE","value":false},{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability","ability_key":"BB_THETHING_SHRIEK","target_key":"Current Target","maximum_distance":2},{"type":"/datum/bt_node/decorator/is_at_distance","children":[{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability","ability_key":"BB_THETHING_CHARGE","target_key":"Current Target"}],"target_key":"Current Target","min_distance":3},{"type":"/datum/bt_node/decorator/pawn_has_trait_from","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":1,"invert":true,"trait":"immobilized","source":"megafauna"}]}]}],"observer_abort":1,"duration":50} diff --git a/build/behavior_trees/modules/mob/living/basic/bots/bot.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/bots/bot.bt.compiled.json new file mode 100644 index 00000000000..b1a4e2b000f --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/bots/bot.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity/pacifist"},{"type":"/datum/bt_node/subtree/bot_respond_to_summon"},{"type":"/datum/bt_node/subtree/bot_salute_authority"},{"type":"/datum/bt_node/subtree/bot_patrol"}]} diff --git a/build/behavior_trees/modules/mob/living/basic/bots/cleanbot/clean_pet_target.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/bots/cleanbot/clean_pet_target.bt.compiled.json new file mode 100644 index 00000000000..67a0637e173 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/bots/cleanbot/clean_pet_target.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_current_pet_target","required_dist":1},{"type":"/datum/bt_node/ai_behavior/execute_clean","target_key":"BB_current_pet_target"}]}],"observer_abort":3,"key":"BB_current_pet_target"} diff --git a/build/behavior_trees/modules/mob/living/basic/bots/cleanbot/cleanbot.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/bots/cleanbot/cleanbot.bt.compiled.json new file mode 100644 index 00000000000..afade702a03 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/bots/cleanbot/cleanbot.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity/pacifist"},{"type":"/datum/bt_node/subtree/bot_respond_to_summon"},{"type":"/datum/bt_node/decorator/override_id_set","children":[{"type":"/datum/bt_node/subtree","override_id":"pet_command"}],"observer_abort":2,"override_id":"pet_command"},{"type":"/datum/bt_node/decorator/bot_is_emagged","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":true},{"type":"/datum/bt_node/ai_behavior/execute_clean","target_key":"Current Target"}]}],"key":"Current Target","observer_abort":3},{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"friendly_janitor","required_dist":1,"finish_on_arrival":true,"movement_type":"/datum/ai_movement/basic_avoidance"},{"type":"/datum/bt_node/ai_behavior/befriend_target","target_key":"friendly_janitor","befriend_message":"friendly_message"}]}],"observer_abort":3,"key":"friendly_janitor"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"friendly_janitor","target_source":"/datum/target_source/oview_single_type/human_mob","targeting_strategy":"/datum/targeting_strategy/conscious_human/cleanbot_whisperer","vision_range":5,"time_between_perform":300}],"key":"friendly_janitor","invert":true}]},{"type":"/datum/bt_node/decorator/key_off_cooldown","children":[{"type":"/datum/bt_node/subtree/bot_patrol"}],"observer_abort":0,"cooldown_key":"post_clean_cooldown"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","time_between_perform":30,"target_key":"Current Target","targeting_strategy":"/datum/targeting_strategy/pickup_item","target_source":"/datum/target_source/cleanbot_cleanables","vision_range":5,"ignore_list_key":"temporary_ignore_list","must_be_reachable":true,"reach_distance":15},{"type":"/datum/bt_node/subtree/bot_salute_authority"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}],"invert":true},{"type":"/datum/bt_node/decorator/bot_is_emagged","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/cooldown","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"acid_spray_target","required_dist":0,"finish_on_arrival":true,"movement_type":"/datum/ai_movement/basic_avoidance"},{"type":"/datum/bt_node/ai_behavior/execute_clean","target_key":"acid_spray_target"}]}],"observer_abort":3,"key":"acid_spray_target"}],"observer_abort":2,"cooldown_key":"acid_spray_cooldown","cooldown_duration":30},{"type":"/datum/bt_node/ai_behavior/use_mob_ability","ability_key":"cleanbot_foam"},{"type":"/datum/bt_node/decorator/key_off_cooldown","children":[{"type":"/datum/bt_node/subtree/bot_patrol"}],"cooldown_key":"post_clean_cooldown"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"acid_spray_target","target_source":"/datum/target_source/oview_single_type/human_mob","targeting_strategy":"/datum/targeting_strategy/conscious_human","ignore_list_key":"temporary_ignore_list","vision_range":5,"time_between_perform":300}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]}]} diff --git a/build/behavior_trees/modules/mob/living/basic/bots/dedbot.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/bots/dedbot.bt.compiled.json new file mode 100644 index 00000000000..6329cd4a108 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/bots/dedbot.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":true,"movement_type":"/datum/ai_movement/basic_avoidance"},{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability/melee","ability_key":"BB_dedbot_exenterate","target_key":"Current Target"}]}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/bot_respond_to_summon"},{"type":"/datum/bt_node/subtree/bot_patrol"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/bots/ed209/ed209.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/bots/ed209/ed209.bt.compiled.json new file mode 100644 index 00000000000..3134c05c97b --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/bots/ed209/ed209.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity/pacifist"},{"type":"/datum/bt_node/subtree/bot_respond_to_summon"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/is_target_stunned","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":true,"movement_type":"/datum/ai_movement/basic_avoidance"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_ranged_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location","max_range":9}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/maintain_distance","target_key":"Current Target","approach_movement_type":"/datum/ai_movement/basic_avoidance"}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}]}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/bot_patrol"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":false,"repeat_secondary_delay":10,"finish_on_primary":false}]} diff --git a/build/behavior_trees/modules/mob/living/basic/bots/ed209/ed209_syndicate.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/bots/ed209/ed209_syndicate.bt.compiled.json new file mode 100644 index 00000000000..b1918838724 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/bots/ed209/ed209_syndicate.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity/pacifist"},{"type":"/datum/bt_node/subtree/bot_respond_to_summon"},{"type":"/datum/bt_node/subtree/simple_ranged_combat","bindings":{"bsjjwub2":"/datum/bt_node/subtree/bot_patrol"}}]} diff --git a/build/behavior_trees/modules/mob/living/basic/bots/firebot/firebot.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/bots/firebot/firebot.bt.compiled.json new file mode 100644 index 00000000000..cd1dc47b6f4 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/bots/firebot/firebot.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity/pacifist"},{"type":"/datum/bt_node/subtree/bot_respond_to_summon"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/announce_fire_detected"},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":true,"movement_type":"/datum/ai_movement/basic_avoidance"},{"type":"/datum/bt_node/ai_behavior/bot_interact/extinguish","target_key":"Current Target"}]}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/bot_patrol"}]},{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"Current Target","target_source":"/datum/target_source/firebot_targets","targeting_strategy":"/datum/targeting_strategy/extinguishable_person","ignore_list_key":"temporary_ignore_list","must_be_reachable":true,"vision_range":5},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"Current Target","target_source":"/datum/target_source/range_turfs/firebot_hotspots","targeting_strategy":"/datum/targeting_strategy/burning_hotspot","ignore_list_key":"temporary_ignore_list","must_be_reachable":true,"vision_range":5}]}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]},{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/handle_firebot_speech"},{"type":"/datum/bt_node/subtree/bot_salute_authority"}]}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/modules/mob/living/basic/bots/honkbots/honkbot.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/bots/honkbots/honkbot.bt.compiled.json new file mode 100644 index 00000000000..bc66e93cd88 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/bots/honkbots/honkbot.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity/pacifist"},{"type":"/datum/bt_node/subtree/bot_respond_to_summon"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":true,"movement_type":"/datum/ai_movement/basic_avoidance"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/honkbot_slip"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/play_with_clown","target_key":"clown_friend"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"clown_friend","required_dist":1,"finish_on_arrival":true,"movement_type":"/datum/ai_movement/basic_avoidance"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"clown_friend"},{"type":"/datum/bt_node/subtree/bot_patrol"}]},{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"slippery_target","target_source":"/datum/target_source/honkbot_slippery","targeting_strategy":"/datum/targeting_strategy/can_see","ignore_list_key":"temporary_ignore_list","vision_range":5,"time_between_perform":50},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"slip_target","target_source":"/datum/target_source/oview_single_type/human_mob","targeting_strategy":"/datum/targeting_strategy/can_see/slip_victim","ignore_list_key":"temporary_ignore_list","must_be_reachable":true,"vision_range":5}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"clown_friend","target_source":"/datum/target_source/oview_single_type/living_mob","targeting_strategy":"/datum/targeting_strategy/clown_friend","ignore_list_key":"temporary_ignore_list","must_be_reachable":true,"vision_range":5,"time_between_perform":50}]}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]},{"type":"/datum/bt_node/subtree/bot_salute_authority"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/modules/mob/living/basic/bots/honkbots/honkbot_slip.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/bots/honkbots/honkbot_slip.bt.compiled.json new file mode 100644 index 00000000000..dd807a874d2 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/bots/honkbots/honkbot_slip.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/can_see_target","children":[{"type":"/datum/bt_node/decorator/can_see_target","children":[{"type":"/datum/bt_node/decorator/pawn_has_gravity","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"slip_target","required_dist":1,"finish_on_arrival":true,"movement_type":"/datum/ai_movement/basic_avoidance"},{"type":"/datum/bt_node/ai_behavior/grab_target","target_key":"slip_target"},{"type":"/datum/bt_node/decorator/is_grabbing_target","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"slippery_target","required_dist":0,"finish_on_arrival":true,"movement_type":"/datum/ai_movement/basic_avoidance"}],"observer_abort":1,"key":"slip_target"},{"type":"/datum/bt_node/ai_behavior/release_and_slip","victim_key":"slip_target"},{"type":"/datum/bt_node/ai_behavior/perform_emote","emote":"flip"}]}]}],"observer_abort":3,"key":"slip_target","range":5}],"observer_abort":3,"key":"slippery_target","range":5} diff --git a/build/behavior_trees/modules/mob/living/basic/bots/hygienebot/hygienebot.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/bots/hygienebot/hygienebot.bt.compiled.json new file mode 100644 index 00000000000..c88e409ba1e --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/bots/hygienebot/hygienebot.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity/pacifist"},{"type":"/datum/bt_node/subtree/bot_respond_to_summon"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/ai_behavior/wash_target","target_key":"wash_target"},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"wash_target","required_dist":0,"finish_on_arrival":false,"movement_type":"/datum/ai_movement/basic_avoidance"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true},{"type":"/datum/bt_node/decorator/cooldown","children":[{"type":"/datum/bt_node/ai_behavior/commence_trashtalk","target_key":"wash_target"}],"observer_abort":0,"cooldown_key":"trash_talk_cooldown","cooldown_duration":40}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}],"observer_abort":3,"key":"wash_target"},{"type":"/datum/bt_node/subtree/bot_patrol"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target/hygiene_wash","target_key":"wash_target","target_source":"/datum/target_source/oview_single_type/human_mob","targeting_strategy":"/datum/targeting_strategy/conscious_human/washable_human","ignore_list_key":"temporary_ignore_list","vision_range":5,"time_between_perform":50},{"type":"/datum/bt_node/subtree/bot_salute_authority"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/bots/medbot/medbot.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/bots/medbot/medbot.bt.compiled.json new file mode 100644 index 00000000000..4542085b09d --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/bots/medbot/medbot.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity/pacifist"},{"type":"/datum/bt_node/subtree/bot_respond_to_summon"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bot_medical_flag","children":[{"type":"/datum/bt_node/subtree/medbot_find_and_announce_crit"}],"observer_abort":3,"flag":1},{"type":"/datum/bt_node/decorator/bot_medical_flag","children":[{"type":"/datum/bt_node/subtree/medbot_treat_patient"}],"observer_abort":3,"invert":true,"flag":8},{"type":"/datum/bt_node/subtree/bot_patrol"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"patient_in_crit","target_source":"/datum/target_source/oview_single_type/human_mob","targeting_strategy":"/datum/targeting_strategy/crit_patient","vision_range":7},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target/medbot_patient","target_key":"Current Target","target_source":"/datum/target_source/oview_single_type/human_mob/medbot_patient","targeting_strategy":"/datum/targeting_strategy/treatable_patient","ignore_list_key":"temporary_ignore_list","must_be_reachable":true,"reach_distance":20,"vision_range":7}],"failure_policy":0,"success_policy":0,"repeat_secondary":false,"repeat_secondary_delay":10,"finish_on_primary":false},{"type":"/datum/bt_node/subtree/bot_salute_authority"},{"type":"/datum/bt_node/decorator/bot_medical_flag","children":[{"type":"/datum/bt_node/ai_behavior/handle_medbot_speech","announce_key":"announce_ability"}],"flag":4}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":false}]} diff --git a/build/behavior_trees/modules/mob/living/basic/bots/medbot/medbot_find_and_announce_crit.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/bots/medbot/medbot_find_and_announce_crit.bt.compiled.json new file mode 100644 index 00000000000..80720b96b5a --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/bots/medbot/medbot_find_and_announce_crit.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/announce_patient","target_key":"patient_in_crit"}],"observer_abort":3,"key":"patient_in_crit"} diff --git a/build/behavior_trees/modules/mob/living/basic/bots/medbot/medbot_treat_patient.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/bots/medbot/medbot_treat_patient.bt.compiled.json new file mode 100644 index 00000000000..47d46973cc8 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/bots/medbot/medbot_treat_patient.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/tend_to_patient","target_key":"Current Target"}]}],"observer_abort":3,"key":"Current Target"} diff --git a/build/behavior_trees/modules/mob/living/basic/bots/mulebot/mulebot.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/bots/mulebot/mulebot.bt.compiled.json new file mode 100644 index 00000000000..73f497e6cb3 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/bots/mulebot/mulebot.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/bot_respond_to_summon"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"mulebot_travel_target","required_dist":0,"finish_on_arrival":true},{"type":"/datum/bt_node/ai_behavior/handle_delivery","target_key":"mulebot_travel_target"}]}],"key":"mulebot_travel_target"},{"type":"/datum/bt_node/decorator/bot_wire_cut","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bot_mode","children":[{"type":"/datum/bt_node/ai_behavior/find_delivery_beacon","target_key":"mulebot_travel_target","tag_key":"mulebot_destination"}],"mode":"Delivering"},{"type":"/datum/bt_node/decorator/bot_mode","children":[{"type":"/datum/bt_node/ai_behavior/find_delivery_beacon","target_key":"mulebot_travel_target","tag_key":"mulebot_home_beacon"}],"mode":"Returning"}]}],"key":"mulebot_travel_target","invert":true}],"wire":"Beacon","invert":true}]},{"type":"/datum/bt_node/subtree/bot_salute_authority"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/modules/mob/living/basic/bots/repairbot/repairbot.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/bots/repairbot/repairbot.bt.compiled.json new file mode 100644 index 00000000000..622583c84e8 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/bots/repairbot/repairbot.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity/pacifist"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/subtree/repairbot_repair_target"},{"type":"/datum/bt_node/subtree/repairbot_find_target"},{"type":"/datum/bt_node/subtree/bot_salute_authority"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/bots/repairbot/repairbot_emagged.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/bots/repairbot/repairbot_emagged.bt.compiled.json new file mode 100644 index 00000000000..4128c88eac7 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/bots/repairbot/repairbot_emagged.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"robot target","required_dist":0,"finish_on_arrival":true,"movement_type":"/datum/ai_movement/basic_avoidance"},{"type":"/datum/bt_node/ai_behavior/bot_interact/tip_robot","target_key":"robot target"},{"type":"/datum/bt_node/ai_behavior/grab_target","target_key":"robot target"}]}],"key":"robot target"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"deconstruct_target","required_dist":0,"finish_on_arrival":true,"movement_type":"/datum/ai_movement/basic_avoidance"},{"type":"/datum/bt_node/ai_behavior/bot_interact","target_key":"deconstruct_target"}]}],"key":"deconstruct_target"}]} diff --git a/build/behavior_trees/modules/mob/living/basic/bots/repairbot/repairbot_find_target.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/bots/repairbot/repairbot_find_target.bt.compiled.json new file mode 100644 index 00000000000..f6922d5734e --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/bots/repairbot/repairbot_find_target.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bot_is_emagged","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/bot_search/valid_robot","target_key":"robot target","minimum_distance":1},{"type":"/datum/bt_node/ai_behavior/bot_search/deconstructable","target_key":"deconstruct_target","minimum_distance":1}]}],"invert":false},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/bot_search/valid_plateless_turf/breached","target_key":"Current Target","minimum_distance":1},{"type":"/datum/bt_node/ai_behavior/bot_search/refillable_target","target_key":"Current Target","minimum_distance":1},{"type":"/datum/bt_node/ai_behavior/bot_search/valid_grille_target","target_key":"Current Target","minimum_distance":1},{"type":"/datum/bt_node/ai_behavior/bot_search/valid_plateless_turf","target_key":"Current Target","minimum_distance":1},{"type":"/datum/bt_node/ai_behavior/bot_search/valid_window_fix","target_key":"Current Target","minimum_distance":1},{"type":"/datum/bt_node/ai_behavior/bot_search/valid_girder","target_key":"Current Target","minimum_distance":1}]},{"type":"/datum/bt_node/ai_behavior/set_bb_key","target_key":"interaction_type","value":1},{"type":"/datum/bt_node/ai_behavior/cancel_current_plan"}]},{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/bot_search/valid_wall_target","target_key":"Current Target","minimum_distance":1},{"type":"/datum/bt_node/ai_behavior/set_bb_key","target_key":"interaction_type","value":2},{"type":"/datum/bt_node/ai_behavior/cancel_current_plan"}]}]}],"observer_abort":0,"invert":true,"key":"Current Target"}]} diff --git a/build/behavior_trees/modules/mob/living/basic/bots/repairbot/repairbot_repair_target.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/bots/repairbot/repairbot_repair_target.bt.compiled.json new file mode 100644 index 00000000000..5ff44f5bafd --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/bots/repairbot/repairbot_repair_target.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bot_is_emagged","children":[{"type":"/datum/bt_node/subtree/repairbot_emagged"}],"observer_abort":3},{"type":"/datum/bt_node/decorator/bot_is_emagged","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":true,"movement_type":"/datum/ai_movement/jps"},{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_equals","children":[{"type":"/datum/bt_node/ai_behavior/bot_interact","target_key":"Current Target"}],"key":"interaction_type","value":1},{"type":"/datum/bt_node/decorator/bb_key_equals","children":[{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability/build_girder","ability_key":"girder_build_ability","target_key":"Current Target"}],"key":"interaction_type","value":2}]}]}],"observer_abort":3,"key":"Current Target"}],"observer_abort":1,"invert":true},{"type":"/datum/bt_node/subtree/bot_patrol"}]} diff --git a/build/behavior_trees/modules/mob/living/basic/bots/secbot/secbot.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/bots/secbot/secbot.bt.compiled.json new file mode 100644 index 00000000000..1437099e876 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/bots/secbot/secbot.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity/pacifist"},{"type":"/datum/bt_node/subtree/bot_respond_to_summon"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":true,"movement_type":"/datum/ai_movement/basic_avoidance"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/bot_patrol"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/bots/vibebot/vibebot.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/bots/vibebot/vibebot.bt.compiled.json new file mode 100644 index 00000000000..bc0a69a489d --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/bots/vibebot/vibebot.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity/pacifist"},{"type":"/datum/bt_node/subtree/bot_respond_to_summon"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"party_target","required_dist":1,"finish_on_arrival":true},{"type":"/datum/bt_node/ai_behavior/vibebot_party","ability_key":"party_ability","target_key":"party_target"}]}],"key":"party_target"},{"type":"/datum/bt_node/subtree/bot_patrol"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"party_target","target_source":"/datum/target_source/oview_single_type/human_mob","targeting_strategy":"/datum/targeting_strategy/conscious_human/party_friend","ignore_list_key":"temporary_ignore_list","vision_range":5,"time_between_perform":50}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/clown/clown.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/clown/clown.bt.compiled.json new file mode 100644 index 00000000000..1722ad983fc --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/clown/clown.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/subtree/simple_retaliate_combat"},{"type":"/datum/bt_node/subtree/random_speech_loop"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/cult/constructs/artificer.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/cult/constructs/artificer.bt.compiled.json new file mode 100644 index 00000000000..eceda0c7f56 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/cult/constructs/artificer.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/run_away_from_target"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets/most_wounded","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list","target_key":"BB_basic_flee_target","targeting_strategy":"flee_targeting_strategy","hiding_location_key":"BB_basic_flee_target_hiding_location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/modules/mob/living/basic/cult/constructs/juggernaut.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/cult/constructs/juggernaut.bt.compiled.json new file mode 100644 index 00000000000..c4e166a3dbd --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/cult/constructs/juggernaut.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/subtree/simple_hostile_obstacles_combat"} diff --git a/build/behavior_trees/modules/mob/living/basic/cult/constructs/proteon.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/cult/constructs/proteon.bt.compiled.json new file mode 100644 index 00000000000..5895d2498f9 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/cult/constructs/proteon.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/subtree/skittish_brawler_combat"}]} diff --git a/build/behavior_trees/modules/mob/living/basic/cult/constructs/wraith.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/cult/constructs/wraith.bt.compiled.json new file mode 100644 index 00000000000..1f1bc0d98f4 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/cult/constructs/wraith.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/ai_behavior/attack_obstructions","target_key":"Current Target"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets/most_wounded","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/cytology/vatbeast.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/cytology/vatbeast.bt.compiled.json new file mode 100644 index 00000000000..98d46260ed3 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/cytology/vatbeast.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/move_to_and_eat"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/attack_obstructions","target_key":"Current Target"},{"type":"/datum/bt_node/decorator/bb_key_at_least","children":[{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability/melee","ability_key":"BB_generic_action","target_key":"Current Target"}],"observer_abort":2,"key":"BB_basic_mob_has_target_time","minimum":100},{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_TARGET_FOOD","targeting_strategy":"/datum/targeting_strategy/pickup_item","target_source":"/datum/target_source/oview_typed/from_bb_key/basic_foods"},{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location","check_faction":true},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}]}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/farm_animals/bee/bee.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/farm_animals/bee/bee.bt.compiled.json new file mode 100644 index 00000000000..ce0187ca3c2 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/farm_animals/bee/bee.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree","override_id":"pet_command"},{"type":"/datum/bt_node/subtree/find_hive"},{"type":"/datum/bt_node/subtree/transition_hive_status"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/pollinate_target"},{"type":"/datum/bt_node/subtree/simple_hostile_combat"}]},{"type":"/datum/bt_node/ai_behavior/enter_exit_hive"},{"type":"/datum/bt_node/decorator/random_chance","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_target_hydro","target_source":"/datum/target_source/oview_single_type/hydroponics","targeting_strategy":"/datum/targeting_strategy/pollinatable_hydro","vision_range":10,"time_between_perform":100}],"chance":0.85}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/find_hive"}],"invert":true,"key":"BB_target_home"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/modules/mob/living/basic/farm_animals/bee/find_hive.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/farm_animals/bee/find_hive.bt.compiled.json new file mode 100644 index 00000000000..383021ff26e --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/farm_animals/bee/find_hive.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_current_home","required_dist":1,"finish_on_arrival":false},{"type":"/datum/bt_node/ai_behavior/inhabit_hive"},{"type":"/datum/bt_node/ai_behavior/clear_key","key":"BB_target_home"}]}],"key":"BB_current_home","invert":true}],"observer_abort":2,"invert":false,"key":"BB_target_home"} diff --git a/build/behavior_trees/modules/mob/living/basic/farm_animals/bee/pollinate_target.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/farm_animals/bee/pollinate_target.bt.compiled.json new file mode 100644 index 00000000000..80f66c96bed --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/farm_animals/bee/pollinate_target.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_target_hydro","required_dist":1,"finish_on_arrival":true},{"type":"/datum/bt_node/ai_behavior/pollinate_hydro"}]}],"observer_abort":2,"key":"BB_target_hydro"} diff --git a/build/behavior_trees/modules/mob/living/basic/farm_animals/bee/queen_bee.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/farm_animals/bee/queen_bee.bt.compiled.json new file mode 100644 index 00000000000..5e4ce721119 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/farm_animals/bee/queen_bee.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/find_hive"},{"type":"/datum/bt_node/subtree/pollinate_target"},{"type":"/datum/bt_node/subtree/transition_hive_status"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/subtree/simple_hostile_combat"},{"type":"/datum/bt_node/ai_behavior/enter_exit_hive/queen"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/find_hive"}],"invert":true,"key":"BB_target_home"}],"failure_policy":0,"success_policy":0,"repeat_secondary":false,"repeat_secondary_delay":10,"finish_on_primary":false} diff --git a/build/behavior_trees/modules/mob/living/basic/farm_animals/bee/transition_hive_status.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/farm_animals/bee/transition_hive_status.bt.compiled.json new file mode 100644 index 00000000000..4edb3e6f376 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/farm_animals/bee/transition_hive_status.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/bb_key_equals","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_current_home","required_dist":1},{"type":"/datum/bt_node/ai_behavior/ai_interact","target_key":"BB_current_home"},{"type":"/datum/bt_node/ai_behavior/clear_key","key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/set_bb_key","target_key":"BB_wants_to_transition_hive","value":false}]}],"observer_abort":2,"key":"BB_wants_to_transition_hive","value":true} diff --git a/build/behavior_trees/modules/mob/living/basic/farm_animals/chicken/chick.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/farm_animals/chicken/chick.bt.compiled.json new file mode 100644 index 00000000000..2fec6c77ba9 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/farm_animals/chicken/chick.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/is_at_distance","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_found_mom","required_dist":1,"finish_on_arrival":true}],"target_key":"BB_found_mom","min_distance":2},{"type":"/datum/bt_node/decorator/random_chance","children":[{"type":"/datum/bt_node/ai_behavior/look_to_parent","parent_key":"BB_found_mom"}],"chance":0.15}]}],"key":"BB_found_mom","observer_abort":1},{"type":"/datum/bt_node/subtree/random_walk"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/find_mom"}],"key":"BB_found_mom","invert":true},{"type":"/datum/bt_node/subtree/random_speech_loop"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/modules/mob/living/basic/farm_animals/chicken/chicken.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/farm_animals/chicken/chicken.bt.compiled.json new file mode 100644 index 00000000000..5f77adc0a39 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/farm_animals/chicken/chicken.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/subtree/skittish_and_speak"} diff --git a/build/behavior_trees/modules/mob/living/basic/farm_animals/cow/cow.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/farm_animals/cow/cow.bt.compiled.json new file mode 100644 index 00000000000..c7f3628371b --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/farm_animals/cow/cow.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/tip_reaction"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/move_to_and_eat"},{"type":"/datum/bt_node/subtree/random_walk"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/subtree/find_food"},{"type":"/datum/bt_node/subtree/random_speech_loop"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/farm_animals/cow/cow_moonicorn.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/farm_animals/cow/cow_moonicorn.bt.compiled.json new file mode 100644 index 00000000000..772646294be --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/farm_animals/cow/cow_moonicorn.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/subtree/tip_reaction"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/move_to_and_eat"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/subtree/find_food"},{"type":"/datum/bt_node/subtree/random_speech_loop"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/farm_animals/cow/cow_wisdom.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/farm_animals/cow/cow_wisdom.bt.compiled.json new file mode 100644 index 00000000000..26ae961a8a1 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/farm_animals/cow/cow_wisdom.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/tip_reaction"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/subtree/random_walk"},{"type":"/datum/bt_node/subtree/random_speech_loop"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/farm_animals/deer/deer.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/farm_animals/deer/deer.bt.compiled.json new file mode 100644 index 00000000000..6a062dfbb42 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/farm_animals/deer/deer.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/subtree/run_away_from_target"}],"key":"Current Target","observer_abort":3},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/stop_and_stare"}],"key":"BB_thing_that_made_us_stationary","observer_abort":2},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/key_off_cooldown","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"deer_home","required_dist":1},{"type":"/datum/bt_node/ai_behavior/deer_rest"}]}],"observer_abort":1,"key":"deer_home"}],"cooldown_key":"deer_next_rest_timer"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"deer_playfriend","required_dist":1},{"type":"/datum/bt_node/ai_behavior/set_bb_cooldown","cooldown_key":"deer_play_cooldown","cooldown_duration":100},{"type":"/datum/bt_node/ai_behavior/deer_play"}]}],"observer_abort":3,"key":"deer_playfriend"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"deer_tree_target","required_dist":1},{"type":"/datum/bt_node/ai_behavior/hunt_target/deer_mark","target_key":"deer_tree_target","cooldown_key":"deer_mark_cooldown"}]}],"observer_abort":2,"key":"deer_tree_target"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"deer_grass_target","required_dist":0},{"type":"/datum/bt_node/ai_behavior/hunt_target/deer_graze","target_key":"deer_grass_target","cooldown_key":"deer_graze_cooldown"}]}],"observer_abort":3,"key":"deer_grass_target"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"deer_water_target","required_dist":1},{"type":"/datum/bt_node/ai_behavior/hunt_target/deer_drink","target_key":"deer_water_target","cooldown_key":"deer_drink_cooldown"}]}],"observer_abort":3,"key":"deer_water_target"},{"type":"/datum/bt_node/subtree/random_walk"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/random_speech_blackboard"},{"type":"/datum/bt_node/decorator/key_off_cooldown","children":[{"type":"/datum/bt_node/decorator/random_chance","children":[{"type":"/datum/bt_node/ai_behavior/set_bb_key","target_key":"deer_wants_to_play","value":true}],"chance":0.03}],"cooldown_key":"deer_play_cooldown"},{"type":"/datum/bt_node/decorator/bb_key_true","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"deer_playfriend","target_source":"/datum/target_source/oview_single_type/deer_animals","targeting_strategy":"/datum/targeting_strategy/playable_deer"},{"type":"/datum/bt_node/ai_behavior/set_bb_key","target_key":"deer_wants_to_play","value":false}]}],"observer_abort":3,"key":"deer_wants_to_play"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/decorator/key_off_cooldown","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"deer_tree_target","target_source":"/datum/target_source/oview_single_type/flora_tree","targeting_strategy":"/datum/targeting_strategy/anything"}],"cooldown_key":"deer_mark_cooldown"}],"invert":true,"key":"deer_tree_target"}],"invert":true,"key":"deer_home"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/decorator/key_off_cooldown","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"deer_grass_target","target_source":"/datum/target_source/range_turfs/typecache_visible/deer_grass","targeting_strategy":"/datum/targeting_strategy/anything"}],"cooldown_key":"deer_graze_cooldown"}],"invert":true,"key":"deer_grass_target"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/decorator/key_off_cooldown","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"deer_water_target","target_source":"/datum/target_source/range_turfs/typecache_visible/deer_water","targeting_strategy":"/datum/targeting_strategy/anything"}],"cooldown_key":"deer_drink_cooldown"}],"invert":true,"key":"deer_water_target"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true},{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list/nearest","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_thing_that_made_us_stationary","target_source":"/datum/target_source/oview_typed/from_bb_key/stationary_targets","targeting_strategy":"/datum/targeting_strategy/anything"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/farm_animals/goat/goat.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/farm_animals/goat/goat.bt.compiled.json new file mode 100644 index 00000000000..03e7b196a70 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/farm_animals/goat/goat.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/subtree/forage_and_retaliate"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/subtree/random_speech_loop"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/modules/mob/living/basic/farm_animals/goose/forage_for_goose_food.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/farm_animals/goose/forage_for_goose_food.bt.compiled.json new file mode 100644 index 00000000000..cf4d3d55a62 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/farm_animals/goose/forage_for_goose_food.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_TARGET_FOOD","targeting_strategy":"/datum/targeting_strategy/goose_edible","target_source":"/datum/target_source/oview_items","vision_range":1} diff --git a/build/behavior_trees/modules/mob/living/basic/farm_animals/goose/goose.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/farm_animals/goose/goose.bt.compiled.json new file mode 100644 index 00000000000..4aa63aee1e5 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/farm_animals/goose/goose.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/subtree/forage_and_retaliate","bindings":{"q8w3rtv1":"/datum/bt_node/subtree/forage_for_goose_food"}}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/subtree/random_speech_loop"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/farm_animals/goose/goose_calm.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/farm_animals/goose/goose_calm.bt.compiled.json new file mode 100644 index 00000000000..3fa72d01055 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/farm_animals/goose/goose_calm.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/subtree/forage_and_retaliate","bindings":{"q8w3rtv1":"/datum/bt_node/subtree/forage_for_goose_food","z4n9bk7p":"/datum/bt_node/subtree/pick_retaliate_target"}}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/subtree/random_speech_loop"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/modules/mob/living/basic/farm_animals/gorilla/gorilla.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/farm_animals/gorilla/gorilla.bt.compiled.json new file mode 100644 index 00000000000..d43acdd1a6d --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/farm_animals/gorilla/gorilla.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/attack_obstructions","target_key":"Current Target","can_attack_turfs":true},{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/move_to_and_eat"},{"type":"/datum/bt_node/subtree/random_walk"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/subtree/find_food"},{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/decorator/random_chance_from_key","children":[{"type":"/datum/bt_node/ai_behavior/run_emote","emote_key":"BB_emotes"}],"chance_key":"BB_EMOTE_CHANCE"}],"success_policy":1,"failure_policy":1,"loop_delay":10}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/farm_animals/pig.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/farm_animals/pig.bt.compiled.json new file mode 100644 index 00000000000..2c15b53fa01 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/farm_animals/pig.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/subtree/skittish_brawler_combat"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/subtree/random_speech_loop"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/modules/mob/living/basic/farm_animals/pony.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/farm_animals/pony.bt.compiled.json new file mode 100644 index 00000000000..cff1f73085d --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/farm_animals/pony.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/subtree/simple_skittish_combat"}],"key":"BB_tamed","observer_abort":2},{"type":"/datum/bt_node/subtree/skittish_brawler_combat"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/subtree/random_speech_loop"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/modules/mob/living/basic/farm_animals/rabbit.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/farm_animals/rabbit.bt.compiled.json new file mode 100644 index 00000000000..5f77adc0a39 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/farm_animals/rabbit.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/subtree/skittish_and_speak"} diff --git a/build/behavior_trees/modules/mob/living/basic/farm_animals/sheep.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/farm_animals/sheep.bt.compiled.json new file mode 100644 index 00000000000..5f77adc0a39 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/farm_animals/sheep.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/subtree/skittish_and_speak"} diff --git a/build/behavior_trees/modules/mob/living/basic/festivus_pole.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/festivus_pole.bt.compiled.json new file mode 100644 index 00000000000..befc9be9d22 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/festivus_pole.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_low_priority_hunting_target","required_dist":1},{"type":"/datum/bt_node/ai_behavior/hunt_target/use_ability_on_target","target_key":"BB_low_priority_hunting_target","ability_key":"BB_festive_apc"}]}],"observer_abort":3,"key":"BB_low_priority_hunting_target"},{"type":"/datum/bt_node/subtree/random_walk","bindings":{"bf07i8ep":10}}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_low_priority_hunting_target","target_source":"/datum/target_source/oview_single_type/apc","targeting_strategy":"/datum/targeting_strategy/chargeable_apc","vision_range":6}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/heretic/raw_prophet.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/heretic/raw_prophet.bt.compiled.json new file mode 100644 index 00000000000..abae9529636 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/heretic/raw_prophet.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/subtree/simple_ability_ranged_combat"}]} diff --git a/build/behavior_trees/modules/mob/living/basic/heretic/rust_walker.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/heretic/rust_walker.bt.compiled.json new file mode 100644 index 00000000000..8635de40819 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/heretic/rust_walker.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/ability_available","children":[{"type":"/datum/bt_node/decorator/pawn_turf_has_trait","children":[{"type":"/datum/bt_node/ai_behavior/use_mob_ability"}],"observer_abort":0,"trait":"rust_trait"}],"observer_abort":2},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/attack_obstructions","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability","ability_key":"BB_TARGETED_action","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/idle_random_walk/rust"}],"success_policy":1,"failure_policy":0}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/heretic/stalker.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/heretic/stalker.bt.compiled.json new file mode 100644 index 00000000000..a1bf2a7fd06 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/heretic/stalker.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/pawn_inside_mob","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/use_mob_ability/shapeshift"}],"observer_abort":2,"invert":true,"key":"Current Target"}],"invert":true},{"type":"/datum/bt_node/decorator/pawn_inside_mob","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_at_least","children":[{"type":"/datum/bt_node/ai_behavior/use_mob_ability/shapeshift"}],"observer_abort":2,"key":"BB_basic_mob_has_target_time","minimum":80},{"type":"/datum/bt_node/ai_behavior/wait"}]}],"observer_abort":3,"invert":false,"key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/wait"}]}],"observer_abort":1},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/heretic/star_gazer.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/heretic/star_gazer.bt.compiled.json new file mode 100644 index 00000000000..e6c3d835944 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/heretic/star_gazer.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/subtree","override_id":"pet_command"},{"type":"/datum/bt_node/subtree/simple_hostile_obstacles_combat"}]} diff --git a/build/behavior_trees/modules/mob/living/basic/icemoon/ice_demon/ice_demon.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/icemoon/ice_demon/ice_demon.bt.compiled.json new file mode 100644 index 00000000000..224616f0eb5 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/icemoon/ice_demon/ice_demon.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/subtree/ice_demon_flee_from_fire"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/subtree/ice_demon_combat"}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/icemoon/ice_demon/ice_demon_afterimage.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/icemoon/ice_demon/ice_demon_afterimage.bt.compiled.json new file mode 100644 index 00000000000..d622cdcd2c8 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/icemoon/ice_demon/ice_demon_afterimage.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/subtree/ice_demon_flee_from_fire"},{"type":"/datum/bt_node/subtree/simple_hostile_combat"}]} diff --git a/build/behavior_trees/modules/mob/living/basic/icemoon/ice_demon/ice_demon_combat.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/icemoon/ice_demon/ice_demon_combat.bt.compiled.json new file mode 100644 index 00000000000..83b18fc48ef --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/icemoon/ice_demon/ice_demon_combat.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/is_at_distance","children":[{"type":"/datum/bt_node/decorator/ability_available","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/find_furthest_turf_from_target","target_key":"Current Target","set_key":"escape_destination","range":7},{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability/and_clear_target","ability_key":"demon_teleport_ability","target_key":"escape_destination"}]}],"ability_key":"demon_teleport_ability"}],"target_key":"Current Target","maximum_distance":1},{"type":"/datum/bt_node/decorator/pawn_health_below","children":[{"type":"/datum/bt_node/decorator/ability_available","children":[{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability","ability_key":"demon_clone_ability","target_key":"Current Target"}],"ability_key":"demon_clone_ability"}],"health_threshold":75},{"type":"/datum/bt_node/decorator/ability_available","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/find_valid_teleport_location","target_key":"Current Target","set_key":"teleport_destination","range":3},{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability/and_clear_target","ability_key":"demon_teleport_ability","target_key":"teleport_destination"}]}],"ability_key":"demon_teleport_ability"},{"type":"/datum/bt_node/ai_behavior/basic_ranged_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location","max_range":9}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/maintain_distance/cover_minimum_distance","target_key":"Current Target"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true} diff --git a/build/behavior_trees/modules/mob/living/basic/icemoon/ice_demon/ice_demon_flee_from_fire.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/icemoon/ice_demon/ice_demon_flee_from_fire.bt.compiled.json new file mode 100644 index 00000000000..d19a08d6d05 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/icemoon/ice_demon/ice_demon_flee_from_fire.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/target_holding_lit_item","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/subtree/run_away_from_target"},{"type":"/datum/bt_node/decorator/ability_available","children":[{"type":"/datum/bt_node/ai_behavior/use_mob_ability","ability_key":"demon_slip_ability"}],"ability_key":"demon_slip_ability"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target","item_types_key":"list_scary_items"} diff --git a/build/behavior_trees/modules/mob/living/basic/icemoon/ice_whelp/ice_whelp.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/icemoon/ice_whelp/ice_whelp.bt.compiled.json new file mode 100644 index 00000000000..384b14d072c --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/icemoon/ice_whelp/ice_whelp.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/attack_obstructions","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability","ability_key":"BB_whelp_widespread_fire","target_key":"Current Target","maximum_distance":3},{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability","ability_key":"BB_whelp_straightline_fire","target_key":"Current Target","maximum_distance":7},{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_target_cannibal","required_dist":1},{"type":"/datum/bt_node/ai_behavior/ai_interact","target_key":"BB_target_cannibal"},{"type":"/datum/bt_node/ai_behavior/clear_key","key":"BB_target_cannibal"}]}],"observer_abort":2,"key":"BB_target_cannibal"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_target_rock","required_dist":0},{"type":"/datum/bt_node/ai_behavior/ai_interact","target_key":"BB_target_rock","combat_mode":false},{"type":"/datum/bt_node/ai_behavior/set_bb_cooldown","cooldown_key":"BB_whelp_sculpt_cooldown","cooldown_duration":3000},{"type":"/datum/bt_node/ai_behavior/clear_key","key":"BB_target_rock"}]}],"observer_abort":2,"key":"BB_target_rock"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_target_tree","required_dist":2},{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability/and_clear_target","ability_key":"BB_whelp_straightline_fire","target_key":"BB_target_tree","maximum_distance":2},{"type":"/datum/bt_node/ai_behavior/set_bb_cooldown","cooldown_key":"BB_whelp_burn_cooldown","cooldown_duration":1200}]}],"observer_abort":2,"key":"BB_target_tree"},{"type":"/datum/bt_node/subtree/random_walk"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_target_cannibal","target_source":"/datum/target_source/oview_single_type/ice_whelp","targeting_strategy":"/datum/targeting_strategy/dead_mob/not_pulled","vision_range":10},{"type":"/datum/bt_node/decorator/key_off_cooldown","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_target_rock","target_source":"/datum/target_source/oview_single_type/icy_rock","targeting_strategy":"/datum/targeting_strategy/anything"}],"cooldown_key":"BB_whelp_sculpt_cooldown"},{"type":"/datum/bt_node/decorator/key_off_cooldown","children":[{"type":"/datum/bt_node/decorator/ability_available","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_target_tree","target_source":"/datum/target_source/oview_single_type/flora_tree","targeting_strategy":"/datum/targeting_strategy/non_stump_tree","vision_range":9}],"ability_key":"BB_whelp_straightline_fire"}],"cooldown_key":"d"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/icemoon/polar_bear/polar.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/icemoon/polar_bear/polar.bt.compiled.json new file mode 100644 index 00000000000..d2df8a3d4e1 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/icemoon/polar_bear/polar.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/subtree/simple_hostile_combat","bindings":{"bbrbyj7y":"/datum/bt_node/ai_behavior/random_speech/bear"}},{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/ai_behavior/enrage"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/icemoon/wolf/wolf.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/icemoon/wolf/wolf.bt.compiled.json new file mode 100644 index 00000000000..41477912f9c --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/icemoon/wolf/wolf.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/subtree","override_id":"pet_command"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/run_away_from_target"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/attack_obstructions","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/decorator/cooldown","children":[{"type":"/datum/bt_node/ai_behavior/call_reinforcements"}],"observer_abort":2,"cooldown_key":"BB_basic_mob_reinforcements_cooldown","cooldown_duration":300,"lock_on_succeed":true},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}]}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/illusion/escape.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/illusion/escape.bt.compiled.json new file mode 100644 index 00000000000..86f5a41b8cd --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/illusion/escape.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/subtree/run_away_from_target"},{"type":"/datum/bt_node/subtree/pick_retaliate_target"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/illusion/retaliate.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/illusion/retaliate.bt.compiled.json new file mode 100644 index 00000000000..86f5a41b8cd --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/illusion/retaliate.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/subtree/run_away_from_target"},{"type":"/datum/bt_node/subtree/pick_retaliate_target"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/jungle/human_trap.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/jungle/human_trap.bt.compiled.json new file mode 100644 index 00000000000..56a99511c05 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/jungle/human_trap.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/subtree/simple_ability_ranged_combat"} diff --git a/build/behavior_trees/modules/mob/living/basic/jungle/leaper/leaper.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/jungle/leaper/leaper.bt.compiled.json new file mode 100644 index 00000000000..8c71682d87c --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/jungle/leaper/leaper.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/subtree","override_id":"pet_command"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability","ability_key":"leaper_bubble","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability","ability_key":"leaper_flop","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability","ability_key":"leaper_volley","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability","ability_key":"leaper_summon","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/jungle/mega_arachnid/mega_arachnid.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/jungle/mega_arachnid/mega_arachnid.bt.compiled.json new file mode 100644 index 00000000000..da6c3f0f363 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/jungle/mega_arachnid/mega_arachnid.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/subtree/mega_arachnid_combat"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_surveillance_target","required_dist":1,"finish_on_arrival":true},{"type":"/datum/bt_node/ai_behavior/ai_interact","target_key":"BB_surveillance_target"},{"type":"/datum/bt_node/ai_behavior/clear_key","key":"BB_surveillance_target"}]}],"key":"BB_surveillance_target","observer_abort":1},{"type":"/datum/bt_node/subtree/climb_tree"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_surveillance_target","target_source":"/datum/target_source/oview_typed/surveillance_equipment","targeting_strategy":"/datum/targeting_strategy/working_machine","vision_range":7},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_climbed_tree","target_source":"/datum/target_source/oview_single_type/flora_tree","targeting_strategy":"/datum/targeting_strategy/anything"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/modules/mob/living/basic/jungle/mega_arachnid/mega_arachnid_combat.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/jungle/mega_arachnid/mega_arachnid_combat.bt.compiled.json new file mode 100644 index 00000000000..3e7b12c6752 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/jungle/mega_arachnid/mega_arachnid_combat.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/subtree/run_away_from_target"},{"type":"/datum/bt_node/decorator/ability_available","children":[{"type":"/datum/bt_node/ai_behavior/use_mob_ability","ability_key":"BB_arachnid_slip"}],"ability_key":"BB_arachnid_slip"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/target_is_type","children":[{"type":"/datum/bt_node/decorator/target_legcuffed","children":[{"type":"/datum/bt_node/decorator/ability_available","children":[{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability","ability_key":"BB_arachnid_restrain","target_key":"Current Target"}],"ability_key":"BB_arachnid_restrain"}],"invert":true}],"target_type":"/mob/living/carbon/human"},{"type":"/datum/bt_node/decorator/target_is_type","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"target_type":"/mob/living/carbon/human","invert":true},{"type":"/datum/bt_node/decorator/target_legcuffed","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}]},{"type":"/datum/bt_node/decorator/target_health_below_fraction","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"key":"Current Target","fraction":0.5}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}]}],"key":"Current Target","observer_abort":3} diff --git a/build/behavior_trees/modules/mob/living/basic/jungle/seedling/seedling.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/jungle/seedling/seedling.bt.compiled.json new file mode 100644 index 00000000000..cb86ef52d36 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/jungle/seedling/seedling.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity/pacifist"},{"type":"/datum/bt_node/decorator/override_id_set","children":[{"type":"/datum/bt_node/subtree","override_id":"pet_command"}],"observer_abort":2,"override_id":"pet_command"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/target_has_reagent","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"hydroplant_target","required_dist":1},{"type":"/datum/bt_node/ai_behavior/ai_interact","target_key":"hydroplant_target"},{"type":"/datum/bt_node/ai_behavior/clear_key","key":"hydroplant_target"}]}],"key":"hydroplant_target","observer_abort":3}],"invert":false,"key":"watercan_target","reagent_type":"/datum/reagent/water"},{"type":"/datum/bt_node/decorator/ability_available","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"beamable_hydroplant_target","required_dist":2},{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability","ability_key":"solarbeam_ability","target_key":"beamable_hydroplant_target"},{"type":"/datum/bt_node/ai_behavior/clear_key","key":"beamable_hydroplant_target"}]}],"key":"beamable_hydroplant_target","observer_abort":3}],"ability_key":"solarbeam_ability","observer_abort":2},{"type":"/datum/bt_node/decorator/item_inside_pawn","children":[{"type":"/datum/bt_node/decorator/target_has_reagent","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_low_priority_hunting_target","required_dist":1},{"type":"/datum/bt_node/ai_behavior/ai_interact","target_key":"BB_low_priority_hunting_target"},{"type":"/datum/bt_node/ai_behavior/clear_key","key":"BB_low_priority_hunting_target"}]}],"observer_abort":3,"key":"BB_low_priority_hunting_target"}],"invert":true,"key":"watercan_target","reagent_type":"/datum/reagent/water"}],"key":"watercan_target"},{"type":"/datum/bt_node/decorator/item_inside_pawn","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"watercan_target","required_dist":1},{"type":"/datum/bt_node/ai_behavior/ai_interact","target_key":"watercan_target"}]}],"key":"watercan_target","observer_abort":3}],"invert":true,"key":"watercan_target"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/decorator/item_inside_pawn","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"watercan_target","targeting_strategy":"/datum/targeting_strategy/pickup_item","target_source":"/datum/target_source/oview_single_type/watering_can"}],"invert":true,"key":"watercan_target"},{"type":"/datum/bt_node/decorator/item_inside_pawn","children":[{"type":"/datum/bt_node/decorator/target_has_reagent","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_low_priority_hunting_target","target_source":"/datum/target_source/oview","targeting_strategy":"/datum/targeting_strategy/water_dispenser"}],"invert":true,"key":"watercan_target","reagent_type":"/datum/reagent/water"}],"key":"watercan_target"},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"hydroplant_target","target_source":"/datum/target_source/oview","targeting_strategy":"/datum/targeting_strategy/treatable_hydro","time_between_perform":50},{"type":"/datum/bt_node/decorator/ability_available","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"beamable_hydroplant_target","target_source":"/datum/target_source/oview","targeting_strategy":"/datum/targeting_strategy/beamable_hydro","time_between_perform":40}],"ability_key":"solarbeam_ability"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/jungle/seedling/seedling_meanie.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/jungle/seedling/seedling_meanie.bt.compiled.json new file mode 100644 index 00000000000..b69f6a865d4 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/jungle/seedling/seedling_meanie.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/subtree","override_id":"pet_command"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability","ability_key":"rapidseeds_ability","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability","ability_key":"solarbeam_ability","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/lavaland/basilisk/basilisk.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/lavaland/basilisk/basilisk.bt.compiled.json new file mode 100644 index 00000000000..77df6cb13d4 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/lavaland/basilisk/basilisk.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/decorator/target_has_trait","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/is_at_distance","children":[{"type":"/datum/bt_node/ai_behavior/basic_ranged_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location","max_range":9}],"min_distance":2},{"type":"/datum/bt_node/ai_behavior/attack_obstructions","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}]}],"success_policy":1,"failure_policy":1}],"observer_abort":3,"invert":true,"key":"watcher_overwatched"},{"type":"/datum/bt_node/ai_behavior/maintain_distance","target_key":"Current Target"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/lavaland/bileworm/bileworm.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/lavaland/bileworm/bileworm.bt.compiled.json new file mode 100644 index 00000000000..80d0afc48c2 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/lavaland/bileworm/bileworm.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/ability_available","children":[{"type":"/datum/bt_node/decorator/mob_stat_at_least","children":[{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability/and_clear_target","ability_key":"BB_bileworm_devour","target_key":"BB_basic_execution_target","maximum_distance":16}],"observer_abort":2,"invert":false,"key":"BB_basic_execution_target","min_stat":2}],"ability_key":"BB_bileworm_devour"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/ability_available","children":[{"type":"/datum/bt_node/decorator/bileworm_should_resurface","children":[{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability/and_plan_execute","ability_key":"BB_bileworm_resurface","target_key":"Current Target"}],"target_key":"Current Target"}],"ability_key":"BB_bileworm_resurface"},{"type":"/datum/bt_node/decorator/ability_available","children":[{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability/and_plan_execute","ability_key":"BB_bileworm_spew_bile","target_key":"Current Target"}],"ability_key":"BB_bileworm_spew_bile"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/decorator/cooldown","children":[{"type":"/datum/bt_node/ai_behavior/call_reinforcements/mining"}],"cooldown_key":"BB_basic_mob_reinforcements_cooldown","cooldown_duration":300,"lock_on_succeed":true}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"finish_on_primary":false}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/wait"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/lavaland/brimdemon/brimdemon.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/lavaland/brimdemon/brimdemon.bt.compiled.json new file mode 100644 index 00000000000..19d1f2b0032 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/lavaland/brimdemon/brimdemon.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability/brimbeam","ability_key":"BB_TARGETED_action","target_key":"Current Target","maximum_distance":9}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_cardinal","target_key":"Current Target","minimum_distance":2,"maximum_distance":9},{"type":"/datum/bt_node/decorator/cooldown","children":[{"type":"/datum/bt_node/ai_behavior/call_reinforcements/mining"}],"cooldown_key":"BB_basic_mob_reinforcements_cooldown","cooldown_duration":300,"lock_on_succeed":true}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location","check_faction":true},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}]}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/lavaland/goldgrub/babygrub.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/lavaland/goldgrub/babygrub.bt.compiled.json new file mode 100644 index 00000000000..8fdab6836a2 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/lavaland/goldgrub/babygrub.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity/pacifist"},{"type":"/datum/bt_node/ai_behavior/dig_away_from_danger"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/subtree/run_away_from_target"}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/grub_eat_target","bindings":{"bjwb8dxm":"BB_ore_target","bj13tsp3":"BB_ore_target","b6t594ql":"BB_ore_target"}},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_found_mom","required_dist":1,"finish_on_arrival":true}],"observer_abort":3,"key":"BB_found_mom"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/ai_behavior/find_ore"},{"type":"/datum/bt_node/ai_behavior/find_mom"}]}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/lavaland/goldgrub/goldgrub.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/lavaland/goldgrub/goldgrub.bt.compiled.json new file mode 100644 index 00000000000..3c4f0195dc2 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/lavaland/goldgrub/goldgrub.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/pawn_buckled_to_obj","children":[{"type":"/datum/bt_node/ai_behavior/resist"}],"observer_abort":2},{"type":"/datum/bt_node/decorator/pawn_grabbed_by_enemy","children":[{"type":"/datum/bt_node/ai_behavior/resist"}],"observer_abort":2},{"type":"/datum/bt_node/decorator/pawn_is_restrained","children":[{"type":"/datum/bt_node/ai_behavior/resist"}],"observer_abort":2}]},{"type":"","override_id":"pet_command"},{"type":"/datum/bt_node/ai_behavior/dig_away_from_danger"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/burrow_through_ground"}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/subtree/run_away_from_target"}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/grub_eat_target","bindings":{"b6t594ql":"BB_boulder_target","bj13tsp3":"BB_boulder_target","bjwb8dxm":"BB_BOULDER_TARGETBB_BOULDER_TARGET"}},{"type":"/datum/bt_node/subtree/grub_eat_target","bindings":{"b6t594ql":"BB_ore_target","bj13tsp3":"BB_ore_target","bjwb8dxm":"BB_ore_target"}},{"type":"/datum/bt_node/subtree/grub_eat_target","bindings":{"b6t594ql":"BB_vent_target","bj13tsp3":"BB_vent_target","bjwb8dxm":"BB_vent_target"}},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_target_mineral_wall"},{"type":"/datum/bt_node/ai_behavior/set_bb_cooldown","cooldown_key":"Mining Cooldown","cooldown_duration":40},{"type":"/datum/bt_node/ai_behavior/mine_wall","target_key":"BB_target_mineral_wall"}]}],"observer_abort":3,"key":"BB_target_mineral_wall"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_low_priority_hunting_target","required_dist":1,"finish_on_arrival":true},{"type":"/datum/bt_node/ai_behavior/grab_target","target_key":"BB_low_priority_hunting_target"},{"type":"/datum/bt_node/ai_behavior/clear_key","key":"BB_low_priority_hunting_target"}]}],"key":"BB_low_priority_hunting_target","observer_abort":3},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/ai_behavior/find_ore"},{"type":"/datum/bt_node/ai_behavior/find_boulder"},{"type":"/datum/bt_node/ai_behavior/find_ore_vent"},{"type":"/datum/bt_node/decorator/key_off_cooldown","children":[{"type":"/datum/bt_node/ai_behavior/find_mineral_wall","target_key":"BB_target_mineral_wall"}],"cooldown_key":"Mining Cooldown"},{"type":"/datum/bt_node/ai_behavior/find_grub_egg"}]}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/lavaland/goldgrub/grub_eat_target.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/lavaland/goldgrub/grub_eat_target.bt.compiled.json new file mode 100644 index 00000000000..dc90fd61f09 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/lavaland/goldgrub/grub_eat_target.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"$bj13tsp3","required_dist":1,"finish_on_arrival":true},{"type":"/datum/bt_node/ai_behavior/grub_eat","target_key":"$bjwb8dxm"}]}],"observer_abort":3,"key":"$b6t594ql","__bindings":{"b6t594ql":{"label":"target_key","default":null},"bj13tsp3":{"label":"target_key","default":""},"bjwb8dxm":{"label":"target_key","default":""}}} diff --git a/build/behavior_trees/modules/mob/living/basic/lavaland/goliath/goliath.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/lavaland/goliath/goliath.bt.compiled.json new file mode 100644 index 00000000000..a28028fef51 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/lavaland/goliath/goliath.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability/goliath_tentacles","ability_key":"BB_goliath_tentacles","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/attack_obstructions"},{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false},{"type":"/datum/bt_node/decorator/cooldown","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/call_reinforcements/mining","target_key":"Current Target"}],"key":"Current Target"}],"cooldown_key":"BB_basic_mob_reinforcements_cooldown","cooldown_duration":300}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/move_to_and_eat"},{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/goliath_find_diggable_turf","target_key":"BB_goliath_hole"}],"key":"BB_goliath_hole","invert":true},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_goliath_hole","required_dist":0},{"type":"/datum/bt_node/ai_behavior/goliath_dig","target_key":"BB_goliath_hole"}]},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list","target_key":"Current Target","hiding_location_key":"Current Target Hiding Location","check_faction":true}]}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/lavaland/gutlunchers/gutlunch_baby.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/lavaland/gutlunchers/gutlunch_baby.bt.compiled.json new file mode 100644 index 00000000000..e96fbf26a77 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/lavaland/gutlunchers/gutlunch_baby.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity/pacifist"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/subtree/run_away_from_target"}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_found_mom","required_dist":1,"finish_on_arrival":true}],"observer_abort":3,"key":"BB_found_mom"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location","check_faction":false},{"type":"/datum/bt_node/ai_behavior/find_parent","mom_types_key":"BB_find_mom_types","found_mom_key":"BB_found_mom"}]}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/lavaland/gutlunchers/gutlunch_milk.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/lavaland/gutlunchers/gutlunch_milk.bt.compiled.json new file mode 100644 index 00000000000..f659cd1d9af --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/lavaland/gutlunchers/gutlunch_milk.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity/pacifist"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/subtree/run_away_from_target"}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"trough_target","required_dist":1},{"type":"/datum/bt_node/ai_behavior/hunt_target/interact_with_target/food_trough","target_key":"trough_target"}]}],"observer_abort":3,"key":"trough_target"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location","check_faction":false},{"type":"/datum/bt_node/decorator/bb_key_true","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"trough_target","target_source":"/datum/target_source/oview_single_type/gutlunch_trough","targeting_strategy":"/datum/targeting_strategy/trough_with_ore","vision_range":9}],"key":"BB_check_hungry"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/lavaland/gutlunchers/gutlunch_warrior.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/lavaland/gutlunchers/gutlunch_warrior.bt.compiled.json new file mode 100644 index 00000000000..f501a0c0e19 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/lavaland/gutlunchers/gutlunch_warrior.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"","override_id":"pet_command"},{"type":"/datum/bt_node/subtree/make_babies"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/make_babies"},{"type":"/datum/bt_node/subtree/simple_retaliate_combat","bindings":{"b2jvnm5d":1}}]},{"type":"/datum/bt_node/ai_behavior/befriend_ashwalkers"},{"type":"/datum/bt_node/subtree/find_partner"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":50,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/lavaland/hivelord/hivelord.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/lavaland/hivelord/hivelord.bt.compiled.json new file mode 100644 index 00000000000..56a99511c05 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/lavaland/hivelord/hivelord.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/subtree/simple_ability_ranged_combat"} diff --git a/build/behavior_trees/modules/mob/living/basic/lavaland/legion/legion.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/lavaland/legion/legion.bt.compiled.json new file mode 100644 index 00000000000..dc586bdcd46 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/lavaland/legion/legion.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability","ability_key":"BB_TARGETED_action","target_key":"Current Target"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/maintain_distance","target_key":"Current Target"},{"type":"/datum/bt_node/decorator/cooldown","children":[{"type":"/datum/bt_node/ai_behavior/call_reinforcements/mining"}],"cooldown_key":"BB_basic_mob_reinforcements_cooldown","cooldown_duration":300,"lock_on_succeed":true}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/random_speech/legion"}],"success_policy":1,"failure_policy":1,"loop_delay":10}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/lavaland/legion/legion_brood.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/lavaland/legion/legion_brood.bt.compiled.json new file mode 100644 index 00000000000..5ea2779a29c --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/lavaland/legion/legion_brood.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/attack_obstructions","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target"}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/modules/mob/living/basic/lavaland/legion/legion_monkey.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/lavaland/legion/legion_monkey.bt.compiled.json new file mode 100644 index 00000000000..a6f3af26ff1 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/lavaland/legion/legion_monkey.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability","ability_key":"BB_TARGETED_action","target_key":"Current Target"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/maintain_distance","target_key":"Current Target"},{"type":"/datum/bt_node/decorator/cooldown","children":[{"type":"/datum/bt_node/ai_behavior/call_reinforcements/mining"}],"cooldown_key":"BB_basic_mob_reinforcements_cooldown","cooldown_duration":300,"lock_on_succeed":true}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/consider_venting"},{"type":"/datum/bt_node/subtree/random_walk","bindings":{"bf07i8ep":10}}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_entry_vent_target","target_source":"/datum/target_source/oview_single_type/vent_pump","targeting_strategy":"/datum/targeting_strategy/anything","vision_range":7},{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/random_speech/legion"}],"success_policy":1,"failure_policy":1,"loop_delay":10}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity.bt.compiled.json new file mode 100644 index 00000000000..2a1862e533d --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"","override_id":"pet_command"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/run_away_from_target"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability","ability_key":"BB_TARGETED_action","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/attack_obstructions","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false},{"type":"/datum/bt_node/decorator/cooldown","children":[{"type":"/datum/bt_node/ai_behavior/call_reinforcements/mining"}],"cooldown_key":"BB_basic_mob_reinforcements_cooldown","cooldown_duration":300,"lock_on_succeed":true}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}]}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/subtree/random_speech_loop"},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity_calm.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity_calm.bt.compiled.json new file mode 100644 index 00000000000..49b0c7917e7 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity_calm.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity/pacifist"},{"type":"","override_id":"pet_command"},{"type":"/datum/bt_node/subtree/simple_retaliate_combat"}]} diff --git a/build/behavior_trees/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity_capricious.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity_capricious.bt.compiled.json new file mode 100644 index 00000000000..cfdc3e730a6 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity_capricious.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"","override_id":"pet_command"},{"type":"/datum/bt_node/subtree/simple_capricious_combat"}]} diff --git a/build/behavior_trees/modules/mob/living/basic/lavaland/mook/bard.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/lavaland/mook/bard.bt.compiled.json new file mode 100644 index 00000000000..d2c4f973b83 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/lavaland/mook/bard.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/subtree/generic_mook_behavior","bindings":{"b1kiiayf":"/datum/bt_node/subtree/bard_play_music","b0ymbjo1":"/datum/bt_node/subtree/bard_find_targets"}} diff --git a/build/behavior_trees/modules/mob/living/basic/lavaland/mook/bard_find_targets.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/lavaland/mook/bard_find_targets.bt.compiled.json new file mode 100644 index 00000000000..0c56f6db6c9 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/lavaland/mook/bard_find_targets.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"mook_tribal_chief","target_source":"/datum/target_source/oview_single_type/tribal_chief","targeting_strategy":"/datum/targeting_strategy/anything"},{"type":"/datum/bt_node/ai_behavior/befriend_target","target_key":"mook_tribal_chief","long_range_friendship":true,"forget_target":false}]}],"invert":true,"key":"mook_tribal_chief"},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"music_audience","target_source":"/datum/target_source/near_village_humans","targeting_strategy":"/datum/targeting_strategy/conscious_human","time_between_perform":10}]} diff --git a/build/behavior_trees/modules/mob/living/basic/lavaland/mook/bard_play_music.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/lavaland/mook/bard_play_music.bt.compiled.json new file mode 100644 index 00000000000..74fa5b9cfed --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/lavaland/mook/bard_play_music.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/subtree/generic_play_instrument","bindings":{"bulu13xf":75}},{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"music_audience","required_dist":2,"finish_on_arrival":false}],"observer_abort":2,"key":"music_audience"},{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/idle_random_walk/walk_near_target","target_key":"home_village","walk_chance":75}],"success_policy":1,"failure_policy":0,"loop_delay":10}]}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true} diff --git a/build/behavior_trees/modules/mob/living/basic/lavaland/mook/chief_find_targets.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/lavaland/mook/chief_find_targets.bt.compiled.json new file mode 100644 index 00000000000..312234cbe87 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/lavaland/mook/chief_find_targets.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/find_deposit_position"}],"key":"BB_SIMPLE_CARRY_ITEM"},{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/find_ore","time_between_perform":10,"range":1}],"invert":true,"key":"BB_SIMPLE_CARRY_ITEM"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"material_stand_target","target_source":"/datum/target_source/oview_single_type/ore_stand","targeting_strategy":"/datum/targeting_strategy/anything"}],"invert":true,"key":"material_stand_target"}]} diff --git a/build/behavior_trees/modules/mob/living/basic/lavaland/mook/chief_issue_commands.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/lavaland/mook/chief_issue_commands.bt.compiled.json new file mode 100644 index 00000000000..9874acfc371 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/lavaland/mook/chief_issue_commands.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/cooldown","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/issue_pet_command","command_list_key":"BB_mook_commands","command_type":"/datum/pet_command/attack","target_key":"Current Target","commandable_mob_type":"/mob/living/basic/mining/mook","command_distance":7}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/issue_pet_command","command_list_key":"BB_mook_commands","command_type":"/datum/pet_command/fetch","target_key":"BB_ore_target","commandable_mob_type":"/mob/living/basic/mining/mook","command_distance":7}],"observer_abort":3,"key":"BB_ore_target"}]}],"cooldown_key":"command cooldown","cooldown_duration":100,"lock_on_succeed":true} diff --git a/build/behavior_trees/modules/mob/living/basic/lavaland/mook/chief_manage_village.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/lavaland/mook/chief_manage_village.bt.compiled.json new file mode 100644 index 00000000000..c3fb056077b --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/lavaland/mook/chief_manage_village.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_deposit_position"},{"type":"/datum/bt_node/ai_behavior/clear_key","key":"BB_deposit_position"},{"type":"/datum/bt_node/ai_behavior/ai_interact","target_key":"material_stand_target"}]}],"observer_abort":2,"key":"BB_deposit_position"}],"key":"BB_SIMPLE_CARRY_ITEM"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"bonfire_target"},{"type":"/datum/bt_node/ai_behavior/hunt_target/interact_with_target","target_key":"bonfire_target","always_reset_target":true,"behavior_combat_mode":false}]}],"observer_abort":2,"invert":false,"key":"bonfire_target"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_ore_target"},{"type":"/datum/bt_node/ai_behavior/pick_up_item_virtual","target_key":"BB_ore_target","storage_key":"BB_SIMPLE_CARRY_ITEM"}]}],"observer_abort":2,"invert":false,"key":"BB_ore_target"}],"observer_abort":2,"invert":true,"key":"BB_SIMPLE_CARRY_ITEM"},{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/idle_random_walk/walk_near_target","target_key":"home_village","walk_chance":75}],"success_policy":1,"failure_policy":0,"loop_delay":10}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"bonfire_target","target_source":"/datum/target_source/oview_typed/from_bb_key/bonfire_targets","targeting_strategy":"/datum/targeting_strategy/unlit_bonfire"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/modules/mob/living/basic/lavaland/mook/generic_mook_behavior.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/lavaland/mook/generic_mook_behavior.bt.compiled.json new file mode 100644 index 00000000000..374bad58e68 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/lavaland/mook/generic_mook_behavior.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/find_village"}],"invert":true,"key":"home_village"},{"type":"/datum/bt_node/subtree","override_id":"pet_command"},{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/pawn_same_z_as_key","children":[{"type":"/datum/bt_node/decorator/pawn_farther_than_from_key","children":[{"type":"/datum/bt_node/decorator/mook_has_flee_reason","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/clear_key","key":"injured_mook"},{"type":"/datum/bt_node/ai_behavior/use_mob_ability","ability_key":"mook_jump_ability"},{"type":"/datum/bt_node/ai_behavior/clear_key","key":"BB_target_mineral_wall"},{"type":"/datum/bt_node/ai_behavior/clear_key","key":"music_audience"}]}],"observer_abort":2}],"anchor_key":"home_village","distance_key":"maximum_distance_to_village"}],"key":"home_village"},{"type":"$bk7ixti3"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability","ability_key":"mook_leap_ability","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"$b1kiiayf"}]},{"type":"$b0ymbjo1"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}],"__bindings":{"b1kiiayf":{"label":"Idle Behavior","default":"/datum/bt_node/subtree"},"b0ymbjo1":{"label":"Targetting Behavior","default":"/datum/bt_node/subtree"},"bk7ixti3":{"label":"High Priority Behavior","default":"/datum/bt_node/subtree"}}} diff --git a/build/behavior_trees/modules/mob/living/basic/lavaland/mook/go_mining.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/lavaland/mook/go_mining.bt.compiled.json new file mode 100644 index 00000000000..b7010694ca1 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/lavaland/mook/go_mining.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_deposit_position","required_dist":0},{"type":"/datum/bt_node/ai_behavior/clear_key","key":"BB_deposit_position"},{"type":"/datum/bt_node/ai_behavior/ai_interact","target_key":"material_stand_target"}]}],"observer_abort":2,"key":"BB_deposit_position"}],"key":"BB_SIMPLE_CARRY_ITEM"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_ore_target"},{"type":"/datum/bt_node/ai_behavior/pick_up_item_virtual","target_key":"BB_ore_target","storage_key":"BB_SIMPLE_CARRY_ITEM"}]}],"observer_abort":2,"invert":false,"key":"BB_ore_target"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_target_mineral_wall"},{"type":"/datum/bt_node/ai_behavior/mine_wall","target_key":"BB_target_mineral_wall","time_between_perform":10},{"type":"/datum/bt_node/ai_behavior/set_bb_cooldown","cooldown_key":"mining cooldown","cooldown_duration":100},{"type":"/datum/bt_node/ai_behavior/find_ore","time_between_perform":5.0}]}],"observer_abort":2,"invert":false,"key":"BB_target_mineral_wall"},{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/calculate_wander_destination"},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_wander_destination","required_dist":0}]},{"type":"/datum/bt_node/subtree/random_walk"}]}],"observer_abort":2,"invert":true,"key":"BB_SIMPLE_CARRY_ITEM"}]},{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/find_deposit_position"}],"key":"BB_SIMPLE_CARRY_ITEM"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/find_ore"},{"type":"/datum/bt_node/decorator/key_off_cooldown","children":[{"type":"/datum/bt_node/ai_behavior/find_mineral_wall/mook","target_key":"BB_target_mineral_wall"}],"cooldown_key":"mining cooldown"}]}],"invert":true,"key":"BB_SIMPLE_CARRY_ITEM"}]}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/modules/mob/living/basic/lavaland/mook/heal_injured.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/lavaland/mook/heal_injured.bt.compiled.json new file mode 100644 index 00000000000..c70b3d625f1 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/lavaland/mook/heal_injured.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"injured_mook"},{"type":"/datum/bt_node/ai_behavior/hunt_target/interact_with_target","always_reset_target":true,"target_key":"injured_mook"}]}],"observer_abort":3,"key":"injured_mook"},{"type":"/datum/bt_node/ai_behavior/idle_random_walk/walk_near_target","target_key":"home_village","walk_chance":75}]} diff --git a/build/behavior_trees/modules/mob/living/basic/lavaland/mook/mook.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/lavaland/mook/mook.bt.compiled.json new file mode 100644 index 00000000000..eb69e776b45 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/lavaland/mook/mook.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/subtree/generic_mook_behavior","bindings":{"b0ymbjo1":"/datum/bt_node/subtree/worker_find_targets","b1kiiayf":"/datum/bt_node/subtree/go_mining"}} diff --git a/build/behavior_trees/modules/mob/living/basic/lavaland/mook/support.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/lavaland/mook/support.bt.compiled.json new file mode 100644 index 00000000000..e82ee7b19c7 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/lavaland/mook/support.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/subtree/generic_mook_behavior","bindings":{"b1kiiayf":"/datum/bt_node/subtree/heal_injured","b0ymbjo1":"/datum/bt_node/subtree/support_find_targets"}} diff --git a/build/behavior_trees/modules/mob/living/basic/lavaland/mook/support_find_targets.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/lavaland/mook/support_find_targets.bt.compiled.json new file mode 100644 index 00000000000..995d92aea78 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/lavaland/mook/support_find_targets.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"mook_tribal_chief","target_source":"/datum/target_source/oview_single_type/tribal_chief","targeting_strategy":"/datum/targeting_strategy/anything"},{"type":"/datum/bt_node/ai_behavior/befriend_target","target_key":"mook_tribal_chief","long_range_friendship":true,"forget_target":false}]}],"invert":true,"key":"mook_tribal_chief"},{"type":"/datum/bt_node/decorator/bb_key_true","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"injured_mook","target_source":"/datum/target_source/oview_typed/from_bb_key/mook_heal_targets","targeting_strategy":"/datum/targeting_strategy/injured_mob"}],"invert":true,"key":"BB_storm_approaching"}]} diff --git a/build/behavior_trees/modules/mob/living/basic/lavaland/mook/tribal_chief.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/lavaland/mook/tribal_chief.bt.compiled.json new file mode 100644 index 00000000000..47b9fe22a20 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/lavaland/mook/tribal_chief.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/subtree/generic_mook_behavior","bindings":{"b0ymbjo1":"/datum/bt_node/subtree/chief_find_targets","b1kiiayf":"/datum/bt_node/subtree/chief_manage_village","bk7ixti3":"/datum/bt_node/subtree/chief_issue_commands"}} diff --git a/build/behavior_trees/modules/mob/living/basic/lavaland/mook/worker_find_targets.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/lavaland/mook/worker_find_targets.bt.compiled.json new file mode 100644 index 00000000000..22d81cd33cc --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/lavaland/mook/worker_find_targets.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"material_stand_target","target_source":"/datum/target_source/oview_single_type/ore_stand","targeting_strategy":"/datum/targeting_strategy/anything"}],"invert":true,"key":"material_stand_target"},{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"mook_tribal_chief","target_source":"/datum/target_source/oview_single_type/tribal_chief","targeting_strategy":"/datum/targeting_strategy/anything"},{"type":"/datum/bt_node/ai_behavior/befriend_target","target_key":"mook_tribal_chief","long_range_friendship":true,"forget_target":false}]}],"invert":true,"key":"mook_tribal_chief"}]} diff --git a/build/behavior_trees/modules/mob/living/basic/lavaland/node_drone/node_drone.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/lavaland/node_drone/node_drone.bt.compiled.json new file mode 100644 index 00000000000..5d4dd01a869 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/lavaland/node_drone/node_drone.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/decorator/pawn_buckled_to_obj","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_current_hunting_target","required_dist":1,"finish_on_arrival":true},{"type":"/datum/bt_node/ai_behavior/hunt_target/latch_onto","target_key":"BB_current_hunting_target","hunt_cooldown":50}]}],"observer_abort":1,"invert":true,"target_key":"BB_current_hunting_target"}],"key":"BB_current_hunting_target","observer_abort":3}],"success_policy":1,"failure_policy":1,"loop_delay":15.0},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_current_hunting_target","target_source":"/datum/target_source/oview_single_type/ore_vent","targeting_strategy":"/datum/targeting_strategy/ore_vent_unclaimed","vision_range":7}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/lavaland/raptor/baby_raptor.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/lavaland/raptor/baby_raptor.bt.compiled.json new file mode 100644 index 00000000000..64de9c0a6b5 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/lavaland/raptor/baby_raptor.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity/pacifist"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/run_away_from_target"},{"type":"/datum/bt_node/subtree/move_to_and_eat"},{"type":"/datum/bt_node/decorator/random_chance","children":[{"type":"/datum/bt_node/ai_behavior/express_happiness"}],"observer_abort":3,"chance":0.3},{"type":"/datum/bt_node/decorator/random_chance","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_found_mom"},{"type":"/datum/bt_node/ai_behavior/look_to_parent"}]}],"observer_abort":3,"key":"BB_found_mom"}],"observer_abort":3,"chance":0.15},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/subtree/raptor_find_food"},{"type":"/datum/bt_node/subtree/random_speech_loop"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/find_mom"}],"invert":true,"key":"BB_found_mom"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/lavaland/raptor/care_for_young.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/lavaland/raptor/care_for_young.bt.compiled.json new file mode 100644 index 00000000000..4b4a289ebbc --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/lavaland/raptor/care_for_young.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/decorator/random_chance","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"raptor_baby"},{"type":"/datum/bt_node/ai_behavior/ai_interact","target_key":"raptor_baby","combat_mode":false},{"type":"/datum/bt_node/ai_behavior/perform_emote","emote":"grooms its baby!"},{"type":"/datum/bt_node/ai_behavior/clear_key","key":"raptor_baby"}]}],"chance":0.6}],"observer_abort":3,"key":"raptor_baby"} diff --git a/build/behavior_trees/modules/mob/living/basic/lavaland/raptor/raptor_ai.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/lavaland/raptor/raptor_ai.bt.compiled.json new file mode 100644 index 00000000000..67a67cd3559 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/lavaland/raptor/raptor_ai.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/subtree","override_id":"pet_command"},{"type":"/datum/bt_node/subtree/raptor_flee"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/raptor_heal_injured"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/raptor_food_trough"},{"type":"/datum/bt_node/subtree/move_to_and_eat"},{"type":"/datum/bt_node/subtree/make_babies"},{"type":"/datum/bt_node/decorator/random_chance","children":[{"type":"/datum/bt_node/subtree/play_with_owner"}],"observer_abort":0,"chance":0.4},{"type":"/datum/bt_node/subtree/care_for_young"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/subtree/raptor_find_food"},{"type":"/datum/bt_node/decorator/bb_key_true","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target","target_key":"BB_owner_target","targeting_strategy":"/datum/targeting_strategy/ally_mob","target_source":"/datum/target_source/oview_single_type/living_mob"}],"observer_abort":0,"key":"raptor_playful"},{"type":"/datum/bt_node/decorator/bb_key_true","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target","target_key":"injured_raptor","targeting_strategy":"/datum/targeting_strategy/injured_mob/not_self/injured_raptor"},{"type":"/datum/bt_node/ai_behavior/retrieve_injured_rider","target_key":"injured_raptor"}]}],"observer_abort":0,"key":"BB_basic_mob_healer"},{"type":"/datum/bt_node/decorator/bb_key_true","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target","target_key":"raptor_baby","targeting_strategy":"/datum/targeting_strategy/healthy_raptor_baby","target_source":"/datum/target_source/oview_raptor_babies"}],"observer_abort":0,"key":"raptor_motherly"},{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}]},{"type":"/datum/bt_node/subtree/find_partner"},{"type":"/datum/bt_node/subtree/random_speech_loop"},{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/express_happiness"}],"success_policy":1,"failure_policy":1,"loop_delay":10}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/lavaland/raptor/raptor_find_food.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/lavaland/raptor/raptor_find_food.bt.compiled.json new file mode 100644 index 00000000000..01309e1c72c --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/lavaland/raptor/raptor_find_food.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/key_off_cooldown","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/subtree/find_food"},{"type":"/datum/bt_node/ai_behavior/acquire_target","target_key":"raptor_trough_target","targeting_strategy":"/datum/targeting_strategy/raptor_trough","target_source":"/datum/target_source/oview_single_type/raptor_trough"}],"failure_policy":0,"success_policy":0,"repeat_secondary":false,"finish_on_primary":false}],"observer_abort":1,"cooldown_key":"BB_NEXT_EAT_FOOD"} diff --git a/build/behavior_trees/modules/mob/living/basic/lavaland/raptor/raptor_flee.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/lavaland/raptor/raptor_flee.bt.compiled.json new file mode 100644 index 00000000000..087fea9e73d --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/lavaland/raptor/raptor_flee.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_true","children":[{"type":"/datum/bt_node/subtree/run_away_from_target"}],"observer_abort":3,"key":"BB_RAPTOR_COWARDLY"},{"type":"/datum/bt_node/decorator/pawn_health_below","children":[{"type":"/datum/bt_node/subtree/run_away_from_target"}],"observer_abort":3,"health_blackboard_key":"raptor_flee_threshold"},{"type":"/datum/bt_node/decorator/check_rider_stat","children":[{"type":"/datum/bt_node/subtree/run_away_from_target"}],"observer_abort":3}]} diff --git a/build/behavior_trees/modules/mob/living/basic/lavaland/raptor/raptor_heal_injured.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/lavaland/raptor/raptor_heal_injured.bt.compiled.json new file mode 100644 index 00000000000..a9af0238bf0 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/lavaland/raptor/raptor_heal_injured.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"injured_raptor"},{"type":"/datum/bt_node/ai_behavior/ai_interact","target_key":"injured_raptor","combat_mode":false},{"type":"/datum/bt_node/ai_behavior/clear_key","key":"injured_raptor"}]}],"observer_abort":3,"key":"injured_raptor"} diff --git a/build/behavior_trees/modules/mob/living/basic/lavaland/raptor/raptor_play_with_owner.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/lavaland/raptor/raptor_play_with_owner.bt.compiled.json new file mode 100644 index 00000000000..1d2d0523de2 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/lavaland/raptor/raptor_play_with_owner.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_owner_target"},{"type":"/datum/bt_node/ai_behavior/hunt_target/play_with_owner","cooldown_key":"BB_owner_target"}]}],"observer_abort":3,"key":"BB_owner_target"} diff --git a/build/behavior_trees/modules/mob/living/basic/lavaland/raptor/raptor_trough.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/lavaland/raptor/raptor_trough.bt.compiled.json new file mode 100644 index 00000000000..bd3184c469a --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/lavaland/raptor/raptor_trough.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"raptor_trough_target"},{"type":"/datum/bt_node/ai_behavior/ai_interact","target_key":"raptor_trough_target","combat_mode":false},{"type":"/datum/bt_node/ai_behavior/clear_key","key":"raptor_trough_target"}]}],"observer_abort":3,"key":"raptor_trough_target"} diff --git a/build/behavior_trees/modules/mob/living/basic/lavaland/tendril/tendril.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/lavaland/tendril/tendril.bt.compiled.json new file mode 100644 index 00000000000..364faacec07 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/lavaland/tendril/tendril.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/is_at_distance","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/use_mob_ability","ability_key":"tendril_spikes"},{"type":"/datum/bt_node/ai_behavior/use_mob_ability","ability_key":"tendril_lash"}]}],"target_key":"Current Target","maximum_distance":7},{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability","ability_key":"tendril_chaser","target_key":"Current Target"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/decorator/cooldown","children":[{"type":"/datum/bt_node/ai_behavior/call_reinforcements/mining"}],"cooldown_key":"BB_basic_mob_reinforcements_cooldown","cooldown_duration":300,"lock_on_succeed":true}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"finish_on_primary":false}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/wait"}]},{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location","check_faction":true,"vision_range":5},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location","target_loss_distance":9,"vision_range":5}]}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/modules/mob/living/basic/lavaland/watcher/watcher.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/lavaland/watcher/watcher.bt.compiled.json new file mode 100644 index 00000000000..db275f6a561 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/lavaland/watcher/watcher.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/decorator/target_has_trait","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_at_least","children":[{"type":"/datum/bt_node/ai_behavior/use_mob_ability"}],"key":"BB_basic_mob_has_target_time","minimum":50},{"type":"/datum/bt_node/ai_behavior/basic_ranged_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location","max_range":9}]}],"success_policy":1,"failure_policy":1}],"observer_abort":3,"invert":true,"key":"watcher_overwatched"},{"type":"/datum/bt_node/ai_behavior/maintain_distance","target_key":"Current Target"},{"type":"/datum/bt_node/decorator/cooldown","children":[{"type":"/datum/bt_node/ai_behavior/call_reinforcements/mining"}],"cooldown_key":"BB_basic_mob_reinforcements_cooldown","cooldown_duration":300,"lock_on_succeed":true}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location","check_faction":true},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}]}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/minebots/minebot.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/minebots/minebot.bt.compiled.json new file mode 100644 index 00000000000..c0787dc703f --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/minebots/minebot.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree","override_id":"pet_command"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/minebot_combat"},{"type":"/datum/bt_node/subtree/minebot_mining"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"miner_friend","target_source":"/datum/target_source/oview_single_type/human_mob","targeting_strategy":"/datum/targeting_strategy/rock_stoner"},{"type":"/datum/bt_node/ai_behavior/befriend_target","target_key":"miner_friend","long_range_friendship":true}]},{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/decorator/cooldown","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"nearby_dead_miner","target_source":"/datum/target_source/oview_single_type/human_mob","targeting_strategy":"/datum/targeting_strategy/unconscious_human"}],"cooldown_key":"minebot callcrit cooldown","cooldown_duration":100},{"type":"/datum/bt_node/ai_behavior/send_sos_message","target_key":"nearby_dead_miner"}]}]}],"success_policy":0,"failure_policy":0,"loop_delay":10}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/minebots/minebot_combat.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/minebots/minebot_combat.bt.compiled.json new file mode 100644 index 00000000000..efd6d22309b --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/minebots/minebot_combat.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability/and_clear_target","ability_key":"minebot_missile_ability","target_key":"minebot_missile_target"}],"observer_abort":2,"key":"minebot_missile_ability"},{"type":"/datum/bt_node/ai_behavior/basic_ranged_attack/minebot","target_key":"Current Target","hiding_location_key":"Current Target Hiding Location"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/maintain_distance","target_key":"Current Target","min_dist_key":"minimum_shooting_distance","max_dist_key":"minimum_shooting_distance"},{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"minebot_missile_target","target_source":"/datum/target_source/oview_single_type/minebot_target","targeting_strategy":"/datum/targeting_strategy/anything","vision_range":7}],"key":"minebot_missile_target","invert":true}],"success_policy":0,"failure_policy":0,"loop_delay":10},{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/use_mob_ability","ability_key":"minebot_landmine_ability"}],"invert":false,"key":"minebot_landmine_ability"}],"success_policy":0,"failure_policy":0,"loop_delay":10}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"key":"Current Target","observer_abort":3} diff --git a/build/behavior_trees/modules/mob/living/basic/minebots/minebot_mining.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/minebots/minebot_mining.bt.compiled.json new file mode 100644 index 00000000000..33fa38f1c9e --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/minebots/minebot_mining.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_true","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/decorator/target_health_below_fraction","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"defend_drone","required_dist":1,"finish_on_arrival":true},{"type":"/datum/bt_node/ai_behavior/repair_drone","target_key":"defend_drone"}]}],"observer_abort":3,"key":"defend_drone","fraction":0.75}],"observer_abort":3,"key":"defend_drone"}],"observer_abort":3,"key":"minebot_repair_drone"},{"type":"/datum/bt_node/ai_behavior/befriend_target/check_ally","target_key":"defend_drone","long_range_friendship":true,"forget_target":false}]},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_ore_target","required_dist":1,"finish_on_arrival":true},{"type":"/datum/bt_node/ai_behavior/collect_ore/minebot","target_key":"BB_ore_target"}]}],"observer_abort":3,"key":"BB_ore_target"},{"type":"/datum/bt_node/decorator/bb_key_true","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"target_mineral_turf","required_dist":2,"finish_on_arrival":true},{"type":"/datum/bt_node/ai_behavior/minebot_mine_turf","target_key":"target_mineral_turf"}]}],"observer_abort":2,"key":"target_mineral_turf"}],"observer_abort":2,"key":"automated_mining"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","time_between_perform":50,"target_key":"defend_drone","targeting_strategy":"/datum/targeting_strategy/anything","target_source":"/datum/target_source/oview_single_type/node_drone","vision_range":9,"revalidation_mode":1},{"type":"/datum/bt_node/decorator/bb_key_true","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_ore_target","targeting_strategy":"/datum/targeting_strategy/pickup_item","target_source":"/datum/target_source/oview_single_type/ore","vision_range":2},{"type":"/datum/bt_node/ai_behavior/find_mineral_wall/minebot","target_key":"target_mineral_turf"}]}],"observer_abort":2,"key":"automated_mining"}]}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/modules/mob/living/basic/pets/cat/cat.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/pets/cat/cat.bt.compiled.json new file mode 100644 index 00000000000..b13bced69f2 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/pets/cat/cat.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/pawn_buckled_to_obj","children":[{"type":"/datum/bt_node/ai_behavior/resist"}],"observer_abort":2},{"type":"/datum/bt_node/decorator/pawn_grabbed_by_enemy","children":[{"type":"/datum/bt_node/ai_behavior/resist"}],"observer_abort":2},{"type":"/datum/bt_node/decorator/pawn_is_restrained","children":[{"type":"/datum/bt_node/ai_behavior/resist"}],"observer_abort":2}]},{"type":"/datum/bt_node/subtree","override_id":"pet_command"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/subtree/run_away_from_target","bindings":{"byk9gqj4":"BB_basic_flee_target"}},{"type":"/datum/bt_node/ai_behavior/clear_key","key":"BB_basic_flee_target"}]}],"key":"BB_basic_flee_target","observer_abort":2},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/cat_reside_in_home"},{"type":"/datum/bt_node/subtree/cat_haul_food"},{"type":"/datum/bt_node/subtree/cat_hunt_mice"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"tresspasser_target","required_dist":1},{"type":"/datum/bt_node/ai_behavior/territorial_struggle","target_key":"tresspasser_target","cries_key":"hostile_meows"}]}],"key":"tresspasser_target","observer_abort":3},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"key":"Current Target","observer_abort":3}],"observer_abort":0,"invert":true,"key":"mouse_target"},{"type":"/datum/bt_node/subtree/cat_find_food"},{"type":"/datum/bt_node/subtree/make_babies"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/subtree/random_speech_loop","bindings":{"bqdqne64":"/datum/bt_node/ai_behavior/random_speech/cats"}}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/subtree/find_partner"},{"type":"/datum/bt_node/decorator/random_chance","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"cat_home","target_source":"/datum/target_source/oview_single_type/cat_house","targeting_strategy":"/datum/targeting_strategy/valid_cat_home"}],"chance":0.05},{"type":"/datum/bt_node/decorator/random_chance","children":[{"type":"/datum/bt_node/ai_behavior/find_cat_tresspasser","target_key":"tresspasser_target"}],"chance":0.05},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"mouse_target","target_source":"/datum/target_source/oview_single_type/mouse","targeting_strategy":"/datum/targeting_strategy/huntable_mouse","vision_range":9},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"cat_food_target","target_source":"/datum/target_source/oview_typed/from_bb_key/huntable_prey","targeting_strategy":"/datum/targeting_strategy/cat_food","vision_range":9}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/pets/cat/cat_bread.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/pets/cat/cat_bread.bt.compiled.json new file mode 100644 index 00000000000..4f03d5a4dde --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/pets/cat/cat_bread.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity/pacifist"},{"type":"/datum/bt_node/subtree","override_id":"pet_command"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/cat_turn_off_stove"},{"type":"/datum/bt_node/subtree/cat_haul_food"},{"type":"/datum/bt_node/subtree/cat_hunt_mice"},{"type":"/datum/bt_node/subtree/cat_find_food"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/subtree/random_speech_loop","bindings":{"bqdqne64":"/datum/bt_node/ai_behavior/random_speech/cats"}}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true},{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/find_partner"},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"mouse_target","target_source":"/datum/target_source/oview_single_type/mouse","targeting_strategy":"/datum/targeting_strategy/huntable_mouse","vision_range":9},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"cat_food_target","target_source":"/datum/target_source/oview_typed/from_bb_key/huntable_prey","targeting_strategy":"/datum/targeting_strategy/cat_food","vision_range":9},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"stove_target","target_source":"/datum/target_source/oview_single_type/oven","targeting_strategy":"/datum/targeting_strategy/finished_stove","vision_range":9},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"food_to_deliver","target_source":"/datum/target_source/carried_huntable_prey","targeting_strategy":"/datum/targeting_strategy/anything"}],"key":"food_to_deliver","invert":true},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"food_to_deliver","target_source":"/datum/target_source/carried_huntable_prey","targeting_strategy":"/datum/targeting_strategy/anything"}],"key":"food_to_deliver","invert":true}]}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/pets/cat/cat_cake.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/pets/cat/cat_cake.bt.compiled.json new file mode 100644 index 00000000000..cfc0bf46ee6 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/pets/cat/cat_cake.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity/pacifist"},{"type":"/datum/bt_node/subtree","override_id":"pet_command"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/cat_turn_off_stove"},{"type":"/datum/bt_node/subtree/cat_decorate_donuts"},{"type":"/datum/bt_node/subtree/cat_haul_food"},{"type":"/datum/bt_node/subtree/cat_hunt_mice"},{"type":"/datum/bt_node/subtree/cat_find_food"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/subtree/random_speech_loop","bindings":{"bqdqne64":"/datum/bt_node/ai_behavior/random_speech/cats"}}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true},{"type":"/datum/bt_node/subtree/find_partner"},{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"mouse_target","target_source":"/datum/target_source/oview_single_type/mouse","targeting_strategy":"/datum/targeting_strategy/huntable_mouse","vision_range":9},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"cat_food_target","target_source":"/datum/target_source/oview_typed/from_bb_key/huntable_prey","targeting_strategy":"/datum/targeting_strategy/cat_food","vision_range":9},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"stove_target","target_source":"/datum/target_source/oview_single_type/oven","targeting_strategy":"/datum/targeting_strategy/finished_stove","vision_range":9},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"donut_target","target_source":"/datum/target_source/oview_single_type/donut","targeting_strategy":"/datum/targeting_strategy/decorated_donut","vision_range":9},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"food_to_deliver","target_source":"/datum/target_source/carried_huntable_prey","targeting_strategy":"/datum/targeting_strategy/anything"}],"key":"food_to_deliver","invert":true},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"food_to_deliver","target_source":"/datum/target_source/carried_huntable_prey","targeting_strategy":"/datum/targeting_strategy/anything"}],"key":"food_to_deliver","invert":true}]}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/pets/cat/cat_decorate_donuts.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/pets/cat/cat_decorate_donuts.bt.compiled.json new file mode 100644 index 00000000000..875e7b4fde4 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/pets/cat/cat_decorate_donuts.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"donut_target","required_dist":1},{"type":"/datum/bt_node/ai_behavior/hunt_target/decorate_donuts","target_key":"donut_target"}]}],"key":"donut_target","observer_abort":3} diff --git a/build/behavior_trees/modules/mob/living/basic/pets/cat/cat_find_food.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/pets/cat/cat_find_food.bt.compiled.json new file mode 100644 index 00000000000..51899c21580 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/pets/cat/cat_find_food.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"cat_food_target","required_dist":1},{"type":"/datum/bt_node/ai_behavior/hunt_target/interact_with_target","target_key":"cat_food_target","always_reset_target":true}]}],"key":"cat_food_target","observer_abort":3} diff --git a/build/behavior_trees/modules/mob/living/basic/pets/cat/cat_haul_food.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/pets/cat/cat_haul_food.bt.compiled.json new file mode 100644 index 00000000000..a717f60d6e4 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/pets/cat/cat_haul_food.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/cat_holding_food","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"kitten_to_feed","required_dist":1},{"type":"/datum/bt_node/ai_behavior/deliver_food_to_kitten","target_key":"kitten_to_feed","food_key":"food_to_deliver"}]}],"key":"food_to_deliver"}],"key":"kitten_to_feed","observer_abort":3}]} diff --git a/build/behavior_trees/modules/mob/living/basic/pets/cat/cat_hunt_mice.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/pets/cat/cat_hunt_mice.bt.compiled.json new file mode 100644 index 00000000000..770c97852cc --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/pets/cat/cat_hunt_mice.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/cat_holding_food","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"mouse_target","required_dist":1},{"type":"/datum/bt_node/ai_behavior/play_with_mouse","target_key":"mouse_target"}]}],"key":"mouse_target","observer_abort":3}],"invert":true} diff --git a/build/behavior_trees/modules/mob/living/basic/pets/cat/cat_reside_in_home.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/pets/cat/cat_reside_in_home.bt.compiled.json new file mode 100644 index 00000000000..0cf26f4dd1a --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/pets/cat/cat_reside_in_home.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/pawn_loc_is_type","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/leave_cat_home"},{"type":"/datum/bt_node/ai_behavior/succeed"}]}],"loc_type":"/obj/structure/cat_house","observer_abort":2},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"cat_home","required_dist":1},{"type":"/datum/bt_node/ai_behavior/enter_cat_home","target_key":"cat_home"}]}],"key":"cat_home","observer_abort":3}]} diff --git a/build/behavior_trees/modules/mob/living/basic/pets/cat/cat_turn_off_stove.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/pets/cat/cat_turn_off_stove.bt.compiled.json new file mode 100644 index 00000000000..d7496ff2ade --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/pets/cat/cat_turn_off_stove.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"stove_target","required_dist":1},{"type":"/datum/bt_node/ai_behavior/hunt_target/interact_with_target","target_key":"stove_target","always_reset_target":true}]}],"key":"stove_target","observer_abort":3} diff --git a/build/behavior_trees/modules/mob/living/basic/pets/cat/kitten.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/pets/cat/kitten.bt.compiled.json new file mode 100644 index 00000000000..3054191cd5a --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/pets/cat/kitten.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree","override_id":"pet_command"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/subtree/run_away_from_target"}],"key":"Current Target","observer_abort":2},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/beacon_for_food","target_key":"human_beg_target","meows_key":"hungry_meows"}],"key":"human_beg_target","observer_abort":3},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/pawn_farther_than_from_key","children":[{"type":"/datum/bt_node/ai_behavior/beacon_for_food","target_key":"cat_food_target","meows_key":"hungry_meows"}],"anchor_key":"cat_food_target","distance_key":"max_distance_to_food"},{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"cat_food_target","required_dist":1},{"type":"/datum/bt_node/ai_behavior/hunt_target/interact_with_target","target_key":"cat_food_target","always_reset_target":true}]}]}],"key":"cat_food_target","observer_abort":3},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/subtree/random_speech_loop","bindings":{"bqdqne64":"/datum/bt_node/ai_behavior/random_speech/cats"}}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true},{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"cat_food_target","target_source":"/datum/target_source/oview_typed/from_bb_key/huntable_prey","targeting_strategy":"/datum/targeting_strategy/cat_food","vision_range":9},{"type":"/datum/bt_node/ai_behavior/find_human_to_beg","target_key":"human_beg_target"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/pets/dog/guarddog.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/pets/dog/guarddog.bt.compiled.json new file mode 100644 index 00000000000..d94250485b8 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/pets/dog/guarddog.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/subtree/simple_retaliate_combat"} diff --git a/build/behavior_trees/modules/mob/living/basic/pets/fox.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/pets/fox.bt.compiled.json new file mode 100644 index 00000000000..cc55e5de082 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/pets/fox.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/subtree","override_id":"pet_command"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/subtree/run_away_from_target","bindings":{"byk9gqj4":"BB_basic_flee_target"}}],"key":"BB_basic_flee_target","observer_abort":2},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/subtree/random_speech_loop","bindings":{"bqdqne64":"/datum/bt_node/ai_behavior/random_speech/fox"}}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true},{"type":"/datum/bt_node/decorator/no_humans_watching","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"polling_rate":20},{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list","target_key":"BB_basic_flee_target","targeting_strategy":"flee_targeting_strategy","hiding_location_key":"BB_basic_flee_target_hiding_location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/pets/fox_docile.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/pets/fox_docile.bt.compiled.json new file mode 100644 index 00000000000..d977d389b7c --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/pets/fox_docile.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/subtree/run_away_from_target","bindings":{"byk9gqj4":"BB_basic_flee_target"}}],"key":"BB_basic_flee_target","observer_abort":2},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/subtree/random_walk"},{"type":"/datum/bt_node/subtree/random_speech_loop","bindings":{"bqdqne64":"/datum/bt_node/ai_behavior/random_speech/fox"}}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true},{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list","target_key":"BB_basic_flee_target","targeting_strategy":"flee_targeting_strategy","hiding_location_key":"BB_basic_flee_target_hiding_location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/pets/gondolas/gondola.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/pets/gondolas/gondola.bt.compiled.json new file mode 100644 index 00000000000..e1ea2e2d727 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/pets/gondolas/gondola.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/subtree/random_walk","bindings":{"bf07i8ep":10}} diff --git a/build/behavior_trees/modules/mob/living/basic/pets/orbie/orbie.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/pets/orbie/orbie.bt.compiled.json new file mode 100644 index 00000000000..69f59fc8113 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/pets/orbie/orbie.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree","override_id":"pet_command"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"nearby_playmate","required_dist":1,"finish_on_arrival":false},{"type":"/datum/bt_node/ai_behavior/interact_with_playmate","target_key":"nearby_playmate"}]}],"key":"nearby_playmate","observer_abort":3},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/relay_pda_message","target_key":"last_received_message"}],"observer_abort":2,"key":"last_received_message"}]},{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/move_to_and_eat"},{"type":"/datum/bt_node/subtree/random_walk"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/decorator/key_off_cooldown","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target/find_playmate"}],"cooldown_key":"next_playdate"},{"type":"/datum/bt_node/subtree/find_food"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/modules/mob/living/basic/pets/parrot/parrot_ai/parrot.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/pets/parrot/parrot_ai/parrot.bt.compiled.json new file mode 100644 index 00000000000..0b2b3a8ebef --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/pets/parrot/parrot_ai/parrot.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/pawn_contained_in_obj","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/container_attackable","children":[{"type":"/datum/bt_node/ai_behavior/break_out_of_object/from_bb","target_key":"BB_basic_mob_escape_target"}]},{"type":"/datum/bt_node/ai_behavior/resist"}]}],"observer_abort":2},{"type":"/datum/bt_node/decorator/pawn_grabbed_by_enemy","children":[{"type":"/datum/bt_node/ai_behavior/resist"}],"observer_abort":2},{"type":"/datum/bt_node/decorator/pawn_is_restrained","children":[{"type":"/datum/bt_node/ai_behavior/resist"}],"observer_abort":2}]},{"type":"/datum/bt_node/subtree","override_id":"pet_command"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/random_chance","children":[{"type":"/datum/bt_node/subtree/perching"}],"chance":0.05},{"type":"/datum/bt_node/subtree/parrot_hoard"},{"type":"/datum/bt_node/ai_behavior/idle_random_walk/parrot"}]}],"success_policy":1,"failure_policy":0,"loop_delay":15.0}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/decorator/cooldown","children":[{"type":"/datum/bt_node/ai_behavior/parrot_repeat_speech"}],"cooldown_key":"BB_parrot_speech_cooldown","cooldown_duration":75.0},{"type":"/datum/bt_node/decorator/pawn_buckled_to_obj","children":[{"type":"/datum/bt_node/decorator/random_chance","children":[{"type":"/datum/bt_node/ai_behavior/resist"}],"chance":0.05}]}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/pets/parrot/parrot_ai/parrot_ghost.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/pets/parrot/parrot_ai/parrot_ghost.bt.compiled.json new file mode 100644 index 00000000000..d0e88078717 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/pets/parrot/parrot_ai/parrot_ghost.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"perch_target","required_dist":0},{"type":"/datum/bt_node/ai_behavior/perch_on_target/haunt","target_key":"perch_target"}]},{"type":"/datum/bt_node/ai_behavior/clear_key","key":"perch_target"}]}],"key":"perch_target"},{"type":"/datum/bt_node/decorator/random_chance","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"perch_target","target_source":"/datum/target_source/oview_single_type/human_mob","targeting_strategy":"/datum/targeting_strategy/conscious_human"}],"chance":0.02},{"type":"/datum/bt_node/subtree/parrot_hoard"},{"type":"/datum/bt_node/ai_behavior/idle_random_walk/parrot"}]}],"success_policy":1,"failure_policy":0,"loop_delay":15.0},{"type":"/datum/bt_node/decorator/cooldown","children":[{"type":"/datum/bt_node/ai_behavior/parrot_repeat_speech"}],"cooldown_key":"BB_parrot_speech_cooldown","cooldown_duration":75.0},{"type":"/datum/bt_node/decorator/pawn_buckled_to_obj","children":[{"type":"/datum/bt_node/decorator/random_chance","children":[{"type":"/datum/bt_node/ai_behavior/resist"}],"chance":0.05}]}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/pets/parrot/parrot_ai/parrot_hoard.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/pets/parrot/parrot_ai/parrot_hoard.bt.compiled.json new file mode 100644 index 00000000000..547cbde046f --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/pets/parrot/parrot_ai/parrot_hoard.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/succeed"}],"key":"hoard_location"},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"hoard_location","targeting_strategy":"/datum/targeting_strategy/parrot_hoard_location","target_source":"/datum/target_source/oview","vision_range":"hoard_location_range"}]},{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/target_is_holding_item","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"hoard_location","required_dist":1},{"type":"/datum/bt_node/ai_behavior/drop_all_held_items"}]}],"observer_abort":1,"key":"Literally me"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"hoard_item_target","required_dist":1},{"type":"/datum/bt_node/ai_behavior/basic_melee_attack/interact_once/parrot","target_key":"hoard_item_target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}]},{"type":"/datum/bt_node/ai_behavior/clear_key","key":"hoard_item_target"}]}],"observer_abort":1,"key":"hoard_item_target"},{"type":"/datum/bt_node/decorator/random_chance","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/parrot_hoard_item","target_key":"hoard_item_target"}],"chance":0.05}]}]} diff --git a/build/behavior_trees/modules/mob/living/basic/pets/parrot/parrot_ai/perching.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/pets/parrot/parrot_ai/perching.bt.compiled.json new file mode 100644 index 00000000000..5cfd1694daf --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/pets/parrot/parrot_ai/perching.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/random_chance","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"perch_target","target_source":"/datum/target_source/oview_single_type/human_mob","targeting_strategy":"/datum/targeting_strategy/ally_mob"}],"chance":0.5},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"perch_target","target_source":"/datum/target_source/oview_typed/from_bb_key/parrot_perch_types","targeting_strategy":"/datum/targeting_strategy/anything"}]}],"key":"perch_target","invert":true},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"perch_target","required_dist":1},{"type":"/datum/bt_node/ai_behavior/perch_on_target","target_key":"perch_target"}]}],"observer_abort":1,"invert":false,"key":"perch_target"}]},{"type":"/datum/bt_node/ai_behavior/clear_key","key":"perch_target"}]} diff --git a/build/behavior_trees/modules/mob/living/basic/pets/penguin/penguin.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/pets/penguin/penguin.bt.compiled.json new file mode 100644 index 00000000000..8e5adef45d2 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/pets/penguin/penguin.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/subtree/run_away_from_target"}],"key":"Current Target","observer_abort":3},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/move_to_and_eat"},{"type":"/datum/bt_node/subtree/move_to_and_hunt","bindings":{"bvtz06kb":"BB_fishing_target","b3y599q4":"BB_fishing_target","brrasnah":"BB_fishing_target","b3cnse9r":1,"bd1towgc":0,"bqwjf4id":450,"bm3y5m55":"BB_fishing_timer"}},{"type":"/datum/bt_node/subtree/move_to_and_hunt","bindings":{"bvtz06kb":"BB_drillable_ice","b3y599q4":"BB_drillable_ice","brrasnah":"BB_drillable_ice","b3cnse9r":1,"bd1towgc":1,"bqwjf4id":150,"bm3y5m55":"BB_ice_drilling_timer"}},{"type":"/datum/bt_node/subtree/move_to_and_hunt"},{"type":"/datum/bt_node/subtree/random_walk"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/subtree/random_speech_loop","bindings":{"bqdqne64":"/datum/bt_node/ai_behavior/random_speech/penguin"}},{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list/nearest","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/subtree/find_food"},{"type":"/datum/bt_node/decorator/key_off_cooldown","children":[{"type":"/datum/bt_node/decorator/key_off_cooldown","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_fishing_target","target_source":"/datum/target_source/range_turfs/typecache_visible/ice","targeting_strategy":"/datum/targeting_strategy/fishing"}],"cooldown_key":"BB_fishing_timer"}],"cooldown_key":"BB_next_food_eat"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/decorator/key_off_cooldown","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_drillable_ice","target_source":"/datum/target_source/range_turfs/typecache_visible/ice","targeting_strategy":"/datum/targeting_strategy/drillable_ice"}],"cooldown_key":"BB_ice_drilling_timer"}],"invert":true,"key":"BB_fishing_target"},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_low_priority_hunting_target","target_source":"/datum/target_source/oview_single_type/penguin_egg","targeting_strategy":"/datum/targeting_strategy/uncarried_egg"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/pets/penguin/penguin_baby.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/pets/penguin/penguin_baby.bt.compiled.json new file mode 100644 index 00000000000..74c13f8600f --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/pets/penguin/penguin_baby.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/subtree/run_away_from_target"}],"key":"Current Target","observer_abort":3},{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/move_to_and_eat"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/decorator/cooldown","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_found_mom","required_dist":1},{"type":"/datum/bt_node/ai_behavior/look_to_parent","parent_key":"BB_found_mom"}]}],"cooldown_key":"BB_parent_emote_cooldown","cooldown_duration":60}],"observer_abort":2,"key":"BB_found_mom"},{"type":"/datum/bt_node/subtree/random_walk"}]}],"success_policy":1,"failure_policy":1}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list/nearest","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/subtree/find_food"},{"type":"/datum/bt_node/ai_behavior/find_mom","mom_types_key":"BB_find_mom_types","ignore_types_key":"BB_ignore_mom_types","found_mom_key":"BB_found_mom"},{"type":"/datum/bt_node/subtree/random_speech_loop","bindings":{"bqdqne64":"/datum/bt_node/ai_behavior/random_speech/penguin"}}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/pets/pet_cult/pet_cult.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/pets/pet_cult/pet_cult.bt.compiled.json new file mode 100644 index 00000000000..3290bdc5f1f --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/pets/pet_cult/pet_cult.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/subtree","override_id":"pet_command"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"friendly_cultist","required_dist":1,"finish_on_arrival":false},{"type":"/datum/bt_node/ai_behavior/befriend_target","target_key":"friendly_cultist","befriend_message":"friendly_message"}]}],"key":"friendly_cultist","observer_abort":3},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"occupied_rune","required_dist":1,"finish_on_arrival":false},{"type":"/datum/bt_node/ai_behavior/activate_rune"}]}],"key":"occupied_rune","observer_abort":3},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"dead_cultist","required_dist":1,"finish_on_arrival":false},{"type":"/datum/bt_node/ai_behavior/drag_target","target_key":"dead_cultist"},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"nearby_rune","required_dist":0,"finish_on_arrival":false},{"type":"/datum/bt_node/ai_behavior/clear_key","key":"dead_cultist"}]}],"key":"dead_cultist","observer_abort":3},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"friendly_cultist","target_source":"/datum/target_source/oview_single_type/carbon_mob","targeting_strategy":"/datum/targeting_strategy/befriendable_cultist","vision_range":9,"time_between_perform":50},{"type":"/datum/bt_node/ai_behavior/find_occupied_rune"},{"type":"/datum/bt_node/ai_behavior/find_dead_cultist"}]}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/pets/sloth.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/pets/sloth.bt.compiled.json new file mode 100644 index 00000000000..814ac30820a --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/pets/sloth.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/subtree/run_away_from_target"}],"key":"Current Target","observer_abort":1},{"type":"/datum/bt_node/subtree/climb_tree"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/subtree/random_speech_loop"},{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list/nearest","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_climbed_tree","target_source":"/datum/target_source/oview_single_type/flora_tree","targeting_strategy":"/datum/targeting_strategy/anything"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/modules/mob/living/basic/revolutionary.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/revolutionary.bt.compiled.json new file mode 100644 index 00000000000..e717c859fad --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/revolutionary.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/subtree/simple_hostile_combat","bindings":{"bbrbyj7y":"/datum/bt_node/ai_behavior/random_speech_blackboard"}}]} diff --git a/build/behavior_trees/modules/mob/living/basic/ruin_defender/cybersun_ai_core.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/ruin_defender/cybersun_ai_core.bt.compiled.json new file mode 100644 index 00000000000..311240eadb5 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/ruin_defender/cybersun_ai_core.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/ai_behavior/wait"},{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability","ability_key":"BB_lightning_strike","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability","ability_key":"BB_cybersun_barrage","target_key":"Current Target"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/wait"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/modules/mob/living/basic/ruin_defender/dark_wizard.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/ruin_defender/dark_wizard.bt.compiled.json new file mode 100644 index 00000000000..a67dcbcb128 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/ruin_defender/dark_wizard.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_ranged_attack","time_between_perform":6.0,"target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/maintain_distance","target_key":"Current Target","approach_movement_type":"/datum/ai_movement/basic_avoidance"}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"}]},{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}]}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/ruin_defender/fleshblob.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/ruin_defender/fleshblob.bt.compiled.json new file mode 100644 index 00000000000..8a767cda225 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/ruin_defender/fleshblob.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/subtree/simple_hostile_combat"} diff --git a/build/behavior_trees/modules/mob/living/basic/ruin_defender/living_floor.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/ruin_defender/living_floor.bt.compiled.json new file mode 100644 index 00000000000..42ff9194db7 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/ruin_defender/living_floor.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/decorator/is_at_distance","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"target_key":"Current Target","maximum_distance":0}],"success_policy":1,"failure_policy":1}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/wait"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location","vision_range":2}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/modules/mob/living/basic/ruin_defender/mad_piano.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/ruin_defender/mad_piano.bt.compiled.json new file mode 100644 index 00000000000..0643dd9bec5 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/ruin_defender/mad_piano.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/subtree/simple_hostile_combat","bindings":{"bp3p5vvb":80}} diff --git a/build/behavior_trees/modules/mob/living/basic/ruin_defender/mimic/mimic_animator.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/ruin_defender/mimic/mimic_animator.bt.compiled.json new file mode 100644 index 00000000000..c3c0eb4d849 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/ruin_defender/mimic/mimic_animator.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/decorator/bb_key_true","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/ai_behavior/basic_ranged_attack","key":"BB_current_hunting_target","targeting_strategy":"/datum/targeting_strategy/anything","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/ai_behavior/move_to_target","key":"BB_current_hunting_target","required_dist":1,"finish_on_arrival":false},{"type":"/datum/bt_node/subtree/random_speech_loop"}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true},{"type":"/datum/bt_node/ai_behavior/clear_key","key":"BB_current_hunting_target"}]}],"key":"BB_GUNMIMIC_GUN_EMPTY","invert":true}],"observer_abort":3,"key":"BB_current_hunting_target"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false},{"type":"/datum/bt_node/subtree/random_speech_loop"}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"key":"Current Target","observer_abort":3},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/decorator/bb_key_true","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets/nearest","key":"BB_current_hunting_target","targeting_strategy":"/datum/targeting_strategy/anything","target_source":"/datum/target_source/animatable_objects","vision_range":7,"revalidation_mode":3}],"key":"BB_GUNMIMIC_GUN_EMPTY","invert":true}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/modules/mob/living/basic/ruin_defender/mimic/mimic_copy.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/ruin_defender/mimic/mimic_copy.bt.compiled.json new file mode 100644 index 00000000000..68d1b491e28 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/ruin_defender/mimic/mimic_copy.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/attack_obstructions","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}]}],"success_policy":0,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false},{"type":"/datum/bt_node/subtree/random_speech_loop"}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"key":"Current Target","observer_abort":3},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/ruin_defender/mimic/mimic_crate.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/ruin_defender/mimic/mimic_crate.bt.compiled.json new file mode 100644 index 00000000000..b9fb9ac2f16 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/ruin_defender/mimic/mimic_crate.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity/pacifist"},{"type":"/datum/bt_node/subtree/simple_hostile_combat"}]} diff --git a/build/behavior_trees/modules/mob/living/basic/ruin_defender/mimic/mimic_gun.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/ruin_defender/mimic/mimic_gun.bt.compiled.json new file mode 100644 index 00000000000..da1e8d7c186 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/ruin_defender/mimic/mimic_gun.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_true","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_ranged_attack/avoid_friendly_fire","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/maintain_distance","target_key":"Current Target"}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"key":"BB_GUNMIMIC_GUN_EMPTY","invert":true},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}]},{"type":"/datum/bt_node/subtree/random_speech_loop"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}],"key":"Current Target","observer_abort":3},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/modules/mob/living/basic/ruin_defender/skeleton.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/ruin_defender/skeleton.bt.compiled.json new file mode 100644 index 00000000000..dc27d1b5265 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/ruin_defender/skeleton.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/move_to_and_eat"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/subtree/find_food"},{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/decorator/random_chance","children":[{"type":"/datum/bt_node/ai_behavior/perform_emote","emote":"rattles"}],"chance":0.2}],"success_policy":1,"failure_policy":1,"loop_delay":10}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/modules/mob/living/basic/ruin_defender/stickman.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/ruin_defender/stickman.bt.compiled.json new file mode 100644 index 00000000000..8a767cda225 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/ruin_defender/stickman.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/subtree/simple_hostile_combat"} diff --git a/build/behavior_trees/modules/mob/living/basic/ruin_defender/stickman_ranged.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/ruin_defender/stickman_ranged.bt.compiled.json new file mode 100644 index 00000000000..6d376bbd651 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/ruin_defender/stickman_ranged.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/subtree/simple_ranged_combat","bindings":{"bpnckxev":50}} diff --git a/build/behavior_trees/modules/mob/living/basic/ruin_defender/wizard/wizard.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/ruin_defender/wizard/wizard.bt.compiled.json new file mode 100644 index 00000000000..3b31b341459 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/ruin_defender/wizard/wizard.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/decorator/cooldown","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability","ability_key":"BB_wizard_targeted_spell","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability","ability_key":"BB_wizard_secondary_spell","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability","ability_key":"BB_wizard_blink_spell","target_key":"Current Target"}]}],"cooldown_key":"BB_wizard_spell_cooldown","cooldown_duration":10}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/maintain_distance/cover_minimum_distance","target_key":"Current Target"}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"invert":false,"key":"Current Target"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/ruin_defender/zombie.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/ruin_defender/zombie.bt.compiled.json new file mode 100644 index 00000000000..83e682656a6 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/ruin_defender/zombie.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/subtree/simple_hostile_obstacles_combat"}]},{"type":"/datum/bt_node/subtree/random_speech_loop"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/modules/mob/living/basic/ruin_defender/zombie_stupid.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/ruin_defender/zombie_stupid.bt.compiled.json new file mode 100644 index 00000000000..9c3ea45a350 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/ruin_defender/zombie_stupid.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/subtree/simple_hostile_combat"},{"type":"/datum/bt_node/subtree/random_speech_loop"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/modules/mob/living/basic/slime/ai/pet_command_attack_slime.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/slime/ai/pet_command_attack_slime.bt.compiled.json new file mode 100644 index 00000000000..28e715a7375 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/slime/ai/pet_command_attack_slime.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/feed_on_slime_target","target_key":"BB_current_pet_target"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_current_pet_target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"key":"BB_current_pet_target","observer_abort":3}]} diff --git a/build/behavior_trees/modules/mob/living/basic/slime/ai/slime.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/slime/ai/slime.bt.compiled.json new file mode 100644 index 00000000000..67621bb800c --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/slime/ai/slime.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/decorator/ability_available","children":[{"type":"/datum/bt_node/ai_behavior/use_mob_ability","ability_key":"BB_slime_evolve"}],"ability_key":"BB_slime_evolve"},{"type":"/datum/bt_node/decorator/ability_available","children":[{"type":"/datum/bt_node/ai_behavior/use_mob_ability","ability_key":"BB_slime_reproduce"}],"observer_abort":2,"ability_key":"BB_slime_reproduce"},{"type":"/datum/bt_node/subtree","override_id":"pet_command"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/feed_on_slime_target","target_key":"BB_slime_eat_target"},{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"BB_slime_eat_target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_slime_eat_target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"BB_slime_eat_target"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/feed_on_slime_target","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location","check_faction":false},{"type":"/datum/bt_node/decorator/slime_wants_to_eat","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_slime_eat_target","targeting_strategy":"/datum/targeting_strategy/slime_food","target_source":"/datum/target_source/oview_living_no_slimes","vision_range":7,"must_be_reachable":true}]},{"type":"/datum/bt_node/subtree/random_speech_loop"},{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/change_slime_face"}],"success_policy":1,"failure_policy":1,"loop_delay":10}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/snails/snail.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/snails/snail.bt.compiled.json new file mode 100644 index 00000000000..997827fa395 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/snails/snail.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/use_mob_ability","ability_key":"snail_retreat_ability"}],"success_policy":1,"failure_policy":1}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/move_to_and_eat"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list/nearest","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/subtree/find_food"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/modules/mob/living/basic/space_fauna/ant.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/space_fauna/ant.bt.compiled.json new file mode 100644 index 00000000000..69269d3f36f --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/space_fauna/ant.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/run_away_from_target"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list/nearest","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/space_fauna/bear/bear.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/space_fauna/bear/bear.bt.compiled.json new file mode 100644 index 00000000000..8c4e6dfba04 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/space_fauna/bear/bear.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"key":"Current Target","observer_abort":3},{"type":"/datum/bt_node/subtree/climb_tree"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_found_honey","required_dist":1},{"type":"/datum/bt_node/ai_behavior/hunt_target/find_hive","target_key":"BB_found_honey","cooldown_key":"BB_bear_hive_cooldown","hunt_cooldown":50}]}],"key":"BB_found_honey","observer_abort":3},{"type":"/datum/bt_node/decorator/is_dragging","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_low_priority_hunting_target","required_dist":1},{"type":"/datum/bt_node/ai_behavior/hunt_target/pull_target","target_key":"BB_low_priority_hunting_target","cooldown_key":"BB_bear_honeycomb_cooldown","hunt_cooldown":50}]}],"key":"BB_low_priority_hunting_target","observer_abort":3}],"observer_abort":1,"invert":true},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_climbed_tree","target_source":"/datum/target_source/oview_single_type/flora_tree","targeting_strategy":"/datum/targeting_strategy/anything"},{"type":"/datum/bt_node/decorator/key_off_cooldown","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_found_honey","target_source":"/datum/target_source/oview_single_type/beehive","targeting_strategy":"/datum/targeting_strategy/stocked_beehive","vision_range":10}],"cooldown_key":"BB_bear_hive_cooldown"},{"type":"/datum/bt_node/decorator/is_dragging","children":[{"type":"/datum/bt_node/decorator/key_off_cooldown","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_low_priority_hunting_target","target_source":"/datum/target_source/oview_single_type/honeycomb","targeting_strategy":"/datum/targeting_strategy/huntable","vision_range":10}],"cooldown_key":"BB_bear_honeycomb_cooldown"}],"invert":true},{"type":"/datum/bt_node/ai_behavior/random_speech/bear"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/space_fauna/carp/basic_carp_tree.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/space_fauna/carp/basic_carp_tree.bt.compiled.json new file mode 100644 index 00000000000..50293ab3288 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/space_fauna/carp/basic_carp_tree.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"$be5c4p10"},{"type":"/datum/bt_node/subtree","override_id":"pet_command"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/carp_flee","bindings":{"bf3k9q2p":"$b7cm2s3z"}},{"type":"$bc0mb4t9"}]}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/composite/selector","children":[{"type":"$bid1e6k3"},{"type":"/datum/bt_node/subtree/random_walk"}]}]},{"type":"$bf1nd7r2"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/find_magicarp_spell_target","ability_key":"BB_magicarp_spell","target_key":"BB_magicarp_spell_target","targeting_strategy_key":"targeting_strategy"}],"key":"BB_magicarp_spell"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}],"__bindings":{"be5c4p10":{"label":"escape_tree","default":"/datum/bt_node/subtree"},"bf1nd7r2":{"label":"find_targets_tree","default":"/datum/bt_node/subtree/carp_target_selection"},"bc0mb4t9":{"label":"combat_tree","default":"/datum/bt_node/subtree/carp_combat"},"bid1e6k3":{"label":"idle_tree","default":"/datum/bt_node/subtree/carp_migration"},"b7cm2s3z":{"label":"flee_from_key","default":"Current Target"}}} diff --git a/build/behavior_trees/modules/mob/living/basic/space_fauna/carp/carp.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/space_fauna/carp/carp.bt.compiled.json new file mode 100644 index 00000000000..f1ea869828c --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/space_fauna/carp/carp.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/subtree/basic_carp_tree","bindings":{"be5c4p10":"/datum/bt_node/subtree/escape_captivity"}} diff --git a/build/behavior_trees/modules/mob/living/basic/space_fauna/carp/carp_combat.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/space_fauna/carp/carp_combat.bt.compiled.json new file mode 100644 index 00000000000..b44fb494abd --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/space_fauna/carp/carp_combat.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/decorator/ability_available","children":[{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability/and_clear_target","ability_key":"BB_magicarp_spell","target_key":"BB_magicarp_spell_target"}],"ability_key":"BB_magicarp_spell"}],"key":"BB_magicarp_spell_target"},{"type":"/datum/bt_node/ai_behavior/attack_obstructions","target_key":"Current Target","time_between_perform":15.0},{"type":"/datum/bt_node/decorator/ability_available","children":[{"type":"/datum/bt_node/ai_behavior/make_carp_rift/towards/aggressive","ability_key":"BB_carp_rift","target_key":"Current Target"}],"ability_key":"BB_carp_rift"},{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true} diff --git a/build/behavior_trees/modules/mob/living/basic/space_fauna/carp/carp_flee.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/space_fauna/carp/carp_flee.bt.compiled.json new file mode 100644 index 00000000000..b50eacda373 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/space_fauna/carp/carp_flee.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/carp_should_flee","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/ability_available","children":[{"type":"/datum/bt_node/ai_behavior/make_carp_rift/away","ability_key":"BB_carp_rift","target_key":"Current Target"}],"ability_key":"BB_carp_rift"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/find_flee_location","target_key":"$bf3k9q2p","hiding_location_key":"Current Target Hiding Location","destination_key":"BB_flee_location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_flee_location","required_dist":0,"finish_on_arrival":true}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}]}],"observer_abort":3,"target_key":"Current Target"} diff --git a/build/behavior_trees/modules/mob/living/basic/space_fauna/carp/carp_migration.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/space_fauna/carp/carp_migration.bt.compiled.json new file mode 100644 index 00000000000..3d9462e4bba --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/space_fauna/carp/carp_migration.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/find_carp_rift_shortcut/nearby","destination_key":"BB_carp_rift_destination"},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_carp_rift_destination","required_dist":0,"finish_on_arrival":true},{"type":"/datum/bt_node/ai_behavior/clear_key","key":"BB_carp_rift_destination"}]},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/carp_path_blocked","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/ability_available","children":[{"type":"/datum/bt_node/ai_behavior/make_carp_rift/towards/unvalidated","ability_key":"BB_carp_rift","target_key":"BB_carp_migration_target"}],"ability_key":"BB_carp_rift"},{"type":"/datum/bt_node/ai_behavior/attack_obstructions","target_key":"BB_carp_migration_target","time_between_perform":15.0}]}],"target_key":"BB_carp_migration_target"},{"type":"/datum/bt_node/ai_behavior/succeed"}]},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_carp_migration_target","required_dist":3,"finish_on_arrival":true},{"type":"/datum/bt_node/ai_behavior/clear_arrived_migration_target","target_key":"BB_carp_migration_target"}]}],"key":"BB_carp_migration_target"},{"type":"/datum/bt_node/ai_behavior/find_next_carp_migration_step","path_key":"BB_carp_migration_path","target_key":"BB_carp_migration_target"}]} diff --git a/build/behavior_trees/modules/mob/living/basic/space_fauna/carp/carp_passive.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/space_fauna/carp/carp_passive.bt.compiled.json new file mode 100644 index 00000000000..56f25cf96ec --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/space_fauna/carp/carp_passive.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/subtree/basic_carp_tree","bindings":{"bf1nd7r2":"/datum/bt_node/subtree/carp_passive_selection","b7cm2s3z":"BB_basic_flee_target"}} diff --git a/build/behavior_trees/modules/mob/living/basic/space_fauna/carp/carp_passive_selection.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/space_fauna/carp/carp_passive_selection.bt.compiled.json new file mode 100644 index 00000000000..71ffcc90003 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/space_fauna/carp/carp_passive_selection.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"BB_basic_flee_target_hiding_location"},{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list/nearest","target_key":"Current Target","targeting_strategy":"flee_targeting_strategy","hiding_location_key":"BB_basic_flee_target_hiding_location","check_faction":false}]} diff --git a/build/behavior_trees/modules/mob/living/basic/space_fauna/carp/carp_pet.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/space_fauna/carp/carp_pet.bt.compiled.json new file mode 100644 index 00000000000..736b43c21ac --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/space_fauna/carp/carp_pet.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/subtree/basic_carp_tree","bindings":{"bf1nd7r2":"/datum/bt_node/subtree/carp_retaliate_selection","bc0mb4t9":"/datum/bt_node/subtree/carp_retaliate_selection","bid1e6k3":"/datum/bt_node/subtree"}} diff --git a/build/behavior_trees/modules/mob/living/basic/space_fauna/carp/carp_ranged.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/space_fauna/carp/carp_ranged.bt.compiled.json new file mode 100644 index 00000000000..f1ea869828c --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/space_fauna/carp/carp_ranged.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/subtree/basic_carp_tree","bindings":{"be5c4p10":"/datum/bt_node/subtree/escape_captivity"}} diff --git a/build/behavior_trees/modules/mob/living/basic/space_fauna/carp/carp_retaliate_selection.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/space_fauna/carp/carp_retaliate_selection.bt.compiled.json new file mode 100644 index 00000000000..c9ac61faf9b --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/space_fauna/carp/carp_retaliate_selection.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location","check_faction":false} diff --git a/build/behavior_trees/modules/mob/living/basic/space_fauna/carp/carp_target_selection.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/space_fauna/carp/carp_target_selection.bt.compiled.json new file mode 100644 index 00000000000..4727d5ac7e3 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/space_fauna/carp/carp_target_selection.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_true","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets/nearest","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"key":"BB_basic_stop_fleeing","invert":true},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets/prioritize_trait","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}]} diff --git a/build/behavior_trees/modules/mob/living/basic/space_fauna/cat_butcherer.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/space_fauna/cat_butcherer.bt.compiled.json new file mode 100644 index 00000000000..3594068e0ce --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/space_fauna/cat_butcherer.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/subtree/simple_hostile_combat_with_retaliate"} diff --git a/build/behavior_trees/modules/mob/living/basic/space_fauna/changeling/headslug.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/space_fauna/changeling/headslug.bt.compiled.json new file mode 100644 index 00000000000..9dbb6fb090f --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/space_fauna/changeling/headslug.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/subtree/random_walk"} diff --git a/build/behavior_trees/modules/mob/living/basic/space_fauna/eyeball/eyeball.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/space_fauna/eyeball/eyeball.bt.compiled.json new file mode 100644 index 00000000000..0889c9e9f35 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/space_fauna/eyeball/eyeball.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/ability_available","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/find_target_facing_turf","target_key":"Current Target","set_key":"BB_glare_position"},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_glare_position","required_dist":0},{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability","ability_key":"BB_glare_ability","target_key":"Current Target"}]}],"observer_abort":3,"ability_key":"BB_glare_ability"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}]}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_blind_target","required_dist":1},{"type":"/datum/bt_node/ai_behavior/heal_eye_damage","target_key":"BB_blind_target"}]}],"observer_abort":3,"key":"BB_blind_target"},{"type":"/datum/bt_node/subtree/move_to_and_hunt","bindings":{"bqwjf4id":20,"b3cnse9r":1}},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_blind_target","target_source":"/datum/target_source/oview_single_type/carbon_mob","targeting_strategy":"/datum/targeting_strategy/damaged_eyes","vision_range":9},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_low_priority_hunting_target","target_source":"/datum/target_source/oview_single_type/carrot","targeting_strategy":"/datum/targeting_strategy/anything","vision_range":6}]}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/space_fauna/faithless.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/space_fauna/faithless.bt.compiled.json new file mode 100644 index 00000000000..4bfe6e5b1d8 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/space_fauna/faithless.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/attack_obstructions","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_low_priority_hunting_target"},{"type":"/datum/bt_node/ai_behavior/hunt_target/interact_with_target","target_key":"BB_low_priority_hunting_target","always_reset_target":true}]}],"observer_abort":3,"key":"BB_low_priority_hunting_target"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_low_priority_hunting_target","target_source":"/datum/target_source/oview_typed/from_bb_key/hunt_target_list","targeting_strategy":"/datum/targeting_strategy/unbroken_light","vision_range":7},{"type":"/datum/bt_node/subtree/random_speech_loop","bindings":{"bqdqne64":"/datum/bt_node/ai_behavior/random_speech/faithless"}}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/space_fauna/garden_gnome.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/space_fauna/garden_gnome.bt.compiled.json new file mode 100644 index 00000000000..73f73c702cb --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/space_fauna/garden_gnome.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/attack_obstructions","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/random_walk"}]}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true},{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location","check_faction":"$b2jvnm5d"},{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/random_speech/garden_gnome"}],"success_policy":1,"failure_policy":1,"loop_delay":10}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/space_fauna/ghost.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/space_fauna/ghost.bt.compiled.json new file mode 100644 index 00000000000..15c3371800e --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/space_fauna/ghost.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/subtree/simple_retaliate_combat"}]} diff --git a/build/behavior_trees/modules/mob/living/basic/space_fauna/hivebot/hivebot.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/space_fauna/hivebot/hivebot.bt.compiled.json new file mode 100644 index 00000000000..5e3ae60adf8 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/space_fauna/hivebot/hivebot.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/attack_obstructions","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/relay_to_hive_partner"},{"type":"/datum/bt_node/subtree/random_walk"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/decorator/random_chance","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_hive_partner","target_source":"/datum/target_source/oview_single_type/hivebot","targeting_strategy":"/datum/targeting_strategy/living_not_dead","vision_range":10}],"chance":0.1}],"success_policy":0,"failure_policy":0,"loop_delay":10}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/modules/mob/living/basic/space_fauna/hivebot/hivebot_mechanic.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/space_fauna/hivebot/hivebot_mechanic.bt.compiled.json new file mode 100644 index 00000000000..80226ce0624 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/space_fauna/hivebot/hivebot_mechanic.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/attack_obstructions","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/basic_ranged_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_machine_target","required_dist":1},{"type":"/datum/bt_node/ai_behavior/hunt_target/repair_machines","target_key":"BB_machine_target"}]}],"observer_abort":2,"key":"BB_machine_target"},{"type":"/datum/bt_node/subtree/relay_to_hive_partner"},{"type":"/datum/bt_node/subtree/random_walk"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_machine_target","target_source":"/datum/target_source/oview_single_type/machine","targeting_strategy":"/datum/targeting_strategy/damaged_machine","vision_range":10},{"type":"/datum/bt_node/decorator/random_chance","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_hive_partner","target_source":"/datum/target_source/oview_single_type/hivebot","targeting_strategy":"/datum/targeting_strategy/living_not_dead","vision_range":10}],"key":"BB_hive_partner","invert":true}],"chance":0.1}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/space_fauna/hivebot/hivebot_ranged.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/space_fauna/hivebot/hivebot_ranged.bt.compiled.json new file mode 100644 index 00000000000..768d4f7c1f9 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/space_fauna/hivebot/hivebot_ranged.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/attack_obstructions","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/basic_ranged_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location","time_between_perform":30,"avoid_friendly_fire":true}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/relay_to_hive_partner"},{"type":"/datum/bt_node/subtree/random_walk"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/decorator/random_chance","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_hive_partner","target_source":"/datum/target_source/oview_single_type/hivebot","targeting_strategy":"/datum/targeting_strategy/living_not_dead","vision_range":10}],"key":"BB_hive_partner","invert":true}],"chance":0.1}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/space_fauna/hivebot/hivebot_ranged_rapid.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/space_fauna/hivebot/hivebot_ranged_rapid.bt.compiled.json new file mode 100644 index 00000000000..54c129760ab --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/space_fauna/hivebot/hivebot_ranged_rapid.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/attack_obstructions","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/basic_ranged_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location","time_between_perform":15.0,"avoid_friendly_fire":true}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/relay_to_hive_partner"},{"type":"/datum/bt_node/subtree/random_walk"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/decorator/random_chance","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_hive_partner","target_source":"/datum/target_source/oview_single_type/hivebot","targeting_strategy":"/datum/targeting_strategy/living_not_dead","vision_range":10}],"key":"BB_hive_partner","invert":true}],"chance":0.1}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/space_fauna/hivebot/relay_to_hive_partner.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/space_fauna/hivebot/relay_to_hive_partner.bt.compiled.json new file mode 100644 index 00000000000..7f515ce5ab4 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/space_fauna/hivebot/relay_to_hive_partner.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_hive_partner","required_dist":1},{"type":"/datum/bt_node/ai_behavior/relay_message","target_key":"BB_hive_partner"},{"type":"/datum/bt_node/ai_behavior/clear_key","key":"BB_hive_partner"}]}],"observer_abort":2,"key":"BB_hive_partner"} diff --git a/build/behavior_trees/modules/mob/living/basic/space_fauna/killer_tomato.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/space_fauna/killer_tomato.bt.compiled.json new file mode 100644 index 00000000000..80638d2c1c5 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/space_fauna/killer_tomato.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/subtree/simple_hostile_obstacles_combat","bindings":{"bqul7l8t":"/datum/bt_node/subtree/random_speech_loop"}} diff --git a/build/behavior_trees/modules/mob/living/basic/space_fauna/lightgeist.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/space_fauna/lightgeist.bt.compiled.json new file mode 100644 index 00000000000..8a767cda225 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/space_fauna/lightgeist.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/subtree/simple_hostile_combat"} diff --git a/build/behavior_trees/modules/mob/living/basic/space_fauna/meteor_heart/meteor_heart.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/space_fauna/meteor_heart/meteor_heart.bt.compiled.json new file mode 100644 index 00000000000..d7f083f2c7d --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/space_fauna/meteor_heart/meteor_heart.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability","ability_key":"BB_meteor_ground_spikes","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/use_mob_ability","ability_key":"BB_meteor_spine_traps"}]}],"success_policy":1,"failure_policy":1}],"key":"Current Target","observer_abort":3},{"type":"/datum/bt_node/ai_behavior/meteor_heart_deaggro"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/space_fauna/morph.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/space_fauna/morph.bt.compiled.json new file mode 100644 index 00000000000..3594068e0ce --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/space_fauna/morph.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/subtree/simple_hostile_combat_with_retaliate"} diff --git a/build/behavior_trees/modules/mob/living/basic/space_fauna/mushroom.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/space_fauna/mushroom.bt.compiled.json new file mode 100644 index 00000000000..a3ff530bcf9 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/space_fauna/mushroom.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","hiding_location_key":"Current Target Hiding Location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/move_to_and_hunt","bindings":{"bvtz06kb":"BB_low_priority_hunting_target","b3y599q4":"BB_low_priority_hunting_target","brrasnah":"BB_low_priority_hunting_target","bd1towgc":1,"b3cnse9r":1}},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_low_priority_hunting_target","targeting_strategy":"/datum/targeting_strategy/pickup_item","target_source":"/datum/target_source/oview_single_type/mushroom_food","vision_range":6}],"key":"BB_low_priority_hunting_target","invert":true}]}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/modules/mob/living/basic/space_fauna/paper_wizard/paper_wizard.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/space_fauna/paper_wizard/paper_wizard.bt.compiled.json new file mode 100644 index 00000000000..6e28b2abdab --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/space_fauna/paper_wizard/paper_wizard.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/attack_obstructions","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability","ability_key":"BB_summon_mimics","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/use_mob_ability","ability_key":"BB_summon_minions"},{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/find_paper_and_write"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_found_paper","target_source":"/datum/target_source/oview_single_type/paper","targeting_strategy":"/datum/targeting_strategy/empty_paper","time_between_perform":100}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/space_fauna/regal_rat/regal_rat.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/space_fauna/regal_rat/regal_rat.bt.compiled.json new file mode 100644 index 00000000000..2c2d16007fc --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/space_fauna/regal_rat/regal_rat.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/subtree/run_away_from_target","bindings":{"byk9gqj4":"BB_basic_flee_target"}},{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability","ability_key":"BB_raise_horde_ability","target_key":"BB_basic_flee_target"},{"type":"/datum/bt_node/ai_behavior/attack_obstructions","target_key":"BB_basic_flee_target"},{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"BB_basic_flee_target","targeting_strategy":"targeting_strategy","hiding_location_key":"BB_basic_flee_target_hiding_location"}]}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"key":"BB_basic_flee_target","observer_abort":3},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list","target_key":"BB_basic_flee_target","targeting_strategy":"flee_targeting_strategy","hiding_location_key":"BB_basic_flee_target_hiding_location"},{"type":"/datum/bt_node/ai_behavior/use_mob_ability/domain","ability_key":"BB_domain_ability"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/space_fauna/roro.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/space_fauna/roro.bt.compiled.json new file mode 100644 index 00000000000..d94250485b8 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/space_fauna/roro.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/subtree/simple_retaliate_combat"} diff --git a/build/behavior_trees/modules/mob/living/basic/space_fauna/snake/banded.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/space_fauna/snake/banded.bt.compiled.json new file mode 100644 index 00000000000..81aa18815bb --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/space_fauna/snake/banded.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/move_to_and_eat"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/subtree/random_speech_loop"},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/subtree/find_food"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/space_fauna/snake/snake.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/space_fauna/snake/snake.bt.compiled.json new file mode 100644 index 00000000000..060d0fd3042 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/space_fauna/snake/snake.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/move_to_and_eat"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/subtree/random_speech_loop"},{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/subtree/find_food"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/space_fauna/spaceman.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/space_fauna/spaceman.bt.compiled.json new file mode 100644 index 00000000000..fa7d5222808 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/space_fauna/spaceman.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity/pacifist"},{"type":"/datum/bt_node/subtree/simple_retaliate_combat"}]} diff --git a/build/behavior_trees/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider.bt.compiled.json new file mode 100644 index 00000000000..cfd354a3a88 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/ai_behavior/attack_obstructions","target_key":"Current Target"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_spider_web_target","required_dist":0},{"type":"/datum/bt_node/ai_behavior/spin_web","action_key":"BB_spider_web_action","target_key":"BB_spider_web_target"}]}],"observer_abort":3,"key":"BB_spider_web_target"}]},{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/ai_behavior/find_unwebbed_turf","target_key":"BB_spider_web_target"}]},{"type":"/datum/bt_node/subtree/random_speech_loop"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider_pest.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider_pest.bt.compiled.json new file mode 100644 index 00000000000..69b4956b49c --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider_pest.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity/pacifist"},{"type":"/datum/bt_node/subtree","override_id":"pet_command"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/subtree/run_away_from_target","bindings":{"byk9gqj4":"BB_basic_flee_target"}}],"observer_abort":2,"key":"BB_basic_flee_target"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_spider_web_target","required_dist":0},{"type":"/datum/bt_node/ai_behavior/spin_web","action_key":"BB_spider_web_action","target_key":"BB_spider_web_target"}]}],"observer_abort":3,"key":"BB_spider_web_target"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list","target_key":"BB_basic_flee_target","targeting_strategy":"flee_targeting_strategy","hiding_location_key":"BB_basic_flee_target_hiding_location"},{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/ai_behavior/find_unwebbed_turf","target_key":"BB_spider_web_target"}]},{"type":"/datum/bt_node/subtree/random_speech_loop"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider_retaliate.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider_retaliate.bt.compiled.json new file mode 100644 index 00000000000..7fbac82cdd8 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider_retaliate.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/ai_behavior/attack_obstructions","target_key":"Current Target"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list/nearest","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/subtree/random_speech_loop"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider_weak.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider_weak.bt.compiled.json new file mode 100644 index 00000000000..16995a0eb62 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider_weak.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity/pacifist"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_spider_web_target","required_dist":0},{"type":"/datum/bt_node/ai_behavior/spin_web","action_key":"BB_spider_web_action","target_key":"BB_spider_web_target"}]}],"observer_abort":3,"key":"BB_spider_web_target"}]},{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/ai_behavior/find_unwebbed_turf","target_key":"BB_spider_web_target"}]},{"type":"/datum/bt_node/subtree/random_speech_loop"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/space_fauna/spider/spiderlings/spiderling.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/space_fauna/spider/spiderlings/spiderling.bt.compiled.json new file mode 100644 index 00000000000..6989bddc89c --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/space_fauna/spider/spiderlings/spiderling.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity/pacifist"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/consider_venting"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/subtree/run_away_from_target"}],"observer_abort":3,"invert":false,"key":"Current Target"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/subtree/random_speech_loop"},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_entry_vent_target","target_source":"/datum/target_source/oview_single_type/vent_pump","targeting_strategy":"/datum/targeting_strategy/anything","vision_range":7}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/space_fauna/spider/young_spider/young_spider.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/space_fauna/spider/young_spider/young_spider.bt.compiled.json new file mode 100644 index 00000000000..7ce03eefeee --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/space_fauna/spider/young_spider/young_spider.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity/pacifist"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/ai_behavior/attack_obstructions","target_key":"Current Target"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/run_away_from_target","bindings":{"byk9gqj4":"Current Target"}},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1}]}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}]}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_spider_web_target","required_dist":0},{"type":"/datum/bt_node/ai_behavior/spin_web","action_key":"BB_spider_web_action","target_key":"BB_spider_web_target"}]}],"observer_abort":3,"key":"BB_spider_web_target"}]},{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list/nearest","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/ai_behavior/find_unwebbed_turf","target_key":"BB_spider_web_target"}]},{"type":"/datum/bt_node/subtree/random_speech_loop"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/space_fauna/statue/stares_at_people.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/space_fauna/statue/stares_at_people.bt.compiled.json new file mode 100644 index 00000000000..3cc7fd46b43 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/space_fauna/statue/stares_at_people.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/face_target_or_face_initial","target_key":"Current Target"}],"observer_abort":2,"key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/wait"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/modules/mob/living/basic/space_fauna/statue/statue.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/space_fauna/statue/statue.bt.compiled.json new file mode 100644 index 00000000000..daa9321f79c --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/space_fauna/statue/statue.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_low_priority_hunting_target","movement_failed":false},{"type":"/datum/bt_node/ai_behavior/hunt_target/interact_with_target","target_key":"BB_low_priority_hunting_target","hunt_cooldown":100,"always_reset_target":true}]}],"observer_abort":3,"key":"BB_low_priority_hunting_target"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"}],"observer_abort":3,"invert":true,"key":"BB_low_priority_hunting_target"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_low_priority_hunting_target","target_source":"/datum/target_source/oview_typed/from_bb_key/hunt_target_list","targeting_strategy":"/datum/targeting_strategy/unbroken_light","vision_range":7}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/space_fauna/statue/suspicious_mannequin.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/space_fauna/statue/suspicious_mannequin.bt.compiled.json new file mode 100644 index 00000000000..39d4a08e4ed --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/space_fauna/statue/suspicious_mannequin.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity/pacifist"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/ai_behavior/wait"},{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"success_policy":1,"failure_policy":1}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/wait"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location","vision_range":14},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/decorator/random_chance","children":[{"type":"/datum/bt_node/ai_behavior/perform_emote","emote":"scream"}],"chance":0.05}],"observer_abort":0,"key":"Current Target"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/space_fauna/supermatter_spider.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/space_fauna/supermatter_spider.bt.compiled.json new file mode 100644 index 00000000000..1b78b4ebaf4 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/space_fauna/supermatter_spider.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/subtree/simple_hostile_obstacles_combat","bindings":{"b95z0f0c":"/datum/bt_node/subtree/random_speech_loop"}} diff --git a/build/behavior_trees/modules/mob/living/basic/space_fauna/wumborian_fugu/wumborian_fugu.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/space_fauna/wumborian_fugu/wumborian_fugu.bt.compiled.json new file mode 100644 index 00000000000..6c6daa9752a --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/space_fauna/wumborian_fugu/wumborian_fugu.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/targeted_mob_ability","ability_key":"BB_fugu_inflate","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/attack_obstructions/attack_turfs","target_key":"Current Target"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/subtree/run_away_from_target"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/stoats/stoat.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/stoats/stoat.bt.compiled.json new file mode 100644 index 00000000000..8e3ac45c42c --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/stoats/stoat.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree","override_id":"pet_command"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"success_policy":0,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"key":"Current Target","observer_abort":1},{"type":"/datum/bt_node/subtree/move_to_and_eat"},{"type":"/datum/bt_node/subtree/make_babies"},{"type":"/datum/bt_node/decorator/is_dragging","children":[{"type":"/datum/bt_node/subtree/run_away_from_target","bindings":{"byk9gqj4":"BB_LAST_STOLEN_ITEM"}}],"observer_abort":1},{"type":"/datum/bt_node/subtree/steal_and_flee"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/subtree/random_speech_loop"},{"type":"/datum/bt_node/subtree/find_stealable_object"},{"type":"/datum/bt_node/subtree/find_food"},{"type":"/datum/bt_node/subtree/find_partner"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/trader/trader.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/trader/trader.bt.compiled.json new file mode 100644 index 00000000000..84e0bf02a83 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/trader/trader.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity/pacifist"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_ranged_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location","avoid_friendly_fire":true,"time_between_perform":30}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_equals","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_first_customer","required_dist":1}],"key":"BB_rush_to_sell","value":true},{"type":"/datum/bt_node/ai_behavior/succeed"}]},{"type":"/datum/bt_node/ai_behavior/setup_shop"}]}],"observer_abort":2,"key":"BB_first_customer"}],"observer_abort":0,"invert":true,"key":"BB_shop_spot"},{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/idle_random_walk/not_while_on_target","target_key":"BB_shop_spot"}],"success_policy":1,"failure_policy":0,"loop_delay":15.0}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/ai_behavior/acquire_target","target_key":"BB_first_customer","target_source":"/datum/target_source/oview","targeting_strategy":"/datum/targeting_strategy/conscious_human","revalidation_mode":1}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/tree.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/tree.bt.compiled.json new file mode 100644 index 00000000000..1b78b4ebaf4 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/tree.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/subtree/simple_hostile_obstacles_combat","bindings":{"b95z0f0c":"/datum/bt_node/subtree/random_speech_loop"}} diff --git a/build/behavior_trees/modules/mob/living/basic/trooper/burst.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/trooper/burst.bt.compiled.json new file mode 100644 index 00000000000..a2f6923b6f4 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/trooper/burst.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/subtree/trooper_ranged","bindings":{"b4kar3zl":30,"bnr1aazo":1}} diff --git a/build/behavior_trees/modules/mob/living/basic/trooper/peaceful.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/trooper/peaceful.bt.compiled.json new file mode 100644 index 00000000000..1f531427f00 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/trooper/peaceful.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/decorator/bb_key_equals","children":[{"type":"/datum/bt_node/decorator/cooldown","children":[{"type":"/datum/bt_node/ai_behavior/call_reinforcements","target_key":"Call reinforcements target"}],"observer_abort":2,"cooldown_key":"BB_basic_mob_reinforcements_cooldown","cooldown_duration":300,"lock_on_succeed":true}],"key":"BB_calls_reinforcements","value":true}],"observer_abort":2,"key":"Call reinforcements target"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/attack_obstructions","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/move_to_reinforce"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Call reinforcements target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/trooper/peaceful_burst.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/trooper/peaceful_burst.bt.compiled.json new file mode 100644 index 00000000000..52da7f12e76 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/trooper/peaceful_burst.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/decorator/bb_key_equals","children":[{"type":"/datum/bt_node/decorator/cooldown","children":[{"type":"/datum/bt_node/ai_behavior/call_reinforcements","target_key":"Call reinforcements target"}],"observer_abort":2,"cooldown_key":"BB_basic_mob_reinforcements_cooldown","cooldown_duration":300,"lock_on_succeed":true}],"key":"BB_calls_reinforcements","value":true}],"observer_abort":2,"key":"Call reinforcements target"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/attack_obstructions","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/basic_ranged_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location","time_between_perform":30,"avoid_friendly_fire":true}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/move_to_reinforce"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Call reinforcements target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/trooper/ranged.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/trooper/ranged.bt.compiled.json new file mode 100644 index 00000000000..eb5c181ecd6 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/trooper/ranged.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/subtree/trooper_ranged","bindings":{"b4kar3zl":10,"beslksyc":5,"bnr1aazo":1}} diff --git a/build/behavior_trees/modules/mob/living/basic/trooper/shotgunner.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/trooper/shotgunner.bt.compiled.json new file mode 100644 index 00000000000..dbcb9251268 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/trooper/shotgunner.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/subtree/trooper_ranged","bindings":{"b4kar3zl":30,"beslksyc":3,"bnr1aazo":1}} diff --git a/build/behavior_trees/modules/mob/living/basic/trooper/trooper.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/trooper/trooper.bt.compiled.json new file mode 100644 index 00000000000..644c7eefb55 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/trooper/trooper.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_equals","children":[{"type":"/datum/bt_node/decorator/cooldown","children":[{"type":"/datum/bt_node/ai_behavior/call_reinforcements"}],"observer_abort":2,"cooldown_key":"BB_basic_mob_reinforcements_cooldown","cooldown_duration":300,"lock_on_succeed":true}],"key":"BB_calls_reinforcements","value":true},{"type":"/datum/bt_node/ai_behavior/attack_obstructions","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/move_to_reinforce"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/trooper/trooper_ranged.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/trooper/trooper_ranged.bt.compiled.json new file mode 100644 index 00000000000..e60f01c3b79 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/trooper/trooper_ranged.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_equals","children":[{"type":"/datum/bt_node/decorator/cooldown","children":[{"type":"/datum/bt_node/ai_behavior/call_reinforcements"}],"observer_abort":2,"cooldown_key":"BB_basic_mob_reinforcements_cooldown","cooldown_duration":300,"lock_on_succeed":true}],"key":"BB_calls_reinforcements","value":true},{"type":"/datum/bt_node/ai_behavior/attack_obstructions","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/basic_ranged_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location","time_between_perform":"$b4kar3zl","max_range":"$beslksyc","avoid_friendly_fire":"$bnr1aazo"},{"type":"/datum/bt_node/ai_behavior/succeed"}]}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/maintain_distance","target_key":"Current Target"}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/move_to_reinforce"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}],"__bindings":{"b4kar3zl":{"label":"time_between_perform","default":0},"beslksyc":{"label":"max_range","default":3},"bnr1aazo":{"label":"avoid_friendly_fire","default":0}}} diff --git a/build/behavior_trees/modules/mob/living/basic/turtle/turtle.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/turtle/turtle.bt.compiled.json new file mode 100644 index 00000000000..2a5a1630c16 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/turtle/turtle.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/subtree/find_food"},{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/ability_available","children":[{"type":"/datum/bt_node/ai_behavior/use_mob_ability","ability_key":"BB_generic_action"}],"observer_abort":2,"ability_key":"BB_generic_action"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"turtle_headbutt_victim","required_dist":1},{"type":"/datum/bt_node/ai_behavior/set_bb_cooldown","cooldown_key":"turtle_headbutt_cooldown","cooldown_duration":600},{"type":"/datum/bt_node/ai_behavior/hunt_target/headbutt_leg","target_key":"turtle_headbutt_victim","always_reset_target":true}]}],"observer_abort":2,"key":"turtle_headbutt_victim"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"turtle_flora_target","required_dist":1},{"type":"/datum/bt_node/ai_behavior/set_bb_cooldown","cooldown_key":"turtle_flora_cooldown","cooldown_duration":600},{"type":"/datum/bt_node/ai_behavior/hunt_target/sniff_flora","target_key":"turtle_flora_target","always_reset_target":true}]}],"observer_abort":2,"key":"turtle_headbutt_victim"},{"type":"/datum/bt_node/subtree/move_to_and_eat"},{"type":"/datum/bt_node/subtree/random_walk","bindings":{"bf07i8ep":10}}]},{"type":"/datum/bt_node/decorator/key_off_cooldown","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"turtle_headbutt_victim","target_source":"/datum/target_source/oview_typed/from_bb_key/turtle_headbutt_types","targeting_strategy":"/datum/targeting_strategy/legged_conscious_human"}],"cooldown_key":"turtle_headbutt_cooldown"},{"type":"/datum/bt_node/decorator/key_off_cooldown","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"turtle_flora_target","target_source":"/datum/target_source/oview_typed/from_bb_key/turtle_flora_types","targeting_strategy":"/datum/targeting_strategy/sniffable_hydro"}],"cooldown_key":"turtle_flora_cooldown"},{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/express_happiness"}],"success_policy":1,"failure_policy":1,"loop_delay":10}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/vermin/axolotl.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/vermin/axolotl.bt.compiled.json new file mode 100644 index 00000000000..9dbb6fb090f --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/vermin/axolotl.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/subtree/random_walk"} diff --git a/build/behavior_trees/modules/mob/living/basic/vermin/butterfly.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/vermin/butterfly.bt.compiled.json new file mode 100644 index 00000000000..9dbb6fb090f --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/vermin/butterfly.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/subtree/random_walk"} diff --git a/build/behavior_trees/modules/mob/living/basic/vermin/cockroach/cockroach.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/vermin/cockroach/cockroach.bt.compiled.json new file mode 100644 index 00000000000..49087f9c345 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/vermin/cockroach/cockroach.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_low_priority_hunting_target","required_dist":1},{"type":"/datum/bt_node/ai_behavior/hunt_target","target_key":"BB_low_priority_hunting_target","cooldown_key":"BB_roach_hunt_cooldown","hunt_cooldown":50,"always_reset_target":true}]}],"key":"BB_low_priority_hunting_target","observer_abort":3},{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/idle_random_walk"}],"success_policy":1,"failure_policy":0,"loop_delay":15.0}]},{"type":"/datum/bt_node/decorator/key_off_cooldown","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_low_priority_hunting_target","target_source":"/datum/target_source/oview_single_type/ants","targeting_strategy":"/datum/targeting_strategy/huntable","vision_range":2}],"cooldown_key":"BB_roach_hunt_cooldown"},{"type":"/datum/bt_node/subtree/random_speech_loop"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/modules/mob/living/basic/vermin/cockroach/cockroach_aggro.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/vermin/cockroach/cockroach_aggro.bt.compiled.json new file mode 100644 index 00000000000..509de755c2e --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/vermin/cockroach/cockroach_aggro.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/subtree","override_id":"pet_command"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"key":"Current Target","observer_abort":3},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_low_priority_hunting_target","required_dist":1},{"type":"/datum/bt_node/ai_behavior/hunt_target","target_key":"BB_low_priority_hunting_target","cooldown_key":"BB_roach_hunt_cooldown","hunt_cooldown":50,"always_reset_target":true}]}],"key":"BB_low_priority_hunting_target","observer_abort":3},{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/idle_random_walk"}],"success_policy":1,"failure_policy":0,"loop_delay":15.0}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/decorator/key_off_cooldown","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_low_priority_hunting_target","target_source":"/datum/target_source/oview_single_type/ants","targeting_strategy":"/datum/targeting_strategy/huntable","vision_range":2}],"cooldown_key":"BB_roach_hunt_cooldown"},{"type":"/datum/bt_node/subtree/random_speech_loop"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/vermin/cockroach/cockroach_glockroach.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/vermin/cockroach/cockroach_glockroach.bt.compiled.json new file mode 100644 index 00000000000..458fb780ce9 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/vermin/cockroach/cockroach_glockroach.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/subtree","override_id":"pet_command"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_ranged_attack/glockroach","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/maintain_distance","target_key":"Current Target"}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"key":"Current Target","observer_abort":3},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_low_priority_hunting_target","required_dist":1},{"type":"/datum/bt_node/ai_behavior/hunt_target","target_key":"BB_low_priority_hunting_target","cooldown_key":"BB_roach_hunt_cooldown","hunt_cooldown":50,"always_reset_target":true}]}],"key":"BB_low_priority_hunting_target","observer_abort":3},{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/idle_random_walk"}],"success_policy":1,"failure_policy":0,"loop_delay":15.0}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/decorator/key_off_cooldown","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_low_priority_hunting_target","target_source":"/datum/target_source/oview_single_type/ants","targeting_strategy":"/datum/targeting_strategy/huntable","vision_range":2}],"cooldown_key":"BB_roach_hunt_cooldown"},{"type":"/datum/bt_node/subtree/random_speech_loop"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/vermin/cockroach/cockroach_mobroach.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/vermin/cockroach/cockroach_mobroach.bt.compiled.json new file mode 100644 index 00000000000..d3b96084f49 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/vermin/cockroach/cockroach_mobroach.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/subtree","override_id":"pet_command"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_ranged_attack/mobroach","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/maintain_distance","target_key":"Current Target"}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"key":"Current Target","observer_abort":3},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_low_priority_hunting_target","required_dist":1},{"type":"/datum/bt_node/ai_behavior/hunt_target","target_key":"BB_low_priority_hunting_target","cooldown_key":"BB_roach_hunt_cooldown","hunt_cooldown":50,"always_reset_target":true}]}],"key":"BB_low_priority_hunting_target","observer_abort":3},{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/idle_random_walk"}],"success_policy":1,"failure_policy":0,"loop_delay":15.0}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/decorator/key_off_cooldown","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_low_priority_hunting_target","target_source":"/datum/target_source/oview_single_type/ants","targeting_strategy":"/datum/targeting_strategy/huntable","vision_range":2}],"cooldown_key":"BB_roach_hunt_cooldown"},{"type":"/datum/bt_node/subtree/random_speech_loop"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/vermin/crab.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/vermin/crab.bt.compiled.json new file mode 100644 index 00000000000..025b68b9301 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/vermin/crab.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/subtree/run_away_from_target","bindings":{"byk9gqj4":"BB_basic_flee_target"}},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/go_for_swim"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list/nearest","target_key":"BB_basic_flee_target","targeting_strategy":"flee_targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"key":"BB_basic_stop_fleeing","invert":true},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/decorator/key_off_cooldown","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"swim_alternate_turf","targeting_strategy":"/datum/targeting_strategy/walkable_turf","target_source":"/datum/target_source/oview_water_turfs"}],"cooldown_key":"key_swimmer_cooldown"},{"type":"/datum/bt_node/subtree/random_speech_loop","bindings":{"bqdqne64":"/datum/bt_node/ai_behavior/random_speech/crab"}}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/modules/mob/living/basic/vermin/eat_cable.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/vermin/eat_cable.bt.compiled.json new file mode 100644 index 00000000000..74aa222412f --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/vermin/eat_cable.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_low_priority_hunting_target","required_dist":0},{"type":"/datum/bt_node/ai_behavior/clear_key","key":"BB_mouse_wants_to_eat_cable"},{"type":"/datum/bt_node/ai_behavior/hunt_target/interact_with_target","target_key":"BB_low_priority_hunting_target","cooldown_key":"BB_mouse_cable_hunt_cooldown","hunt_cooldown":200}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_low_priority_hunting_target","target_source":"/datum/target_source/oview_single_type/cable","targeting_strategy":"/datum/targeting_strategy/accessible_cable","vision_range":0}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}],"observer_abort":3,"key":"BB_low_priority_hunting_target"} diff --git a/build/behavior_trees/modules/mob/living/basic/vermin/eat_cheese.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/vermin/eat_cheese.bt.compiled.json new file mode 100644 index 00000000000..235e587e3b9 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/vermin/eat_cheese.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_current_hunting_target","required_dist":1},{"type":"/datum/bt_node/ai_behavior/hunt_target/interact_with_target","target_key":"BB_current_hunting_target","cooldown_key":"BB_mouse_cheese_hunt_cooldown","hunt_cooldown":200}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_current_hunting_target","targeting_strategy":"/datum/targeting_strategy/pickup_item","target_source":"/datum/target_source/oview_single_type/cheese","vision_range":1}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}],"observer_abort":3,"key":"BB_current_hunting_target"} diff --git a/build/behavior_trees/modules/mob/living/basic/vermin/frog.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/vermin/frog.bt.compiled.json new file mode 100644 index 00000000000..4f860c1f6df --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/vermin/frog.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/frog_engage_target"},{"type":"/datum/bt_node/subtree/go_for_swim"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/decorator/key_off_cooldown","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"swim_alternate_turf","targeting_strategy":"/datum/targeting_strategy/walkable_turf","target_source":"/datum/target_source/oview_water_turfs"}],"cooldown_key":"key_swimmer_cooldown"},{"type":"/datum/bt_node/subtree/random_speech_loop","bindings":{"bqdqne64":"/datum/bt_node/ai_behavior/random_speech/frog"}}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/modules/mob/living/basic/vermin/frog_engage_target.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/vermin/frog_engage_target.bt.compiled.json new file mode 100644 index 00000000000..3df4a0ae111 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/vermin/frog_engage_target.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/target_has_trait","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/find_flee_location","target_key":"Current Target","hiding_location_key":"Current Target Hiding Location","destination_key":"BB_flee_location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_flee_location","required_dist":0,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"key":"Current Target","trait":"scary_fisherman"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":true}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}]}],"observer_abort":3,"key":"Current Target"} diff --git a/build/behavior_trees/modules/mob/living/basic/vermin/lizard.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/vermin/lizard.bt.compiled.json new file mode 100644 index 00000000000..3231de256ac --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/vermin/lizard.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/move_to_and_eat"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/subtree/find_food"},{"type":"/datum/bt_node/subtree/random_speech_loop","bindings":{"bqdqne64":"/datum/bt_node/ai_behavior/random_speech/lizard"}}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/modules/mob/living/basic/vermin/mothroach/mothroach.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/vermin/mothroach/mothroach.bt.compiled.json new file mode 100644 index 00000000000..426dcea2127 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/vermin/mothroach/mothroach.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/override_id_set","children":[{"type":"/datum/bt_node/subtree","override_id":"pet_command"}],"observer_abort":2,"override_id":"pet_command"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/subtree/run_away_from_target"}],"key":"Current Target","observer_abort":1},{"type":"/datum/bt_node/subtree/move_to_and_eat"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list/nearest","target_key":"Current Target","targeting_strategy":"flee_targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/subtree/find_food"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true},{"type":"/datum/bt_node/ai_behavior/random_speech/mothroach"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/vermin/mouse.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/vermin/mouse.bt.compiled.json new file mode 100644 index 00000000000..ea00144350e --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/vermin/mouse.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/override_id_set","children":[{"type":"/datum/bt_node/subtree","override_id":"pet_command"}],"observer_abort":2,"override_id":"pet_command"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/eat_cheese"},{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/subtree/run_away_from_target"},{"type":"/datum/bt_node/decorator/key_off_cooldown","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_current_hunting_target","target_source":"/datum/target_source/oview_single_type/cheese","targeting_strategy":"/datum/targeting_strategy/anything","vision_range":1}],"cooldown_key":"BB_mouse_cheese_hunt_cooldown"},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets/nearest","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/play_instrument_on_floor"},{"type":"/datum/bt_node/subtree/eat_cable"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/subtree/random_walk"},{"type":"/datum/bt_node/decorator/key_off_cooldown","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_current_hunting_target","target_source":"/datum/target_source/oview_single_type/cheese","targeting_strategy":"/datum/targeting_strategy/anything","vision_range":1}],"cooldown_key":"BB_mouse_cheese_hunt_cooldown"},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets/nearest","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_SONG_INSTRUMENT","target_source":"/datum/target_source/oview_single_type/piano_synth","targeting_strategy":"/datum/targeting_strategy/playable_synthesizer"},{"type":"/datum/bt_node/decorator/bb_key_true","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_low_priority_hunting_target","target_source":"/datum/target_source/oview_single_type/cable","targeting_strategy":"/datum/targeting_strategy/accessible_cable","vision_range":0}],"key":"BB_mouse_wants_to_eat_cable"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]},{"type":"/datum/bt_node/subtree/random_speech_loop","bindings":{"bqdqne64":"/datum/bt_node/ai_behavior/random_speech/mouse"}},{"type":"/datum/bt_node/decorator/key_off_cooldown","children":[{"type":"/datum/bt_node/decorator/random_chance","children":[{"type":"/datum/bt_node/ai_behavior/set_bb_key","target_key":"BB_mouse_wants_to_eat_cable","value":true}],"chance":0.01}],"cooldown_key":"BB_mouse_cable_hunt_cooldown"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/vermin/mouse_rat.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/vermin/mouse_rat.bt.compiled.json new file mode 100644 index 00000000000..b74136eee82 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/vermin/mouse_rat.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/decorator/override_id_set","children":[{"type":"/datum/bt_node/subtree","override_id":"pet_command"}],"observer_abort":2,"override_id":"pet_command"},{"type":"/datum/bt_node/composite/parallel","children":[],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/ai_behavior/attack_obstructions","target_key":"Current Target"},{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}]}],"success_policy":0,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/eat_cheese"},{"type":"/datum/bt_node/subtree/eat_cable"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"},{"type":"/datum/bt_node/decorator/key_off_cooldown","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_current_hunting_target","target_source":"/datum/target_source/oview_single_type/cheese","targeting_strategy":"/datum/targeting_strategy/anything","vision_range":1}],"cooldown_key":"BB_mouse_cheese_hunt_cooldown"},{"type":"/datum/bt_node/decorator/bb_key_true","children":[{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_low_priority_hunting_target","target_source":"/datum/target_source/oview_single_type/cable","targeting_strategy":"/datum/targeting_strategy/accessible_cable","vision_range":0}],"key":"BB_mouse_wants_to_eat_cable"},{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/random_speech_loop","bindings":{"bqdqne64":"/datum/bt_node/ai_behavior/random_speech/mouse"}},{"type":"/datum/bt_node/decorator/key_off_cooldown","children":[{"type":"/datum/bt_node/decorator/random_chance","children":[{"type":"/datum/bt_node/ai_behavior/set_bb_key","target_key":"BB_mouse_wants_to_eat_cable","value":true}],"chance":0.01}],"cooldown_key":"BB_mouse_cable_hunt_cooldown"}]}],"success_policy":0,"failure_policy":0,"loop_delay":10}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/build/behavior_trees/modules/mob/living/basic/vermin/play_instrument_on_floor.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/vermin/play_instrument_on_floor.bt.compiled.json new file mode 100644 index 00000000000..04e34b375de --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/vermin/play_instrument_on_floor.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/is_at_distance","children":[{"type":"/datum/bt_node/ai_behavior/keep_playing_instrument","song_instrument_key":"BB_SONG_INSTRUMENT"}],"observer_abort":1,"maximum_distance":1,"require_reach":true},{"type":"/datum/bt_node/composite/sequence","children":[{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"BB_SONG_INSTRUMENT","required_dist":1},{"type":"/datum/bt_node/ai_behavior/setup_instrument","song_instrument_key":"BB_SONG_INSTRUMENT","song_lines_key":"song_lines"},{"type":"/datum/bt_node/ai_behavior/play_instrument","song_instrument_key":"BB_SONG_INSTRUMENT","volume":50}]}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_interaction_target","target_key":"BB_SONG_INSTRUMENT","target_source":"/datum/target_source/oview_single_type/piano_synth","targeting_strategy":"/datum/targeting_strategy/playable_synthesizer"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}],"observer_abort":3,"key":"BB_SONG_INSTRUMENT"} diff --git a/build/behavior_trees/modules/mob/living/basic/vermin/space_bat.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/vermin/space_bat.bt.compiled.json new file mode 100644 index 00000000000..d94250485b8 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/vermin/space_bat.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/subtree/simple_retaliate_combat"} diff --git a/build/behavior_trees/modules/mob/living/basic/vermin/suicide_frog.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/vermin/suicide_frog.bt.compiled.json new file mode 100644 index 00000000000..232470affa5 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/vermin/suicide_frog.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/decorator/bb_key_set","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/subplan","children":[{"type":"/datum/bt_node/ai_behavior/basic_melee_attack","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"success_policy":1,"failure_policy":1},{"type":"/datum/bt_node/ai_behavior/move_to_target","target_key":"Current Target","required_dist":1,"finish_on_arrival":false}],"failure_policy":1,"success_policy":0,"repeat_secondary":true,"finish_on_primary":true}],"observer_abort":3,"key":"Current Target"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets/prioritize_trait","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true} diff --git a/build/behavior_trees/modules/mob/living/basic/vermin/trash.bt.compiled.json b/build/behavior_trees/modules/mob/living/basic/vermin/trash.bt.compiled.json new file mode 100644 index 00000000000..f663b3d4ce7 --- /dev/null +++ b/build/behavior_trees/modules/mob/living/basic/vermin/trash.bt.compiled.json @@ -0,0 +1 @@ +{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/escape_captivity"},{"type":"/datum/bt_node/subtree","override_id":"pet_command"},{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/parallel","children":[{"type":"/datum/bt_node/composite/selector","children":[{"type":"/datum/bt_node/subtree/frog_engage_target"},{"type":"/datum/bt_node/subtree/random_walk"}]},{"type":"/datum/bt_node/ai_behavior/random_speech/frog"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true},{"type":"/datum/bt_node/ai_behavior/acquire_target/update_combat_targets","target_key":"Current Target","targeting_strategy":"targeting_strategy","hiding_location_key":"Current Target Hiding Location"}],"failure_policy":0,"success_policy":0,"repeat_secondary":true,"repeat_secondary_delay":10,"finish_on_primary":true}]} diff --git a/code/__DEFINES/MC.dm b/code/__DEFINES/MC.dm index ec1a3ebbb13..d7337ed6695 100644 --- a/code/__DEFINES/MC.dm +++ b/code/__DEFINES/MC.dm @@ -144,10 +144,3 @@ /datum/controller/subsystem/ai_controllers/##X/fire() {..() /*just so it shows up on the profiler*/} \ /datum/controller/subsystem/ai_controllers/##X -#define UNPLANNED_CONTROLLER_SUBSYSTEM_DEF(X) GLOBAL_REAL(SS##X, /datum/controller/subsystem/unplanned_controllers/##X);\ -/datum/controller/subsystem/unplanned_controllers/##X/New(){\ - NEW_SS_GLOBAL(SS##X);\ - PreInit();\ -}\ -/datum/controller/subsystem/unplanned_controllers/##X/fire() {..() /*just so it shows up on the profiler*/} \ -/datum/controller/subsystem/unplanned_controllers/##X diff --git a/code/__DEFINES/ai/ai.dm b/code/__DEFINES/ai/ai.dm index a6724aeb4aa..7711efb42d0 100644 --- a/code/__DEFINES/ai/ai.dm +++ b/code/__DEFINES/ai/ai.dm @@ -1,16 +1,36 @@ -#define GET_AI_BEHAVIOR(behavior_type) SSai_behaviors.ai_behaviors[behavior_type] -#define GET_TARGETING_STRATEGY(targeting_type) SSai_behaviors.targeting_strategies[targeting_type] -#define GET_TARGET_PRIORITY_STRATEGY(targeting_type) SSai_behaviors.target_priority_strategies[targeting_type] +#define GET_TARGETING_STRATEGY(targeting_type) SSai_controllers.targeting_strategies[targeting_type] +#define GET_TARGET_PRIORITY_STRATEGY(targeting_type) SSai_controllers.target_priority_strategies[targeting_type] +#define GET_TARGET_SOURCE(source_type) SSai_controllers.target_sources[source_type] + +/** + * Returns TRUE if the target should be rejected based on factions. + * Inlined faction check for targeting strategies but with less proc overhead. + * Strategies with fully custom faction logic set custom_faction_check and override faction_check() instead. + */ +#define TARGETING_FACTION_CHECK(strategy, controller, living_mob, the_target) \ + (((strategy).ignore_faction || (controller).blackboard[BB_ALWAYS_IGNORE_FACTION] || (controller).blackboard[BB_TEMPORARILY_IGNORE_FACTION]) \ + ? (strategy).invert_faction_check \ + : ((living_mob).faction_check_atom((the_target), exact_match = (strategy).check_factions_exactly) \ + ? !(strategy).invert_faction_check \ + : (strategy).invert_faction_check)) + +// Revalidation modes for /datum/bt_node/ai_behavior/acquire_target +/// If a target is already set, validate it via is_valid_target before searching. Replace if invalid. +#define TARGET_REVALIDATE 1 +/// If a target is already set, return SUCCESS immediately without re-checking. +#define TARGET_KEEP_IF_SET 2 +/// Always run the full candidate search, ignoring any existing target. +#define TARGET_ALWAYS_SEARCH 3 #define HAS_AI_CONTROLLER_TYPE(thing, type) istype(thing?.ai_controller, type) //AI controller flags //If you add a new status, be sure to add it to the ai_controllers subsystem's ai_controllers_by_status list. -///The AI is currently active. +///The AI is currently active, and is planned by the high priority subsystem. #define AI_STATUS_ON "ai_on" -///The AI is currently offline for any reason. +///The AI is currently active, but is planned by the low priority (background) subsystem. +#define AI_STATUS_ON_LOW "ai_on_low" +///The AI is not running. Cancels any active plans if set. #define AI_STATUS_OFF "ai_off" -///The AI is currently in idle mode. -#define AI_STATUS_IDLE "ai_idle" //Flags returned by get_able_to_run() ///pauses AI processing @@ -43,16 +63,6 @@ #define AI_BEHAVIOR_INSTANT (NONE) -///Does this task require movement from the AI before it can be performed? -#define AI_BEHAVIOR_REQUIRE_MOVEMENT (1<<0) -///Does this require the current_movement_target to be adjacent and in reach? -#define AI_BEHAVIOR_REQUIRE_REACH (1<<1) -///Does this task let you perform the action while you move closer? (Things like moving and shooting) -#define AI_BEHAVIOR_MOVE_AND_PERFORM (1<<2) -///Does finishing this task not null the current movement target? -#define AI_BEHAVIOR_KEEP_MOVE_TARGET_ON_FINISH (1<<3) -///Does this behavior NOT block planning? -#define AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION (1<<4) ///AI flags /// Don't move if being pulled @@ -65,19 +75,16 @@ #define CAN_ACT_IN_STASIS (1<<3) /// Continue processing while aggressively grabbed #define CAN_ACT_WHILE_GRABBED (1<<4) +/// Keeps planning on low prio when unwatched +#define RUN_WHILE_UNWATCHED (1<<5) +/// Always plans at high priority. Only works with RUN_WHILE_UNWATCHED +#define ALWAYS_HIGH_PRIORITY (1<<6) /// Flags we expect for most AI controllers #define DEFAULT_AI_FLAGS (PAUSE_DURING_DO_AFTER | CAN_ACT_WHILE_GRABBED) /// Flags for passive mobs that are easy to push around #define PASSIVE_AI_FLAGS (PAUSE_DURING_DO_AFTER | STOP_MOVING_WHEN_PULLED) -//Base Subtree defines - -///This subtree should cancel any further planning, (Including from other subtrees) -#define SUBTREE_RETURN_FINISH_PLANNING 1 - -//Generic subtree defines - /// default search range (tiles, passed to oview) when using find_and_set #define SEARCH_TACTIC_DEFAULT_RANGE 7 /// probability that the pawn should try resisting out of restraints @@ -97,3 +104,7 @@ GLOBAL_LIST_INIT(all_radial_directions, list( "WEST" = image(icon = 'icons/testing/turf_analysis.dmi', icon_state = "red_arrow", dir = WEST), "NORTHWEST" = image(icon = 'icons/testing/turf_analysis.dmi', icon_state = "red_arrow", dir = NORTHWEST) )) + + +///Use this if you dont want a controller to show up in the sidebar (e.g. when its a class that just sets BB keys) +#define ABSTRACT_AI_CLASS "Abstract" diff --git a/code/__DEFINES/ai/ai_blackboard.dm b/code/__DEFINES/ai/ai_blackboard.dm index 21ed0c47aa1..ec074de5c2c 100644 --- a/code/__DEFINES/ai/ai_blackboard.dm +++ b/code/__DEFINES/ai/ai_blackboard.dm @@ -1,5 +1,22 @@ //Generic BB keys +///Use this if you need a generic variable for a target; Use this if you don't have multiple different things to target in your ai (PROTIP: YOU BASICALLY NEVER DO!) +#define BB_CURRENT_TARGET "Current Target" +///Use this if you need a generic variable for a hiding location; +#define BB_CURRENT_TARGET_HIDING_LOCATION "Current Target Hiding Location" +///For any battle screech cooldowns +#define BB_BATTLE_SCREECH_COOLDOWN "Battle Screech Cooldown" +///Target for snitching (calling reinforcements on) but dont want to atatck. +#define BB_CALL_REINFORCEMENTS_TARGET "Call reinforcements target" +///Generic target for hunting +#define BB_HUNT_TARGET_LIST "Hunt Target List" +///Target of current movement.alist +#define BB_CURRENT_MOVEMENT_TARGET "Current target movement" +///The pawn controlled by this controller +#define BB_MY_PAWN "Literally me" + +///Cooldown on venting (sus) +#define BB_VENTING_COOLDOWN "Venting Cooldown" #define BB_CURRENT_MIN_MOVE_DISTANCE "min_move_distance" ///time until we should next eat, set by the generic hunger subtree @@ -30,13 +47,17 @@ #define BB_GUILTY_CONSCIOUS_CHANCE "guilty_concious_rate" ///the item we will steal #define BB_ITEM_TO_STEAL "item_to_steal" +///be stoat do crime +#define BB_WANTS_TO_COMMIT_THEFT "BB_WANTS_TO_COMMIT_THEFT" +///last stolen item +#define BB_LAST_STOLEN_ITEM "BB_LAST_STOLEN_ITEM" ///the owner we will try to play with #define BB_OWNER_TARGET "BB_owner_target" ///the list of interactions we can have with the owner #define BB_INTERACTIONS_WITH_OWNER "BB_interactions_with_owner" -///The trait checked by ai_behavior/find_potential_targets/prioritize_trait to return a target with a trait over the rest. +///The trait checked by ai_behavior/update_targets/prioritize_trait to return a target with a trait over the rest. #define BB_TARGET_PRIORITY_TRAIT "target_priority_trait" /// Store a single or list of emotes at this key @@ -48,6 +69,8 @@ #define BB_REINFORCEMENTS_SAY "BB_reinforcements_say" /// Something the mob will remote when calling reinforcements #define BB_REINFORCEMENTS_EMOTE "BB_reinforcements_emote" +/// Does this mob call reinforcements? +#define BB_CALLS_REINFORCEMENTS "BB_calls_reinforcements" ///Turf we want a mob to move to #define BB_TRAVEL_DESTINATION "BB_travel_destination" @@ -56,6 +79,8 @@ #define BB_SONG_INSTRUMENT "BB_SONG_INSTRUMENT" ///song lines blackboard, set by default on controllers #define BB_SONG_LINES "song_lines" +///mob doesn't use hand code so we're assuming its instrument is up its ass +#define BB_INSTRUMENT_UP_ASS "Has its instrument up its ass" ///bane ai used by example script #define BB_BANE_BATMAN "BB_bane_batman" @@ -66,6 +91,12 @@ /// Are we a panicking goose? #define BB_GOOSE_VOMIT_CHANCE "BB_goose_vomit_chance" +/// Set TRUE to suppress a mob's idle wandering (e.g. while under external/deadchat control). +#define BB_DISABLE_IDLE "BB_disable_idle" + +/// Set TRUE on a mob's controller by /datum/component/tameable once it has been tamed. +#define BB_TAMED "BB_tamed" + //Hunting BB keys ///key that holds our current hunting target #define BB_CURRENT_HUNTING_TARGET "BB_current_hunting_target" @@ -73,6 +104,14 @@ #define BB_LOW_PRIORITY_HUNTING_TARGET "BB_low_priority_hunting_target" ///key that holds the cooldown for our hunting subtree #define BB_HUNTING_COOLDOWN(type) "BB_HUNTING_COOLDOWN_[type]" +///cooldown key for the cockroach's ant hunt +#define BB_ROACH_HUNT_COOLDOWN "BB_roach_hunt_cooldown" +///cooldown key for the mouse's cheese hunt +#define BB_MOUSE_CHEESE_HUNT_COOLDOWN "BB_mouse_cheese_hunt_cooldown" +///cooldown key for the mouse's cable hunt +#define BB_MOUSE_CABLE_HUNT_COOLDOWN "BB_mouse_cable_hunt_cooldown" +///Whether we're hungry for a cable +#define BB_MOUSE_WANTS_TO_EAT_CABLE "BB_mouse_wants_to_eat_cable" ///Basic Mob Keys @@ -81,14 +120,13 @@ /// Key used to store the time we can actually attack #define BB_BASIC_MOB_MELEE_COOLDOWN_TIMER "BB_basic_melee_cooldown_timer" -///Targeting subtrees -#define BB_BASIC_MOB_CURRENT_TARGET "BB_basic_current_target" -#define BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION "BB_basic_current_target_hiding_location" #define BB_TARGETING_STRATEGY "targeting_strategy" #define BB_TARGET_PRIORITY_STRATEGY "target_priority_strategy" #define BB_HUNT_TARGETING_STRATEGY "hunt_targeting_strategy" ///some behaviors that check current_target also set this on deep crit mobs #define BB_BASIC_MOB_EXECUTION_TARGET "BB_basic_execution_target" +/// Target atom written by escape-captivity BT decorators (pawn_buckled_to_obj, pawn_contained_in_obj) +#define BB_BASIC_MOB_ESCAPE_TARGET "BB_basic_mob_escape_target" ///Blackboard key for a whitelist typecache of "things we can target while trying to move" #define BB_OBSTACLE_TARGETING_WHITELIST "BB_targeting_whitelist" /// Key for the minimum status at which we want to target mobs (does not need to be specified if CONSCIOUS) @@ -101,6 +139,9 @@ #define BB_BASIC_MOB_IDLE_WALK_CHANCE "BB_basic_idle_walk_chance" #define BB_BASIC_MOB_TARGET_REFRESH_COOLDOWN "BB_basic_mob_target_refresh_cooldown" +/// whether we can have fuck +#define BB_FUCKS "can we fuck?" + /// Minimum range to keep target within #define BB_RANGED_SKIRMISH_MIN_DISTANCE "BB_ranged_skirmish_min_distance" /// Maximum range to keep target within @@ -115,6 +156,8 @@ #define BB_FLEE_TARGETING_STRATEGY "flee_targeting_strategy" #define BB_BASIC_MOB_FLEE_DISTANCE "BB_basic_flee_distance" #define DEFAULT_BASIC_FLEE_DISTANCE 9 +/// Computed flee destination turf set by find_flee_location and consumed by move_to_target +#define BB_FLEE_LOCATION "BB_flee_location" /// Generic key for a non-specific targeted action #define BB_TARGETED_ACTION "BB_TARGETED_action" @@ -189,7 +232,7 @@ ///are we in some kind of temporary state of ignoring factions when targeting? can result in volatile results if multiple behaviours touch this #define BB_TEMPORARILY_IGNORE_FACTION "BB_temporarily_ignore_factions" -///currently only used by clowns, a list of what can the mob speak randomly +///A list of what can the mob speak randomly #define BB_BASIC_MOB_SPEAK_LINES "BB_speech_lines" #define BB_EMOTE_SAY "emote_say" #define BB_EMOTE_HEAR "emote_hear" @@ -224,6 +267,9 @@ ///key that holds the next time we will start fishing #define BB_FISHING_TIMER "BB_fishing_timer" +///key that holds the next time we will start drilling ice +#define BB_ICE_DRILLING_TIMER "BB_ice_drilling_timer" + ///are we ONLY allowed to fish when we're hungry? #define BB_ONLY_FISH_WHILE_HUNGRY "BB_only_fish_while_hungry" @@ -241,7 +287,7 @@ // Keys used by one and only one behavior // Used to hold state without making bigass lists -/// For /datum/ai_behavior/find_potential_targets, what if any field are we using currently +/// For /datum/ai_behavior/update_targets, what if any field are we using currently #define BB_FIND_TARGETS_FIELD(type) "bb_find_targets_field_[type]" ///Currently enraged diff --git a/code/__DEFINES/ai/behavior_trees.dm b/code/__DEFINES/ai/behavior_trees.dm new file mode 100644 index 00000000000..309c4112ebf --- /dev/null +++ b/code/__DEFINES/ai/behavior_trees.dm @@ -0,0 +1,72 @@ +/// Maximum number of execution indices the bt_viewer draining log can hold between polls. +#define BT_EXECUTION_LOG_MAX 250 + +// BT node return values +/// Node completed its goal and succeeded +#define BT_SUCCESS 1 +/// Node failed for one reason or the other +#define BT_FAILURE 2 +/// Node has an action running; +#define BT_RUNNING 3 + +// Parallel node completion policies (mutually exclusive per axis) +/// Parallel succeeds when child 1 succeeds (default) +#define BT_PARALLEL_SUCCESS_CHILD_ONE 0 +/// Parallel succeeds only when all children succeed +#define BT_PARALLEL_SUCCESS_ALL 1 +/// Parallel fails when child 1 fails (default) +#define BT_PARALLEL_FAILURE_CHILD_ONE 0 +/// Parallel fails when any child fails +#define BT_PARALLEL_FAILURE_ANY 1 + +/// Subplan propagates BT_SUCCESS when all children succeed (default) +#define BT_SUBPLAN_SUCCEED_ON_SUCCESS 0 +/// Subplan resets and loops (returns BT_RUNNING) when all children succeed +#define BT_SUBPLAN_LOOP_ON_SUCCESS 1 +/// Subplan propagates BT_FAILURE when a child fails (default) +#define BT_SUBPLAN_FAIL_ON_FAILURE 0 +/// Subplan resets and loops (returns BT_RUNNING) when a child fails +#define BT_SUBPLAN_LOOP_ON_FAILURE 1 + +/// No observer abort registered +#define BT_ABORT_NONE 0 +/// Abort this branch when the watched condition becomes FALSE +#define BT_ABORT_SELF (1<<0) +/// Abort lower-priority running behaviors when the watched condition becomes TRUE +#define BT_ABORT_LOWER_PRIORITY (1<<1) +/// Both BT_ABORT_SELF and BT_ABORT_LOWER_PRIORITY +#define BT_ABORT_BOTH (BT_ABORT_SELF | BT_ABORT_LOWER_PRIORITY) + +// BT viewer node type identifiers (stored on bt_node.node_type) +/// Selector composite node +#define BT_NODE_SELECTOR 0 +/// Sequence composite node +#define BT_NODE_SEQUENCE 1 +/// Parallel composite node +#define BT_NODE_PARALLEL 2 +/// Decorator (gate/condition) node +#define BT_NODE_DECORATOR 3 +/// Leaf behavior node +#define BT_NODE_LEAF 4 +/// Subtree container node +#define BT_NODE_SUBTREE 5 +/// Subplan composite node +#define BT_NODE_SUBPLAN 6 + +// Strings used to actually fill the JSONs for behavior trees +/// Key storing the node typepath in a descriptor list +#define BT_DESC_TYPE "type" +/// Key storing the children list in a descriptor list +#define BT_DESC_CHILDREN "children" +/// Key storing the override slot ID in a subtree descriptor +#define BT_DESC_OVERRIDE_ID "override_id" +/// Key storing bindable parameter declarations in a compiled subtree descriptor +#define BT_DESC_BINDINGS "__bindings" + +/// Resolves the compiled JSON path from a behavior tree's source JSON path (e.g. "code/datums/ai/dog/dog.bt.json" -> "build/behavior_trees/datums/ai/dog/dog.bt.compiled.json"). +#define BT_COMPILED_PATH(json_path) (replacetext(replacetext(json_path, "code/", "build/behavior_trees/"), ".json", ".compiled.json")) + +// Runtime subtree IDs. Can be used to override trees at runtime + +/// pet_command ID to override based on given pet command +#define SUBPLAN_ID_PET_COMMAND "pet_command" diff --git a/code/__DEFINES/ai/bot_keys.dm b/code/__DEFINES/ai/bot_keys.dm index 653bace09d8..f134f088d29 100644 --- a/code/__DEFINES/ai/bot_keys.dm +++ b/code/__DEFINES/ai/bot_keys.dm @@ -9,6 +9,8 @@ #define BB_SALUTE_MESSAGES "salute_messages" ///the beepsky we will salute #define BB_SALUTE_TARGET "salute_target" +///cooldown key for authority saluting +#define BB_SALUTE_COOLDOWN "salute_cooldown" ///our announcement ability #define BB_ANNOUNCE_ABILITY "announce_ability" ///list of our radio channels @@ -19,8 +21,6 @@ #define BB_BOT_BEACON_COOLDOWN "bot_beacon_cooldown" // medbot keys -///the patient we must heal -#define BB_PATIENT_TARGET "patient_target" ///list holding our wait dialogue #define BB_WAIT_SPEECH "wait_speech" ///what we will say to our patient after we heal them @@ -53,12 +53,13 @@ #define BB_CLEANBOT_EMAGGED_PHRASES "emagged_phrases" ///key that holds drawings we hunt #define BB_CLEANABLE_DRAWINGS "cleanable_drawings" -///Key that holds our clean target -#define BB_CLEAN_TARGET "clean_target" + ///key that holds the janitor we will befriend #define BB_FRIENDLY_JANITOR "friendly_janitor" ///key that holds the victim we will spray #define BB_ACID_SPRAY_TARGET "acid_spray_target" +///key that holds our acid spray attack cooldown +#define BB_ACID_SPRAY_COOLDOWN "acid_spray_cooldown" ///key that holds trash we will burn #define BB_HUNTABLE_TRASH "huntable_trash" @@ -75,6 +76,8 @@ #define BB_WASH_FRUSTRATION "wash_frustration" ///key that holds cooldown after we finish cleaning something, so we dont immediately run off to patrol #define BB_POST_CLEAN_COOLDOWN "post_clean_cooldown" +///key that holds cooldown after trash talk +#define BB_TRASH_TALK_COOLDOWN "trash_talk_cooldown" //secbots ///threat of our current target @@ -99,8 +102,6 @@ //firebot keys ///things we can extinguish #define BB_FIREBOT_CAN_EXTINGUISH "can_extinguish" -///the target we will extinguish -#define BB_FIREBOT_EXTINGUISH_TARGET "extinguish_target" ///lines we say when we detect a fire #define BB_FIREBOT_FIRE_DETECTED_LINES "fire_detected_lines" ///lines we say when we are idle @@ -123,33 +124,23 @@ #define BB_VIBEBOT_INSTRUMENT "instrument" //repairbots -///key that holds the floor we should tile over -#define BB_TILELESS_FLOOR "tileless_floor" -///key that holds the turf we should place a girder over -#define BB_GIRDER_TARGET "girder_target" -///key that holds the girder we should place a wall over -#define BB_GIRDER_TO_WALL_TARGET "girder_to_wall" -///key that holds the grille we must fix -#define BB_WINDOW_FRAMETARGET "grille_target" -///key that holds the machinery we repair with a welder -#define BB_WELDER_TARGET "welder_target" ///our wall girder ability #define BB_GIRDER_BUILD_ABILITY "girder_build_ability" -///key that holds breached floors we should repair -#define BB_BREACHED_FLOOR "breached_floor" ///key that holds our emagged speech #define BB_REPAIRBOT_EMAGGED_SPEECH "emagged_speech" ///key that holds our normal speech #define BB_REPAIRBOT_NORMAL_SPEECH "normal_speech" -///key that holds the thing we should deconstruct -#define BB_DECONSTRUCT_TARGET "deconstruct_target" ///key that holds our speech timer #define BB_REPAIRBOT_SPEECH_COOLDOWN "speech_cooldown" -///key that holds our target borg -#define BB_ROBOT_TARGET "robot_target" -///key that holds materials we can refill -#define BB_REFILLABLE_TARGET "refillable_target" +///Key that holds our current interaction type, to determine how we will interact with this object +#define BB_REPAIRBOT_INTERACTION_TYPE "interaction_type" +///Key that hold robot target +#define BB_ROBOT_TARGET "robot target" +///Key thast holds thingt to deconstruct +#define BB_DECONSTRUCT_TARGET "deconstruct_target" +#define REPAIRBOT_INTERACTION_INTERACT 1 +#define REPAIRBOT_INTERACTION_BUILD_GIRDERS 2 //mulebots ///key that holds our delivery destination's name diff --git a/code/__DEFINES/ai/carp.dm b/code/__DEFINES/ai/carp.dm index 459b98ffb02..1ff4acb3fe2 100644 --- a/code/__DEFINES/ai/carp.dm +++ b/code/__DEFINES/ai/carp.dm @@ -6,6 +6,8 @@ #define BB_CARP_MIGRATION_PATH "BB_carp_migration_path" /// Current target turf in your migration #define BB_CARP_MIGRATION_TARGET "BB_carp_migration_target" +/// Turf of a carp rift we've decided to ride towards +#define BB_CARP_RIFT_DESTINATION "BB_carp_rift_destination" /// Targeting keys for magicarp spells #define BB_MAGICARP_SPELL_TARGET "BB_magicarp_spell_target" diff --git a/code/__DEFINES/ai/haunted.dm b/code/__DEFINES/ai/haunted.dm index d836d6be60d..1956d60ac3d 100644 --- a/code/__DEFINES/ai/haunted.dm +++ b/code/__DEFINES/ai/haunted.dm @@ -8,6 +8,8 @@ #define HAUNTED_ITEM_AGGRO_ADDITION 2 ///how far a cursed item will still try to chase a target #define CURSED_VIEW_RANGE 7 +///Max number of throws per attack sequence before giving up on the current target +#define HAUNTED_MAX_THROW_ATTEMPTS 4 #define BB_TO_HAUNT_LIST "BB_to_haunt_list" ///Actual mob the item is haunting at the moment diff --git a/code/__DEFINES/ai/monkey.dm b/code/__DEFINES/ai/monkey.dm index f11ac96fefe..6329e546cad 100644 --- a/code/__DEFINES/ai/monkey.dm +++ b/code/__DEFINES/ai/monkey.dm @@ -7,14 +7,12 @@ #define BB_MONKEY_BLACKLISTITEMS "BB_monkey_blacklistitems" #define BB_MONKEY_PICKUPTARGET "BB_monkey_pickuptarget" #define BB_MONKEY_PICKPOCKETING "BB_monkey_pickpocketing" -#define BB_MONKEY_CURRENT_ATTACK_TARGET "BB_monkey_current_attack_target" #define BB_MONKEY_CURRENT_PRESS_TARGET "BB_monkey_current_press_target" #define BB_MONKEY_CURRENT_GIVE_TARGET "BB_monkey_current_give_target" #define BB_MONKEY_TARGET_DISPOSAL "BB_monkey_target_disposal" #define BB_MONKEY_TARGET_MONKEYS "BB_monkey_target_monkeys" #define BB_MONKEY_DISPOSING "BB_monkey_disposing" #define BB_MONKEY_RECRUIT_COOLDOWN "BB_monkey_recruit_cooldown" -#define BB_RESISTING "BB_resisting" /// Monkey is not necessarily a wild animal so it won't resort to fighting over food and such #define BB_MONKEY_TAMED "BB_monkey_tamed" /// Chance to give our held item to a nearby mob @@ -23,3 +21,17 @@ #define BB_MONKEY_PRESS_TYPEPATH "BB_monkey_press_typepath" /// The item the monkey is currently serving to someone #define BB_MONKEY_CURRENT_SERVED_ITEM "BB_monkey_current_served_item" +/// Set to TRUE when the monkey's current pickup target is on a mob (pickpocket), null when it's on the floor +#define BB_MONKEY_PICKUP_IS_PICKPOCKET "BB_monkey_pickup_is_pickpocket" +/// List of nearby patrons the monkey can serve food to +#define BB_MONKEY_PATRONS_NEARBY "BB_monkey_patrons_nearby" +/// Common emotes played during idle behavior +#define BB_MONKEY_IDLE_COMMON_EMOTES "BB_monkey_idle_common_emotes" +/// Rare emotes played during idle behavior +#define BB_MONKEY_IDLE_RARE_EMOTES "BB_monkey_idle_rare_emotes" +/// Whether the monkey believes its gun still works (20% chance it doesn't notice a dry-fire) +#define BB_MONKEY_GUN_WORKED "BB_monkey_gun_worked" +///Whether the monkey wants to press some shit +#define BB_MONKEY_WANNA_PRESS_SOME_SHIT "Monkeys wants to press something" +///Whether the monkey can look for patrons again +#define BB_MONKEY_PATRON_FIND_COOLDOWN "Monkeys can look for patron" diff --git a/code/__DEFINES/ai/monsters.dm b/code/__DEFINES/ai/monsters.dm index 8a4e9d56aa5..b13ef26e1b0 100644 --- a/code/__DEFINES/ai/monsters.dm +++ b/code/__DEFINES/ai/monsters.dm @@ -60,16 +60,28 @@ // bee keys ///the bee hive we live inside #define BB_CURRENT_HOME "BB_current_home" +///the bee hive we want to move into +#define BB_TARGET_HOME "BB_target_home" ///the hydro we will pollinate #define BB_TARGET_HYDRO "BB_target_hydro" ///key to swarm around #define BB_SWARM_TARGET "BB_swarm_target" +///turf picked by swirl_around_target for move_to_target to path toward +#define BB_SWIRL_TURF "BB_swirl_turf" +///key to indicate if the bee wants to go in or out of its hive. +#define BB_WANTS_TO_TRANSITION_HIVE "BB_wants_to_transition_hive" // bear keys ///the hive with honey that we will steal from #define BB_FOUND_HONEY "BB_found_honey" +///cooldown between hive raids +#define BB_BEAR_HIVE_COOLDOWN "BB_bear_hive_cooldown" +///cooldown between honeycomb hunts +#define BB_BEAR_HONEYCOMB_COOLDOWN "BB_bear_honeycomb_cooldown" ///the tree that we will climb #define BB_CLIMBED_TREE "BB_climbed_tree" +///tree climbing cooldown +#define BB_TREE_CLIMBING_COOLDOWN "Tree Climbing Cooldown" /// Lobstrosities will only attack people with one of these traits #define BB_LOBSTROSITY_EXPLOIT_TRAITS "BB_lobstrosity_exploit_traits" @@ -90,6 +102,8 @@ #define BB_BLIND_TARGET "BB_blind_target" ///value to store the minimum eye damage to prevent us from attacking a human #define BB_EYE_DAMAGE_THRESHOLD "BB_eye_damage_threshold" +///the turf in front of our target we move to so our glare lines up +#define BB_GLARE_POSITION "BB_glare_position" // hivebot keys ///the machine we must go to repair @@ -108,6 +122,10 @@ #define BB_TARGET_CANNIBAL "BB_target_cannibal" ///the tree we will burn down #define BB_TARGET_TREE "BB_target_tree" +///cooldown key for sculpting statues +#define BB_WHELP_SCULPT_COOLDOWN "BB_whelp_sculpt_cooldown" +///cooldown key for burning trees +#define BB_WHELP_BURN_COOLDOWN "BB_whelp_burn_cooldown" // Regal Rats /// The rat's ability to corrupt an area. @@ -140,6 +158,8 @@ #define BB_BOULDER_TARGET "BB_boulder_target" /// key that holds the ore_vent we will harvest boulders from #define BB_VENT_TARGET "BB_vent_target" +/// Prevent us from strip-mining Lavaland lol +#define BB_MINING_COOLDOWN "Mining Cooldown" // minebot keys /// key that stores our toggle light ability @@ -174,6 +194,8 @@ #define BB_MINEBOT_REPAIR_DRONE "minebot_repair_drone" ///should we plant mines? #define BB_MINEBOT_PLANT_MINES "minebot_plant_mines" +///should we plant mines? +#define BB_MINEBOT_CRIT_ALERT_COOLDOWN "minebot callcrit cooldown" //seedling keys /// the water can we will pick up @@ -231,6 +253,21 @@ #define BB_MOOK_MUSIC_AUDIENCE "music_audience" /// the bonfire we will light up #define BB_MOOK_BONFIRE_TARGET "bonfire_target" +/// turf we are wandering toward (away from village) +#define BB_WANDER_DESTINATION "BB_wander_destination" +/// turf we will stand on to deposit ores at the material stand +#define BB_DEPOSIT_POSITION "BB_deposit_position" +/// list of /datum/pet_command instances the chief can issue +#define BB_MOOK_COMMANDS "BB_mook_commands" +/// list of things we can heal +#define BB_MOOK_HEAL_TARGETS "heal targets" +/// Cooldown on giving commands +#define BB_COMMAND_COOLDOWN "command cooldown" +///Things to light on fire +#define BB_BONFIRE_TARGETS "bonfire targets" +///Mook mining cooldown +#define BB_MOOK_MINING_COOLDOWN "mining cooldown" + //gutlunch keys ///the trough we will eat from @@ -250,7 +287,7 @@ #define BB_SWIM_ALTERNATE_TURF "swim_alternate_turf" ///key holds our state of swimming #define BB_CURRENTLY_SWIMMING "currently_swimming" -///key holds how long we will be swimming for +///Time between swims #define BB_KEY_SWIMMER_COOLDOWN "key_swimmer_cooldown" //Wizard AI keys /// Key where we store our main targeted spell @@ -301,6 +338,8 @@ #define BB_DEER_GRASS_TARGET "deer_grass_target" ///our tree target #define BB_DEER_TREE_TARGET "deer_tree_target" +///set when we roll the urge to go find another deer to play with +#define BB_DEER_WANTS_TO_PLAY "deer_wants_to_play" ///our temporary playmate #define BB_DEER_PLAYFRIEND "deer_playfriend" ///our home target @@ -309,6 +348,11 @@ #define BB_DEER_RESTING "deer_resting" ///time till our next rest duration #define BB_DEER_NEXT_REST_TIMER "deer_next_rest_timer" +///cooldowns gating how often we graze, drink and mark territory +#define BB_DEER_GRAZE_COOLDOWN "deer_graze_cooldown" +#define BB_DEER_DRINK_COOLDOWN "deer_drink_cooldown" +#define BB_DEER_MARK_COOLDOWN "deer_mark_cooldown" +#define BB_DEER_PLAY_COOLDOWN "deer_play_cooldown" //the thing boss #define BB_THETHING_CHARGE "BB_THETHING_CHARGE" @@ -318,19 +362,25 @@ #define BB_THETHING_CARDTENDRILS "BB_THETHING_CARDTENDRILS" #define BB_THETHING_ACIDSPIT "BB_THETHING_ACIDSPIT" /// Blackboard key for The Thing boss that determines attack mode. TRUE means it will focus on closing the distance and murdering the person in question. Otherwise AOE. -#define BB_THETHING_ATTACKMODE "BB_THETHING_ATTACKMODE" +#define BB_THETHING_MELEEMODE "BB_THETHING_MELEEMODE" /// The Thing will be in attack mode forever if true #define BB_THETHING_NOAOE "BB_THETHING_NOAOE" /// What (first in combo) attack was last executed #define BB_THETHING_LASTAOE "BB_THETHING_LASTAOE" +/// The action object selected by pick_random_ability for the current AOE slot +#define BB_THETHING_SELECTED_AOE "BB_THETHING_SELECTED_AOE" -//turtle -///our tree's ability -#define BB_TURTLE_TREE_ABILITY "turtle_tree_ability" ///people we headbutt! #define BB_TURTLE_HEADBUTT_VICTIM "turtle_headbutt_victim" ///flore we must smell #define BB_TURTLE_FLORA_TARGET "turtle_flora_target" +///Cooldown between headbutts +#define BB_TURTLE_HEADBUTT_COOLDOWN "turtle_headbutt_cooldown" +///Cooldown between smelling flora +#define BB_TURTLE_FLORA_COOLDOWN "turtle_flora_cooldown" +#define BB_TURTLE_HEADBUTT_TYPES "turtle_headbutt_types" +#define BB_TURTLE_FLORA_TYPES "turtle_flora_types" + #define BB_GUNMIMIC_GUN_EMPTY "BB_GUNMIMIC_GUN_EMPTY" diff --git a/code/__DEFINES/ai/pet_commands.dm b/code/__DEFINES/ai/pet_commands.dm index 5f03bf1a5b0..33b574fdfaf 100644 --- a/code/__DEFINES/ai/pet_commands.dm +++ b/code/__DEFINES/ai/pet_commands.dm @@ -5,6 +5,10 @@ #define BB_CURRENT_PET_TARGET "BB_current_pet_target" /// Blackboard field for how we target things, as usually we want to be more permissive than normal #define BB_PET_TARGETING_STRATEGY "BB_pet_targeting" +/// Hiding location scratch key used by basic_melee_attack in pet command attack subtrees +#define BB_PET_ATTACK_HIDING_LOCATION "BB_pet_attack_hiding_location" +/// Scratch key holding the active untargeted ability datum for /datum/pet_command/untargeted_ability +#define BB_PET_ACTIVE_ABILITY "BB_pet_active_ability" /// Typecache of weakrefs to mobs this mob is friends with, will follow their instructions and won't attack them #define BB_FRIENDS_LIST "BB_friends_list" /// List of strings we might say to encourage someone to make better choices. diff --git a/code/__DEFINES/ai/pets.dm b/code/__DEFINES/ai/pets.dm index 067569a8a9a..ef61ebcc4ac 100644 --- a/code/__DEFINES/ai/pets.dm +++ b/code/__DEFINES/ai/pets.dm @@ -29,6 +29,8 @@ #define BB_FIND_MOM_TYPES "BB_find_mom_types" ///list of types of mobs we must ignore #define BB_IGNORE_MOM_TYPES "BB_ignore_mom_types" +///cooldown between emoting at our parent +#define BB_PARENT_EMOTE_COOLDOWN "BB_parent_emote_cooldown" /// The current string that this parrot will repeat back to someone #define BB_PARROT_REPEAT_STRING "BB_parrot_repeat_string" @@ -52,6 +54,8 @@ #define BB_HOARD_LOCATION_RANGE "hoard_location_range" /// key that holds items we arent interested in hoarding #define BB_IGNORE_ITEMS "ignore_items" +/// cooldown key throttling how often a parrot repeats a phrase +#define BB_PARROT_SPEECH_COOLDOWN "BB_parrot_speech_cooldown" // Cultist pet keys ///our ability to summon runes diff --git a/code/__DEFINES/ai/slime.dm b/code/__DEFINES/ai/slime.dm index ba08dbba556..9c8c2be10a8 100644 --- a/code/__DEFINES/ai/slime.dm +++ b/code/__DEFINES/ai/slime.dm @@ -12,3 +12,5 @@ #define BB_SLIME_EVOLVE "BB_slime_evolve" ///Our reproduce action #define BB_SLIME_REPRODUCE "BB_slime_reproduce" +///Target to try and eat, separate from retaliation because they use different strats +#define BB_SLIME_EAT_TARGET "BB_slime_eat_target" diff --git a/code/__DEFINES/ai/tourist.dm b/code/__DEFINES/ai/tourist.dm index 5ef27550d6f..e46db87fde6 100644 --- a/code/__DEFINES/ai/tourist.dm +++ b/code/__DEFINES/ai/tourist.dm @@ -15,3 +15,7 @@ #define BB_CUSTOMER_CURRENT_TARGET "BB_customer_current_target" /// Robot customer has said their can't find seat line at least once. Used to rate limit how often they'll complain after the first time. #define BB_CUSTOMER_SAID_CANT_FIND_SEAT_LINE "BB_customer_said_cant_find_seat_line" +/// The exit portal atom the customer walks to when leaving the venue. +#define BB_CUSTOMER_EXIT_PORTAL "BB_customer_exit_portal" +/// Cooldown key used to throttle seat-search retries. +#define BB_CUSTOMER_FIND_SEAT_COOLDOWN "BB_customer_find_seat_cooldown" diff --git a/code/__DEFINES/ai/trader.dm b/code/__DEFINES/ai/trader.dm index 853dd8736b6..5d8c0405296 100644 --- a/code/__DEFINES/ai/trader.dm +++ b/code/__DEFINES/ai/trader.dm @@ -4,3 +4,5 @@ #define BB_SHOP_SPOT "BB_shop_spot" ///Reference to our first customer to harass with deals #define BB_FIRST_CUSTOMER "BB_first_customer" +///Whether we spook customers with our deals +#define BB_TRADER_RUSH_TO_SELL "BB_rush_to_sell" diff --git a/code/__DEFINES/ai/ventcrawling.dm b/code/__DEFINES/ai/ventcrawling.dm index a60b7fd5940..b672113738d 100644 --- a/code/__DEFINES/ai/ventcrawling.dm +++ b/code/__DEFINES/ai/ventcrawling.dm @@ -14,3 +14,5 @@ #define BB_TIME_TO_GIVE_UP_ON_VENT_PATHING "BB_seconds_until_we_give_up_on_vent_pathing" /// The timer ID of the timer that makes us give up on vent pathing. #define BB_GIVE_UP_ON_VENT_PATHING_TIMER_ID "BB_give_up_on_vent_pathing_timer_id" +/// The world.time when we entered a vent. Null when not in one. +#define BB_VENT_ENTRY_TIME "BB_vent_entry_time" diff --git a/code/__DEFINES/basic_mobs.dm b/code/__DEFINES/basic_mobs.dm index 1a97163a871..d97418718a1 100644 --- a/code/__DEFINES/basic_mobs.dm +++ b/code/__DEFINES/basic_mobs.dm @@ -127,3 +127,9 @@ GLOBAL_LIST_EMPTY(customized_pets) #define BASIC_MOB_END_ATTACK_CHAIN 1 ///Return value for [/mob/living/basic/proc/early_melee_attack]. Using this value will make the attack end, and sets a cooldown. Useful if you add behavior to early_melee_attack #define BASIC_MOB_END_ATTACK_CHAIN_COOLDOWN 2 + +///Delay between trying to update target selection +#define BASIC_MOB_FIND_TARGET_RATE 1 SECONDS + +///Time between idle behavior execution +#define IDLE_BEHAVIOR_RATE 1.5 SECONDS diff --git a/code/__DEFINES/dcs/signals/signals_ai_controller.dm b/code/__DEFINES/dcs/signals/signals_ai_controller.dm index 1888612d2c4..5ff3d2419a5 100644 --- a/code/__DEFINES/dcs/signals/signals_ai_controller.dm +++ b/code/__DEFINES/dcs/signals/signals_ai_controller.dm @@ -3,7 +3,5 @@ #define COMSIG_AI_CONTROLLER_POSSESSED_PAWN "ai_controller_possessed_pawn" ///sent from ai controllers when they stop possessing a pawn: (datum/ai_controller/source_controller) #define COMSIG_AI_CONTROLLER_UNPOSSESSED_PAWN "ai_controller_unpossessed_pawn" -///sent from ai controllers when they pick behaviors: (list/datum/ai_behavior/old_behaviors, list/datum/ai_behavior/new_behaviors) -#define COMSIG_AI_CONTROLLER_PICKED_BEHAVIORS "ai_controller_picked_behaviors" -///sent from ai controllers when a behavior is inserted into the queue: (list/new_arguments) -#define AI_CONTROLLER_BEHAVIOR_QUEUED(type) "ai_controller_behavior_queued_[type]" +///sent from the pawn of an ai controller when a runtime subtree override slot changes: (new_type) new_type is null when cleared +#define COMSIG_AI_OVERRIDE_SLOT_CHANGED(id) "ai_override_slot_changed_[id]" diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_ai.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_ai.dm index 1c6fcbffbda..de7093057dd 100644 --- a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_ai.dm +++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_ai.dm @@ -14,3 +14,6 @@ ///Signal sent off of ai/movement/proc/start_moving_towards #define COMSIG_MOB_AI_MOVEMENT_STARTED "mob_ai_movement_started" + +///Signal sent off of ai/movement/proc/increment_pathing_failures +#define COMSIG_MOB_AI_MOVEMENT_FAILED "mob_ai_movement_failed" diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_basic.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_basic.dm index b459c2530b1..95c138ad579 100644 --- a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_basic.dm +++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_basic.dm @@ -10,7 +10,7 @@ ///from the ranged_attacks component for basic mobs: (mob/living/basic/firer, atom/target, modifiers) #define COMSIG_BASICMOB_POST_ATTACK_RANGED "basicmob_post_attack_ranged" -/// Sent from /datum/ai_planning_subtree/parrot_as_in_repeat() : () +/// Sent from /datum/bt_node/ai_behavior/parrot_repeat_speech/perform(): () #define COMSIG_NEEDS_NEW_PHRASE "parrot_needs_new_phrase" #define NO_NEW_PHRASE_AVAILABLE (1<<0) //! Cancel to try again later for when we actually get a new phrase diff --git a/code/__DEFINES/dcs/signals/signals_object.dm b/code/__DEFINES/dcs/signals/signals_object.dm index 7092efd0e2d..04075907d90 100644 --- a/code/__DEFINES/dcs/signals/signals_object.dm +++ b/code/__DEFINES/dcs/signals/signals_object.dm @@ -153,6 +153,8 @@ #define COMSIG_MOB_DROPPED_ITEM "mob_dropped_item" ///from base of obj/item/pickup(): (/mob/taker) #define COMSIG_ITEM_PICKUP "item_pickup" +///from base of mob/put_in_hand(), after forceMove item is now in the mob's hand: (mob/holder, hand_index) +#define COMSIG_ITEM_ENTERED_HANDS "item_entered_hands" ///from base of obj/item/on_outfit_equip(): (mob/equipper, visuals_only, slot) #define COMSIG_ITEM_EQUIPPED_AS_OUTFIT "item_equip_as_outfit" ///from base of datum/storage/handle_enter(): (datum/storage/storage) diff --git a/code/__DEFINES/monkeys.dm b/code/__DEFINES/monkeys.dm index 8dfccc937f7..a2280ca95a4 100644 --- a/code/__DEFINES/monkeys.dm +++ b/code/__DEFINES/monkeys.dm @@ -33,12 +33,9 @@ /// amount of aggro to add if someone stole the food we wanted #define MONKEY_FOOD_HATRED_AMOUNT 2 /// probability of reducing aggro by one when the monkey attacks -#define MONKEY_HATRED_REDUCTION_PROB 20 +#define MONKEY_HATRED_REDUCTION_PROB 40 /// Monkey was calmed, such as from weed #define MONKEY_CALMED_HATRED_AMOUNT -2 /// Monkey was angered, such as from alcohol #define MONKEY_ANGERED_HATRED_AMOUNT 2 - -///Monkey recruit cooldown -#define MONKEY_RECRUIT_COOLDOWN (1 MINUTES) diff --git a/code/__DEFINES/robots.dm b/code/__DEFINES/robots.dm index 84735e146dd..ad7a6a40c2a 100644 --- a/code/__DEFINES/robots.dm +++ b/code/__DEFINES/robots.dm @@ -90,7 +90,6 @@ GLOBAL_LIST_EMPTY(cyborg_all_models_icon_list) /// Default view range for finding targets. #define DEFAULT_SCAN_RANGE 7 //Amount of time that must pass after a Commissioned bot gets saluted to get another. -#define BOT_COMMISSIONED_SALUTE_DELAY (60 SECONDS) //Bot mode defines displaying how Bots act ///The Bot is currently active, and will do whatever it is programmed to do. diff --git a/code/__DEFINES/subsystems.dm b/code/__DEFINES/subsystems.dm index 02a2ff07e4c..b7be2dda65d 100644 --- a/code/__DEFINES/subsystems.dm +++ b/code/__DEFINES/subsystems.dm @@ -127,7 +127,6 @@ // Subsystem fire priority, from lowest to highest priority // If the subsystem isn't listed here it's either DEFAULT or PROCESS (if it's a processing subsystem child) -#define FIRE_PRIORITY_UNPLANNED_NPC 3 #define FIRE_PRIORITY_IDLE_NPC 5 #define FIRE_PRIORITY_PING 10 #define FIRE_PRIORITY_SERVER_MAINT 10 @@ -137,13 +136,13 @@ #define FIRE_PRIORITY_BLOOD_DRYING 10 #define FIRE_PRIORITY_GARBAGE 15 #define FIRE_PRIORITY_DATABASE 16 +#define FIRE_PRIORITY_NPC_LOW 19 #define FIRE_PRIORITY_WET_FLOORS 20 #define FIRE_PRIORITY_AIR 20 -#define FIRE_PRIORITY_NPC 20 #define FIRE_PRIORITY_ASSETS 20 #define FIRE_PRIORITY_HYPERSPACE_DRIFT 20 -#define FIRE_PRIORITY_NPC_MOVEMENT 21 -#define FIRE_PRIORITY_NPC_ACTIONS 22 +#define FIRE_PRIORITY_NPC 21 +#define FIRE_PRIORITY_NPC_MOVEMENT 22 #define FIRE_PRIORITY_PATHFINDING 23 #define FIRE_PRIORITY_CLIFF_FALLING 24 #define FIRE_PRIORITY_PROCESS 25 diff --git a/code/_globalvars/lists/basic_ai.dm b/code/_globalvars/lists/basic_ai.dm deleted file mode 100644 index 561d874c4e9..00000000000 --- a/code/_globalvars/lists/basic_ai.dm +++ /dev/null @@ -1,19 +0,0 @@ -///all basic ai subtrees -GLOBAL_LIST_EMPTY(ai_subtrees) - -///basic ai controllers based on status -GLOBAL_LIST_INIT(ai_controllers_by_status, list( - AI_STATUS_ON = list(), - AI_STATUS_OFF = list(), - AI_STATUS_IDLE = list(), -)) - -///basic ai controllers based on their z level -GLOBAL_LIST_EMPTY(ai_controllers_by_zlevel) - -///basic ai controllers that are currently performing idled behaviors -GLOBAL_LIST_INIT_TYPED(unplanned_controllers, /list/datum/ai_controller, list( - AI_STATUS_ON = list(), - AI_STATUS_IDLE = list(), -)) - diff --git a/code/controllers/subsystem/ai_controllers.dm b/code/controllers/subsystem/ai_controllers.dm index 4102806be67..27d5cdf3281 100644 --- a/code/controllers/subsystem/ai_controllers.dm +++ b/code/controllers/subsystem/ai_controllers.dm @@ -1,35 +1,87 @@ +/// How many of the most expensive controllers to track per pass for the MC stat entry +#define AI_STAT_EXPENSIVE_TRACKED 5 + /// The subsystem used to tick [/datum/ai_controllers] instances. Handling the re-checking of plans. SUBSYSTEM_DEF(ai_controllers) name = "AI Controller Ticker" - ss_flags = SS_POST_FIRE_TIMING|SS_BACKGROUND + ss_flags = SS_POST_FIRE_TIMING priority = FIRE_PRIORITY_NPC dependencies = list( /datum/controller/subsystem/movement/ai_movement, ) - wait = 0.5 SECONDS //Plan every half second if required, not great not terrible. + wait = 0.25 SECONDS //Plan every 1/4th second if required. In theory your AI should not be planning this much, but its useful because we want planning to be responsive when a previous plan ends. runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME var/list/currentrun = list() ///type of status we are interested in running var/planning_status = AI_STATUS_ON - /// The average tick cost of all active AI, calculated on fire. - var/our_cost - /// The tick cost of all currently processed AI, being summed together + /// CPU cost accumulated by the in-progress pass, summed across fires. var/summing_cost + /// world.time at which the in-progress pass started. + var/pass_started + /// How many controllers the in-progress pass started with. + var/pass_size + /// Average wall-clock duration for a full pass on controllers + var/average_pass_time + /// Longest gap any single controller went between two ticks. + var/longest_tick_gap + /// Running longest tick gap of the in-progress pass. + var/summing_tick_gap + /// Display strings for the most expensive controllers of the last completed pass, most expensive first. + var/list/most_expensive = list() + /// Worst SelectBehaviors cost seen this round. + var/worst_controller_cost = 0 + /// Display string for the controller responsible for worst_controller_cost. + var/worst_controller_name + /// Running top-cost candidates of the in-progress pass. Assoc list of controller -> SelectBehaviors cost in ms, has a capped amount of entries + var/list/summing_expensive = list() + /// Cheapest cost in summing_expensive once it's full; a controller must beat this to enter the list. + var/summing_expensive_cutoff = 0 + /// List of all targeting_strategy singletons, key is the typepath while assigned value is a newly created instance of the typepath. See setup_targeting_strats() + var/list/targeting_strategies + /// List of all target_priority_strategy singletons, key is the typepath while assigned value is a newly created instance of the typepath. See setup_target_priority_strats() + var/list/target_priority_strategies + /// List of all target_source singletons, key is the typepath while assigned value is a newly created instance of the typepath. See setup_target_sources() + var/list/target_sources + ///AI controllers, sorted by their status + var/list/ai_controllers_by_status = list( + AI_STATUS_ON = list(), + AI_STATUS_ON_LOW = list(), + AI_STATUS_OFF = list(), + ) + ///AI controllers, sorted by their z level + var/list/ai_controllers_by_zlevel = list() + +/datum/controller/subsystem/ai_controllers/Recover() + if(islist(SSai_controllers.ai_controllers_by_status)) + ai_controllers_by_status = SSai_controllers.ai_controllers_by_status + if(islist(SSai_controllers.ai_controllers_by_zlevel)) + ai_controllers_by_zlevel = SSai_controllers.ai_controllers_by_zlevel /datum/controller/subsystem/ai_controllers/Initialize() - setup_subtrees() + setup_targeting_strats() + setup_target_priority_strats() + setup_target_sources() return SS_INIT_SUCCESS /datum/controller/subsystem/ai_controllers/stat_entry(msg) - var/list/planning_list = GLOB.ai_controllers_by_status[planning_status] - msg = "\n Planning AIs:[length(planning_list)]/[round(our_cost,1)]%" + msg = "\n Active:[length(SSai_controllers.ai_controllers_by_status[planning_status])]|Off:[length(SSai_controllers.ai_controllers_by_status[AI_STATUS_OFF])]" + msg += "\n Pass:[pass_size - length(currentrun)]/[pass_size]|AvgPass:[round(average_pass_time * 0.1, 0.1)]s|WorstGap:[round(longest_tick_gap * 0.1, 0.1)]s" + if(length(most_expensive)) + msg += "\n Top: [most_expensive.Join(" | ")]" + if(worst_controller_name) + msg += "\n Slowest bozo of the round: [worst_controller_name]" return ..() /datum/controller/subsystem/ai_controllers/fire(resumed) if(!resumed) - var/list/planning_list = GLOB.ai_controllers_by_status[planning_status] + var/list/planning_list = SSai_controllers.ai_controllers_by_status[planning_status] currentrun = planning_list.Copy() summing_cost = 0 + summing_tick_gap = 0 + summing_expensive = list() + summing_expensive_cutoff = 0 + pass_started = world.time + pass_size = length(currentrun) //cache for sanic speed (lists are references anyways) var/list/current_run = src.currentrun @@ -37,12 +89,35 @@ SUBSYSTEM_DEF(ai_controllers) while(length(current_run)) var/datum/ai_controller/ai_controller = current_run[length(current_run)] current_run.len-- - if(!ai_controller.able_to_plan) - continue - ai_controller.SelectBehaviors(wait * 0.1) + // Pass the real time since this controller last ticked, so SPT_PROB rolls and + // time accumulators stay time-correct even when a pass takes several seconds. + var/seconds_per_tick = wait * 0.1 + if(ai_controller.last_bt_tick) + var/tick_gap = world.time - ai_controller.last_bt_tick + summing_tick_gap = max(summing_tick_gap, tick_gap) + seconds_per_tick = tick_gap * 0.1 + ai_controller.last_bt_tick = world.time + var/controller_timer = TICK_USAGE_REAL + ai_controller.SelectBehaviors(seconds_per_tick) - if(!length(ai_controller.current_behaviors)) //Still no plan - ai_controller.planning_failed() + ///Lets check if this is an expensive controller + var/tick_cost = TICK_DELTA_TO_MS(TICK_USAGE_REAL - controller_timer) + if(tick_cost > worst_controller_cost) + worst_controller_cost = tick_cost + worst_controller_name = "[ai_controller.pawn || ai_controller] [round(tick_cost, 0.01)]ms" + if(tick_cost > summing_expensive_cutoff) + summing_expensive[ai_controller] = tick_cost + if(length(summing_expensive) > AI_STAT_EXPENSIVE_TRACKED) + var/cheapest_cost = INFINITY + var/datum/ai_controller/cheapest + for(var/datum/ai_controller/candidate as anything in summing_expensive) + if(summing_expensive[candidate] < cheapest_cost) + cheapest_cost = summing_expensive[candidate] + cheapest = candidate + summing_expensive -= cheapest + summing_expensive_cutoff = INFINITY + for(var/datum/ai_controller/candidate as anything in summing_expensive) + summing_expensive_cutoff = min(summing_expensive_cutoff, summing_expensive[candidate]) if(MC_TICK_CHECK) break @@ -51,20 +126,47 @@ SUBSYSTEM_DEF(ai_controllers) if(MC_TICK_CHECK) return - our_cost = MC_AVERAGE(our_cost, summing_cost) + average_pass_time = MC_AVERAGE(average_pass_time, world.time - pass_started) + longest_tick_gap = summing_tick_gap -///Creates all instances of ai_subtrees and assigns them to the ai_subtrees list. -/datum/controller/subsystem/ai_controllers/proc/setup_subtrees() - if(length(GLOB.ai_subtrees)) - return - for(var/subtree_type in subtypesof(/datum/ai_planning_subtree)) - var/datum/ai_planning_subtree/subtree = new subtree_type - GLOB.ai_subtrees[subtree_type] = subtree + // Publish the pass's most expensive controllers as display strings, sorted most expensive first. + // Only a handful of entries, so a selection sort is fine. + var/list/expensive_entries = list() + while(length(summing_expensive)) + var/costliest_cost = 0 + var/datum/ai_controller/costliest + for(var/datum/ai_controller/candidate as anything in summing_expensive) + if(summing_expensive[candidate] >= costliest_cost) + costliest_cost = summing_expensive[candidate] + costliest = candidate + summing_expensive -= costliest + expensive_entries += "[costliest.pawn || costliest] [round(costliest_cost, 0.01)]ms" + most_expensive = expensive_entries ///Called when the max Z level was changed, updating our coverage. /datum/controller/subsystem/ai_controllers/proc/on_max_z_changed() - if(!length(GLOB.ai_controllers_by_zlevel)) - GLOB.ai_controllers_by_zlevel = new /list(world.maxz,0) - while (GLOB.ai_controllers_by_zlevel.len < world.maxz) - GLOB.ai_controllers_by_zlevel.len++ - GLOB.ai_controllers_by_zlevel[GLOB.ai_controllers_by_zlevel.len] = list() + if(!length(ai_controllers_by_zlevel)) + ai_controllers_by_zlevel = new /list(world.maxz,0) + while (ai_controllers_by_zlevel.len < world.maxz) + ai_controllers_by_zlevel.len++ + ai_controllers_by_zlevel[ai_controllers_by_zlevel.len] = list() + +/datum/controller/subsystem/ai_controllers/proc/setup_targeting_strats() + targeting_strategies = list() + for(var/target_type in subtypesof(/datum/targeting_strategy)) + var/datum/targeting_strategy/target_start = new target_type + targeting_strategies[target_type] = target_start + +/datum/controller/subsystem/ai_controllers/proc/setup_target_priority_strats() + target_priority_strategies = list() + for(var/target_type in subtypesof(/datum/target_priority_strategy)) + var/datum/target_priority_strategy/target_start = new target_type + target_priority_strategies[target_type] = target_start + +/datum/controller/subsystem/ai_controllers/proc/setup_target_sources() + target_sources = list() + for(var/source_type in subtypesof(/datum/target_source)) + var/datum/target_source/source = new source_type + target_sources[source_type] = source + +#undef AI_STAT_EXPENSIVE_TRACKED diff --git a/code/controllers/subsystem/ai_controllers_low_priority.dm b/code/controllers/subsystem/ai_controllers_low_priority.dm new file mode 100644 index 00000000000..a453a982f56 --- /dev/null +++ b/code/controllers/subsystem/ai_controllers_low_priority.dm @@ -0,0 +1,6 @@ +/// Plans background controllers that are active when unwatched but not critical +AI_CONTROLLER_SUBSYSTEM_DEF(low_priority_ai_controllers) + name = "AI Controller Ticker (Low)" + ss_flags = parent_type::ss_flags | SS_BACKGROUND | SS_NO_INIT + planning_status = AI_STATUS_ON_LOW + priority = FIRE_PRIORITY_NPC_LOW diff --git a/code/controllers/subsystem/ai_idle_controllers.dm b/code/controllers/subsystem/ai_idle_controllers.dm deleted file mode 100644 index 94b43207f82..00000000000 --- a/code/controllers/subsystem/ai_idle_controllers.dm +++ /dev/null @@ -1,10 +0,0 @@ -AI_CONTROLLER_SUBSYSTEM_DEF(ai_idle_controllers) - name = "AI Idle Controllers" - ss_flags = SS_POST_FIRE_TIMING | SS_BACKGROUND - priority = FIRE_PRIORITY_IDLE_NPC - dependencies = list( - /datum/controller/subsystem/ai_controllers, - ) - wait = 5 SECONDS - runlevels = RUNLEVEL_GAME - planning_status = AI_STATUS_IDLE diff --git a/code/controllers/subsystem/movement/movement_types.dm b/code/controllers/subsystem/movement/movement_types.dm index ea1e7f90cda..dcf01e4bfd4 100644 --- a/code/controllers/subsystem/movement/movement_types.dm +++ b/code/controllers/subsystem/movement/movement_types.dm @@ -129,12 +129,11 @@ owner?.processing_move_loop_flags = flags var/result = move() //Result is an enum value. Enums defined in __DEFINES/movement.dm - if(result) - EVLOG_PATH(moving, EVLOG_CATEGORY_MOVELOOPS, "Moved using [src]", list(old_loc, moving.loc)) //You might think, this runs a lot; but if not logging, it only does a lookup on the event logger. - if(moving) var/direction = get_dir(old_loc, moving.loc) SEND_SIGNAL(moving, COMSIG_MOVABLE_MOVED_FROM_LOOP, src, old_dir, direction) + if(result) + EVLOG_PATH(moving, EVLOG_CATEGORY_MOVELOOPS, "Moved using [src]", list(old_loc, moving.loc)) //You might think, this runs a lot; but if not logging, it only does a lookup on the event logger. owner?.processing_move_loop_flags = NONE SEND_SIGNAL(src, COMSIG_MOVELOOP_POSTPROCESS, result, delay * visual_delay) @@ -438,7 +437,8 @@ /datum/move_loop/has_target/jps/proc/on_finish_pathing(list/path) movement_path = path is_pathing = FALSE - EVLOG_PATH(moving, EVLOG_CATEGORY_JPS, "Planned AI path", movement_path) + if(moving) + EVLOG_PATH(moving, EVLOG_CATEGORY_JPS, "Planned AI path", movement_path) SEND_SIGNAL(src, COMSIG_MOVELOOP_JPS_FINISHED_PATHING, path) /datum/move_loop/has_target/jps/move() diff --git a/code/controllers/subsystem/processing/ai_behaviors.dm b/code/controllers/subsystem/processing/ai_behaviors.dm deleted file mode 100644 index aa5ef4eb55c..00000000000 --- a/code/controllers/subsystem/processing/ai_behaviors.dm +++ /dev/null @@ -1,40 +0,0 @@ -/// The subsystem used to tick [/datum/ai_behavior] instances. Handling the individual actions an AI can take like punching someone in the fucking NUTS -PROCESSING_SUBSYSTEM_DEF(ai_behaviors) - name = "AI Behavior Ticker" - ss_flags = SS_POST_FIRE_TIMING|SS_BACKGROUND - priority = FIRE_PRIORITY_NPC_ACTIONS - runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME - dependencies = list( - /datum/controller/subsystem/movement/ai_movement, - ) - wait = 1 - /// List of all ai_behavior singletons, key is the typepath while assigned value is a newly created instance of the typepath. See setup_ai_behaviors() - var/list/ai_behaviors - /// List of all targeting_strategy singletons, key is the typepath while assigned value is a newly created instance of the typepath. See setup_targeting_strats() - var/list/targeting_strategies - /// List of all target_priority_strategy singletons, key is the typepath while assigned value is a newly created instance of the typepath. See setup_target_priority_strats() - var/list/target_priority_strategies - -/datum/controller/subsystem/processing/ai_behaviors/Initialize() - setup_ai_behaviors() - setup_targeting_strats() - setup_target_priority_strats() - return SS_INIT_SUCCESS - -/datum/controller/subsystem/processing/ai_behaviors/proc/setup_ai_behaviors() - ai_behaviors = list() - for(var/behavior_type in subtypesof(/datum/ai_behavior)) - var/datum/ai_behavior/ai_behavior = new behavior_type - ai_behaviors[behavior_type] = ai_behavior - -/datum/controller/subsystem/processing/ai_behaviors/proc/setup_targeting_strats() - targeting_strategies = list() - for(var/target_type in subtypesof(/datum/targeting_strategy)) - var/datum/targeting_strategy/target_start = new target_type - targeting_strategies[target_type] = target_start - -/datum/controller/subsystem/processing/ai_behaviors/proc/setup_target_priority_strats() - target_priority_strategies = list() - for(var/target_type in subtypesof(/datum/target_priority_strategy)) - var/datum/target_priority_strategy/target_start = new target_type - target_priority_strategies[target_type] = target_start diff --git a/code/controllers/subsystem/processing/ai_idle_behaviors.dm b/code/controllers/subsystem/processing/ai_idle_behaviors.dm deleted file mode 100644 index 2b631da96f3..00000000000 --- a/code/controllers/subsystem/processing/ai_idle_behaviors.dm +++ /dev/null @@ -1,19 +0,0 @@ -PROCESSING_SUBSYSTEM_DEF(idle_ai_behaviors) - name = "AI Idle Behaviors" - ss_flags = SS_BACKGROUND - wait = 1.5 SECONDS - priority = FIRE_PRIORITY_IDLE_NPC - dependencies = list( - /datum/controller/subsystem/ai_controllers, - ) - ///List of all the idle ai behaviors - var/list/idle_behaviors = list() - -/datum/controller/subsystem/processing/idle_ai_behaviors/Initialize() - setup_idle_behaviors() - return SS_INIT_SUCCESS - -/datum/controller/subsystem/processing/idle_ai_behaviors/proc/setup_idle_behaviors() - for(var/behavior_type in subtypesof(/datum/idle_behavior)) - var/datum/idle_behavior/behavior = new behavior_type - idle_behaviors[behavior_type] = behavior diff --git a/code/controllers/subsystem/unplanned_ai_idle_controllers.dm b/code/controllers/subsystem/unplanned_ai_idle_controllers.dm deleted file mode 100644 index 6385239e18c..00000000000 --- a/code/controllers/subsystem/unplanned_ai_idle_controllers.dm +++ /dev/null @@ -1,4 +0,0 @@ -UNPLANNED_CONTROLLER_SUBSYSTEM_DEF(idle_unplanned_controllers) - name = "Unplanned AI Idle Controllers" - wait = 2.5 SECONDS - target_status = AI_STATUS_IDLE diff --git a/code/controllers/subsystem/unplanned_controllers.dm b/code/controllers/subsystem/unplanned_controllers.dm deleted file mode 100644 index e2e78e0951e..00000000000 --- a/code/controllers/subsystem/unplanned_controllers.dm +++ /dev/null @@ -1,39 +0,0 @@ -GLOBAL_LIST_EMPTY(unplanned_controller_subsystems) -/// Handles making mobs perform lightweight "idle" behaviors such as wandering around when they have nothing planned -SUBSYSTEM_DEF(unplanned_controllers) - name = "Unplanned AI Controllers" - ss_flags = SS_POST_FIRE_TIMING|SS_BACKGROUND - priority = FIRE_PRIORITY_UNPLANNED_NPC - dependencies = list( - /datum/controller/subsystem/movement/ai_movement, - ) - wait = 0.25 SECONDS - runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME - ///what ai status are we interested in - var/target_status = AI_STATUS_ON - var/list/current_run = list() - -/datum/controller/subsystem/unplanned_controllers/Initialize() - ..() - GLOB.unplanned_controller_subsystems += src - return SS_INIT_SUCCESS - -/datum/controller/subsystem/unplanned_controllers/Destroy() - GLOB.unplanned_controller_subsystems -= src - return ..() - -/datum/controller/subsystem/unplanned_controllers/stat_entry(msg) - msg = "\n Planning AIs:[length(GLOB.unplanned_controllers[target_status])]" - return ..() - -/datum/controller/subsystem/unplanned_controllers/fire(resumed) - if(!resumed) - src.current_run = GLOB.unplanned_controllers[target_status].Copy() - var/list/current_run = src.current_run // cache for sonic speed - while(length(current_run)) - var/datum/ai_controller/unplanned = current_run[current_run.len] - current_run.len-- - if(!QDELETED(unplanned)) - unplanned.idle_behavior.perform_idle_behavior(wait * 0.1, unplanned) - if (MC_TICK_CHECK) - return diff --git a/code/datums/actions/mobs/create_legion_skull.dm b/code/datums/actions/mobs/create_legion_skull.dm index bfd997c5d45..e78ddd08aa0 100644 --- a/code/datums/actions/mobs/create_legion_skull.dm +++ b/code/datums/actions/mobs/create_legion_skull.dm @@ -16,4 +16,4 @@ /datum/action/cooldown/mob_cooldown/create_legion_skull/proc/create(atom/target) var/mob/living/basic/mining/legion_brood/minion = new(owner.loc) minion.assign_creator(owner) - minion.ai_controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] = target + minion.ai_controller.blackboard[BB_CURRENT_TARGET] = target diff --git a/code/datums/ai/README.md b/code/datums/ai/README.md index fb4022775e8..937d869ccc9 100644 --- a/code/datums/ai/README.md +++ b/code/datums/ai/README.md @@ -8,19 +8,15 @@ Our AI controller system is an attempt at making it possible to create modulariz A datum that can be added to any atom in the game. Similarly to components, they might only support a given subtype (e.g. /mob/living), but the idea is that theoretically, you could apply a specific AI controller to a big a group of different types as possible and it would still work. -These datums handle both the normal movement of mobs, but also their decision making, deciding which actions they will take based on the checks you put into their SelectBehaviors proc. - -If behaviors are selected, and the AI is in range, it will try to perform them. It runs all the behaviors it currently has in parallel; allowing for it to for example screech at someone while trying to attack them. As long as it has behaviors running, it will not try to generate new plans, making it not waste CPU when it already has an active goal. - They also hold data for any of the actions they might need to use, such as cooldowns, whether or not they're currently fighting, etcetera this is stored in the blackboard, more information on that below. ### Blackboard -The blackboard is an associated list keyed with strings and with values of whatever you want. These store information the mob has such as "Am I attacking someone", "Do I have a weapon". By using an associated list like this, no data needs to be stored on the actions themselves, and you could make actions that work on multiple ai controllers if you so pleased by making the key to use a variable. +The blackboard is an associated list keyed with strings and with values of whatever you want. These store information the mob has such as "Am I attacking someone", "Do I have a weapon". By using an associated list like this, You could make actions that work on multiple ai controllers if you so pleased by making the key to use a variable. ## AI Behavior -AI behaviors are the actions an AI can take. These can range from "Do an emote" to "Attack this target until he is dead". They are singletons and should contain nothing but static data. Any dynamic data should be stored in the blackboard, to allow different controllers to use the same behaviors. +AI behaviors are the actions an AI can take. These can range from "Do an emote" to "Attack this target". Any dynamic data such as target should be stored in the blackboard # Guides: diff --git a/code/datums/ai/_ai_behavior.dm b/code/datums/ai/_ai_behavior.dm index a73ca167214..dd26ab204a8 100644 --- a/code/datums/ai/_ai_behavior.dm +++ b/code/datums/ai/_ai_behavior.dm @@ -1,46 +1,131 @@ -///Abstract class for an action an AI can take, can range from movement to grabbing a nearby weapon. -/datum/ai_behavior - ///What distance you need to be from the target to perform the action - var/required_distance = 1 - ///Flags for extra behavior +/// Base type for AI behavior leaf nodes in the behavior tree system. +/// setup() is called once on first activation, perform() each tick while running. +/// Returns BT_SUCCESS / BT_FAILURE on completion, BT_RUNNING while active. +/datum/bt_node/ai_behavior + ///Flags for extra behavior (see AI_BEHAVIOR_* defines) var/behavior_flags = NONE - ///Cooldown between actions performances, defaults to the value of CLICK_CD_MELEE because that seemed like a nice standard for the speed of AI behavior - ///Do not read directly or mutate, instead use get_cooldown() - var/action_cooldown = CLICK_CD_MELEE + ///Cooldown between perform() calls; do not read directly use get_cooldown() + var/time_between_perform = 0 + /// TRUE after setup() has been called and before finish_action() completes. + var/running = FALSE + /// world.time when perform() may next be called. + var/next_perform_time = 0 + /// TRUE when the last perform() failed and we are waiting out next_perform_time to say we failed + var/failed_last_perform = FALSE + /// TRUE while an async perform kicked off by start_async() is going + VAR_PRIVATE/async_running = FALSE + /// TRUE once async finished and via finish_async + VAR_PRIVATE/async_finished = FALSE + /// AI_BEHAVIOR_* flags that came out of async perform + VAR_PRIVATE/async_result_flags = NONE -/// Returns the delay to use for this behavior in the moment -/// Override to return a conditional delay -/datum/ai_behavior/proc/get_cooldown(datum/ai_controller/cooldown_for) - return action_cooldown +/datum/bt_node/ai_behavior/has_active_descendants() + return running -/// Called by the ai controller when first being added. Additional arguments depend on the behavior type. -/// Return FALSE to cancel -/datum/ai_behavior/proc/setup(datum/ai_controller/controller, ...) +/datum/bt_node/ai_behavior/get_status_marker() + if(running) + return "*" + return ..() + +/datum/bt_node/ai_behavior/append_active_nodes(list/lines, indent) + if(running) + lines += "[indent][span_bold("● [label]")]" + +/** + * ai behavior tick. Runs setup() once on first activation, then perform() each tick. + * Respects per-controller cooldowns set by AI_BEHAVIOR_DELAY. + * Returns BT_SUCCESS / BT_FAILURE on completion, BT_RUNNING while active. + */ +/datum/bt_node/ai_behavior/tick(datum/ai_controller/controller, seconds_per_tick) + if(next_perform_time > world.time) + if(!running && failed_last_perform) + return BT_FAILURE + controller.active_execution_index = execution_index + return BT_RUNNING + + if(controller.bt_execution_log != null) // dont track if we're not viewing + if(length(controller.bt_execution_log) < BT_EXECUTION_LOG_MAX) + controller.bt_execution_log += execution_index + + if(!running) + if(!setup(controller)) + EVLOG_TEXT(controller, EVLOG_CATEGORY_AI_BEHAVIORS, "[controller.pawn] [type]: setup() failed") + return BT_FAILURE + EVLOG_TEXT(controller, EVLOG_CATEGORY_AI_BEHAVIORS, "[controller.pawn] starting [type]") + running = TRUE + + var/process_flags = perform(seconds_per_tick, controller) + + if(process_flags & AI_BEHAVIOR_DELAY) + next_perform_time = world.time + get_cooldown(controller) + if(process_flags & AI_BEHAVIOR_SUCCEEDED) + EVLOG_TEXT(controller, EVLOG_CATEGORY_AI_BEHAVIORS, "[controller.pawn] [type]: succeeded") + failed_last_perform = FALSE + finish_action(controller, TRUE) + return BT_SUCCESS + if(process_flags & AI_BEHAVIOR_FAILED) + EVLOG_TEXT(controller, EVLOG_CATEGORY_AI_BEHAVIORS, "[controller.pawn] [type]: failed") + failed_last_perform = TRUE + finish_action(controller, FALSE) + return BT_FAILURE + controller.active_execution_index = execution_index + return BT_RUNNING + +/// Returns the cooldown to apply after a AI_BEHAVIOR_DELAY perform(). Override for conditional delays. +/datum/bt_node/ai_behavior/proc/get_cooldown(datum/ai_controller/cooldown_for) + return time_between_perform + +/// Called when this behavior first activates on a controller. Return FALSE to abort (returns BT_FAILURE). +/datum/bt_node/ai_behavior/proc/setup(datum/ai_controller/controller) return TRUE -///Called by the AI controller when this action is performed -///Returns a set of flags defined in [code/__DEFINES/ai/ai.dm] -/datum/ai_behavior/proc/perform(seconds_per_tick, datum/ai_controller/controller, ...) +/// Called each tick while the behavior is running. Returns AI_BEHAVIOR_* flags. +/datum/bt_node/ai_behavior/proc/perform(seconds_per_tick, datum/ai_controller/controller) + SHOULD_NOT_SLEEP(TRUE) return -///Called when the action is finished. This needs the same args as perform besides the default ones -/datum/ai_behavior/proc/finish_action(datum/ai_controller/controller, succeeded, ...) - controller.dequeue_behavior(src) - controller.behavior_args -= type - if(!(behavior_flags & AI_BEHAVIOR_REQUIRE_MOVEMENT)) //If this was a movement task, reset our movement target if necessary - return - if(behavior_flags & AI_BEHAVIOR_KEEP_MOVE_TARGET_ON_FINISH) - return - clear_movement_target(controller) - controller.ai_movement.stop_moving_towards(controller) - EVLOG_MAPTEXT(controller, EVLOG_CATEGORY_AI_BEHAVIORS, "[controller.pawn] has [succeeded ? "succeeded" : "failed"] at performing [src]", get_turf(controller.pawn), "Behavior finished: [succeeded ? "Success" : "Failure"]") +/// Called when the behavior finishes (succeeded or failed). Subtypes should call ..(). +/datum/bt_node/ai_behavior/proc/finish_action(datum/ai_controller/controller, succeeded) + SHOULD_CALL_PARENT(TRUE) + running = FALSE + async_running = FALSE + async_finished = FALSE + async_result_flags = NONE -/// Helper proc to ensure consistency in setting the source of the movement target -/datum/ai_behavior/proc/set_movement_target(datum/ai_controller/controller, atom/target, datum/ai_movement/new_movement) - controller.set_movement_target(type, target, new_movement) +///Checks if we're running async behavior, and if its finished, returns result flags +/datum/bt_node/ai_behavior/proc/handle_async() + if(async_running) + return AI_BEHAVIOR_DELAY + if(async_finished) + return async_result_flags | AI_BEHAVIOR_DELAY + return NONE -/// Clear the controller's movement target only if it was us who last set it -/datum/ai_behavior/proc/clear_movement_target(datum/ai_controller/controller) - if (controller.movement_target_source != type) +///Marks that async behavior has started and runs perform_async +/datum/bt_node/ai_behavior/proc/start_async() + async_running = TRUE + INVOKE_ASYNC(src, PROC_REF(perform_async), owning_controller) + return AI_BEHAVIOR_DELAY + +///Override this if you have sleeping behavior, be sure to implement the other async procs in perform() +/datum/bt_node/ai_behavior/proc/perform_async(datum/ai_controller/controller) + return + +/// Call from an async behavior after its sleeping call, before committing side effects. FALSE means the behavior was aborted/reset mid-flight bail out without side effects. +/datum/bt_node/ai_behavior/proc/async_still_valid() + return async_running && !QDELETED(owning_controller?.pawn) + +/// Call from an async behavior to commit its result. No-op if the behavior was aborted mid-flight. +/datum/bt_node/ai_behavior/proc/finish_async(result_flags) + if(!async_still_valid()) return - controller.set_movement_target(type, null) + async_result_flags = result_flags + async_finished = TRUE + async_running = FALSE + +/datum/bt_node/ai_behavior/proc/modify_cooldown(new_next_perform_time) + next_perform_time = new_next_perform_time + +/datum/bt_node/ai_behavior/reset_tick_state() + if(running) + finish_action(owning_controller, FALSE) + ..() diff --git a/code/datums/ai/_ai_bt_composites.dm b/code/datums/ai/_ai_bt_composites.dm new file mode 100644 index 00000000000..765472df88f --- /dev/null +++ b/code/datums/ai/_ai_bt_composites.dm @@ -0,0 +1,307 @@ +/** + * Base composite node. Holds an ordered list of child bt_node instances. + * */ +/datum/bt_node/composite + /// Typepaths of child nodes declared on the type. Resolved to instances at tree construction. + var/list/children_typepaths = null + /// Resolved child instances. Populated at tree construction. Do not set directly. + var/list/children = null + +/datum/bt_node/composite/Destroy() + QDEL_LIST(children) + return ..() + +/datum/bt_node/composite/get_children() + return children + +/datum/bt_node/composite/has_active_descendants() + if(!children) + return FALSE + for(var/datum/bt_node/child as anything in children) + if(child.has_active_descendants()) + return TRUE + return FALSE + +/datum/bt_node/composite/finalize_node(datum/ai_controller/controller, list/to_visit) + ..() + if(!children) + return + for(var/datum/bt_node/child as anything in children) + child.parent_node = src + to_visit += children + +/datum/bt_node/composite/set_descriptor_children(list/children_descs, datum/ai_controller/controller) + var/list/resolved = list() + for(var/child_entry in children_descs) + var/datum/bt_node/child_node = controller.get_or_build_node(child_entry) + if(!isnull(child_node)) + resolved += child_node + children = resolved + +/datum/bt_node/composite/collect_reset_children(list/to_visit) + if(children) + to_visit += children + +/datum/bt_node/composite/assign_execution_indices(counter) + execution_index = counter + counter++ + for(var/datum/bt_node/c in children) + counter = c.assign_execution_indices(counter) + last_execution_index = counter - 1 + return counter + +/** + * Sequence node: ticks children in order. + * Returns BT_FAILURE on first child failure. + * Returns BT_RUNNING on first child returning BT_RUNNING (stops further evaluation). + * Returns BT_SUCCESS only if all children succeed. + * + * Resumes from the last RUNNING child index rather than restarting from child 1. + */ +/datum/bt_node/composite/sequence + node_type = BT_NODE_SEQUENCE + label = "SEQUENCE" + /// Index of the child that last returned BT_RUNNING. + var/running_child_index = 0 + +/datum/bt_node/composite/sequence/tick(datum/ai_controller/controller, seconds_per_tick) + var/result = BT_SUCCESS + var/start = running_child_index || 1 + for(var/i in start to length(children)) + var/datum/bt_node/child = children[i] + var/child_result = child.tick(controller, seconds_per_tick) + if(controller.cancelled_during_tick) + return BT_FAILURE + if(child_result != BT_SUCCESS) + result = child_result + if(child_result == BT_RUNNING) + running_child_index = i + else + running_child_index = 0 + return result + + running_child_index = 0 + return result + +/datum/bt_node/composite/sequence/reset_tick_state() + . = ..() + running_child_index = 0 + +/datum/bt_node/composite/sequence/append_active_nodes(list/lines, indent) + var/found_active = FALSE + for(var/datum/bt_node/child as anything in children) + if(found_active) + lines += "[indent]↑ [child.label]" + else if(child.has_active_descendants()) + found_active = TRUE + child.append_active_nodes(lines, indent) + +/datum/bt_node/composite/sequence/append_full_tree_state(list/lines, indent) + var/child_info = running_child_index ? " (child [running_child_index]/[length(children)])" : "" + lines += "[indent][get_status_marker()] SEQUENCE[child_info]" + for(var/datum/bt_node/child as anything in children) + child.append_full_tree_state(lines, "[indent] ") + +/** + * Selector node: ticks children in order. + * Returns the first non-BT_FAILURE result (BT_SUCCESS or BT_RUNNING), stopping further evaluation. + * Returns BT_FAILURE only if all children fail. + * + * Resumes from the last RUNNING child index rather than restarting from child 1. + + */ +/datum/bt_node/composite/selector + node_type = BT_NODE_SELECTOR + label = "SELECTOR" + /// Index of the child that last returned BT_RUNNING. + var/running_child_index = 0 + +/datum/bt_node/composite/selector/tick(datum/ai_controller/controller, seconds_per_tick) + var/result = BT_FAILURE + var/start = running_child_index || 1 + for(var/i in start to length(children)) + var/datum/bt_node/child = children[i] + var/child_result = child.tick(controller, seconds_per_tick) + if(controller.cancelled_during_tick) + return BT_FAILURE + if(child_result != BT_FAILURE) + result = child_result + if(child_result == BT_RUNNING) + running_child_index = i + else + running_child_index = 0 + return result + + running_child_index = 0 + return result + +/datum/bt_node/composite/selector/reset_tick_state() + . = ..() + running_child_index = 0 + +/datum/bt_node/composite/selector/append_active_nodes(list/lines, indent) + for(var/datum/bt_node/child as anything in children) + if(child.has_active_descendants()) + child.append_active_nodes(lines, indent) + return + +/datum/bt_node/composite/selector/append_full_tree_state(list/lines, indent) + var/child_info = running_child_index ? " (child [running_child_index]/[length(children)])" : "" + lines += "[indent][get_status_marker()] SELECTOR[child_info]" + for(var/datum/bt_node/child as anything in children) + child.append_full_tree_state(lines, "[indent] ") + +/** + * Subplan node: Runs child and applies configurable restart policies + * when the child completes instead of propagating completion directly. + * + * success_policy: + * BT_SUBPLAN_SUCCEED_ON_SUCCESS (default) propagates BT_SUCCESS when all children succeed. + * BT_SUBPLAN_LOOP_ON_SUCCESS resets all children and returns BT_RUNNING, restarting next tick. + * + * failure_policy: + * BT_SUBPLAN_FAIL_ON_FAILURE (default) propagates BT_FAILURE when a child fails. + * BT_SUBPLAN_LOOP_ON_FAILURE resets all children and returns BT_RUNNING, restarting next tick. + * + * Combining both loop policies creates an infinite loop that only exits via an external observer abort or cancel_current_plan(), so be careful pls + */ +/datum/bt_node/composite/subplan + node_type = BT_NODE_SUBPLAN + /// BT_SUBPLAN_SUCCEED_ON_SUCCESS: propagate success (default). BT_SUBPLAN_LOOP_ON_SUCCESS: restart. + var/success_policy = BT_SUBPLAN_SUCCEED_ON_SUCCESS + /// BT_SUBPLAN_FAIL_ON_FAILURE: propagate failure (default). BT_SUBPLAN_LOOP_ON_FAILURE: restart. + var/failure_policy = BT_SUBPLAN_FAIL_ON_FAILURE + /// Minimum delay before ticking again after a loop policy restarts the child. 0 = immediate (default). + var/loop_delay = 0 + /// world.time when this subplan is next allowed to tick after a loop restart. + var/next_loop_time = 0 + +/datum/bt_node/composite/subplan/tick(datum/ai_controller/controller, seconds_per_tick) + if(loop_delay > 0 && next_loop_time > world.time) + return BT_RUNNING + + var/datum/bt_node/child = LAZYACCESS(children, 1) + if(isnull(child)) + next_loop_time = 0 + return BT_FAILURE + + var/child_result = child.tick(controller, seconds_per_tick) + if(controller.cancelled_during_tick) + return BT_FAILURE + + if(child_result == BT_RUNNING) + return BT_RUNNING + + if(child_result == BT_FAILURE) + if(failure_policy == BT_SUBPLAN_LOOP_ON_FAILURE) + child.reset_tick_state() + if(loop_delay > 0) + next_loop_time = world.time + loop_delay + return BT_RUNNING + next_loop_time = 0 + return BT_FAILURE + + if(success_policy == BT_SUBPLAN_LOOP_ON_SUCCESS) + child.reset_tick_state() + if(loop_delay > 0) + next_loop_time = world.time + loop_delay + return BT_RUNNING + next_loop_time = 0 + return BT_SUCCESS + +/datum/bt_node/composite/subplan/reset_tick_state() + . = ..() + next_loop_time = 0 + +/datum/bt_node/composite/subplan/set_descriptor_children(list/children_descs, datum/ai_controller/controller) + ..() + if(length(children) > 1) + var/datum/bt_node/composite/sequence/legacy_subplan_sequence = new + legacy_subplan_sequence.children = children + children = list(legacy_subplan_sequence) + +/** + * Parallel node: ticks ALL children every planning cycle, regardless of intermediate results. + * Success and failure are determined by the configurable success_policy and failure_policy. + * + * Intended use: run multiple independent branches simultaneously, e.g. action + locomotion + */ +/datum/bt_node/composite/parallel + node_type = BT_NODE_PARALLEL + label = "PARALLEL" + /// BT_PARALLEL_SUCCESS_CHILD_ONE: succeed when child 1 succeeds (default). + /// BT_PARALLEL_SUCCESS_ALL: succeed only when all children succeed. + var/success_policy = BT_PARALLEL_SUCCESS_CHILD_ONE + /// BT_PARALLEL_FAILURE_CHILD_ONE: fail when child 1 fails (default). + /// BT_PARALLEL_FAILURE_ANY: fail when any child fails. + var/failure_policy = BT_PARALLEL_FAILURE_CHILD_ONE + /// If TRUE, children 2+ that complete are reset and reticked + var/repeat_secondary = FALSE + /// Minimum delay before a repeat_secondary child can be re-ticked after completing. 0 = immediate (default). + var/repeat_secondary_delay = 0 + /// world.time values for when each secondary child is next allowed to tick. Null when no delays are active. + var/list/secondary_ready_at = null + /// If TRUE, when child 1 finishes (non-RUNNING), all children 2+ are cancelled and the parallel immediately returns child 1's result. + var/finish_on_primary = FALSE + +/datum/bt_node/composite/parallel/tick(datum/ai_controller/controller, seconds_per_tick) + var/succeeded = 0 + var/failed = 0 + var/primary_result + + for(var/i in 1 to length(children)) + var/datum/bt_node/child = children[i] + + if(i > 1 && repeat_secondary && repeat_secondary_delay > 0 && LAZYACCESS(secondary_ready_at, i) > world.time) + continue // secondary child is waiting out its repeat delay + + var/child_result = child.tick(controller, seconds_per_tick) + if(controller.cancelled_during_tick) + return BT_FAILURE + + if(i == 1) + primary_result = child_result + if(child_result == BT_SUCCESS) + succeeded++ + else if(child_result == BT_FAILURE) + failed++ + else if(repeat_secondary && child_result != BT_RUNNING) + child.reset_tick_state() + if(repeat_secondary_delay > 0) + if(length(secondary_ready_at) < i) + LAZYSETLEN(secondary_ready_at, i) + secondary_ready_at[i] = world.time + repeat_secondary_delay + else + if(child_result == BT_SUCCESS) + succeeded++ + else if(child_result == BT_FAILURE) + failed++ + + if(finish_on_primary && primary_result != BT_RUNNING) + // Secondaries may be mid-RUNNING here, so the reset must recurse to cancel any deeper active behaviors. + for(var/i in 2 to length(children)) + var/datum/bt_node/child = children[i] + child.reset_subtree_tick_states() + return primary_result + + if((failure_policy == BT_PARALLEL_FAILURE_CHILD_ONE && primary_result == BT_FAILURE) || \ + (failure_policy == BT_PARALLEL_FAILURE_ANY && failed > 0)) + return BT_FAILURE + if((success_policy == BT_PARALLEL_SUCCESS_CHILD_ONE && primary_result == BT_SUCCESS) || \ + (success_policy == BT_PARALLEL_SUCCESS_ALL && succeeded == length(children))) + return BT_SUCCESS + return BT_RUNNING + +/datum/bt_node/composite/parallel/reset_tick_state() + . = ..() + secondary_ready_at = null + +/datum/bt_node/composite/parallel/append_active_nodes(list/lines, indent) + for(var/datum/bt_node/child as anything in children) + if(child.has_active_descendants()) + child.append_active_nodes(lines, indent) + +/datum/bt_node/composite/parallel/append_full_tree_state(list/lines, indent) + lines += "[indent][get_status_marker()] PARALLEL" + for(var/datum/bt_node/child as anything in children) + child.append_full_tree_state(lines, "[indent] ") diff --git a/code/datums/ai/_ai_bt_decorators.dm b/code/datums/ai/_ai_bt_decorators.dm new file mode 100644 index 00000000000..eec04a978de --- /dev/null +++ b/code/datums/ai/_ai_bt_decorators.dm @@ -0,0 +1,242 @@ +/** + * Base decorator node. Wraps a single child with a condition check. + * + * + * Supports observer aborts: register to watch specific signals, which triggers a re-check of the condition, potentially aborting the plan depending on the observer_abort settings. + */ +/datum/bt_node/decorator + node_type = BT_NODE_DECORATOR + /// Typepath of the single child node. Resolved to an instance at tree construction. + var/child_typepath = null + /// Resolved child instance. Populated at tree construction. Do not set directly. + var/datum/bt_node/child = null + /// Observer abort mode. Controls reactive re-planning when watched keys change. BT_ABORT_NONE (default) means no reactivity. BT_ABORT_SELF triggers re-plan if we're inside one of our children and the condition changes, while BT_ABORT_LOWER_PRIORITY triggers re-plan if we are in a lower priority (e.g. further to the right) node and the condition changes. BT_ABORT_BOTH does both. + var/observer_abort = BT_ABORT_NONE + /// If TRUE, the result of check_condition() is inverted before gating the child. + var/invert = FALSE + /// Whether the child is currently BT_RUNNING. This makes tick() skip check_condition() and delegate directly to child.tick(). + var/child_active = FALSE + /// Set to TRUE once register_observe_signals() has been called for this instance. + var/observers_registered = FALSE + /// Set to TRUE when register_observe_signals() registered at least one signal. If this is not true but we are observing; then we need to check the condition every tick; not efficient, but allows for reactivity. + var/has_observer_signals = FALSE + /// Last result seen by poll_condition(). null = not yet polled. Used to detect condition changes when no signal is available. + var/last_poll_result = null + /// TRUE when this decorator is registered in the controller's polling_observers list. + var/is_polled = FALSE + /// When polling (no observer signals), minimum deciseconds between condition re-evaluations. 0 = every controller tick. Please don't run viewers() every tick bro. + var/polling_rate = 0 + /// world.time of the last poll_condition() evaluation. Only meaningful when polling_rate > 0. + VAR_PRIVATE/last_poll_time = 0 + + +/datum/bt_node/decorator/Destroy() + if(observers_registered) + unregister_observe_signals(owning_controller?.pawn) + if(is_polled) + LAZYREMOVE(owning_controller?.polling_observers, src) + is_polled = FALSE + observers_registered = FALSE + has_observer_signals = FALSE + QDEL_NULL(child) + return ..() + +/datum/bt_node/decorator/get_children() + return child ? list(child) : null + +/datum/bt_node/decorator/has_active_descendants() + return child && child.has_active_descendants() + +/datum/bt_node/decorator/finalize_node(datum/ai_controller/controller, list/to_visit) + ..() + if(child) + child.parent_node = src + to_visit += child + +/datum/bt_node/decorator/append_active_nodes(list/lines, indent) + if(child && child.has_active_descendants()) + lines += "[indent][label]" + child.append_active_nodes(lines, "[indent] ") + +/datum/bt_node/decorator/set_descriptor_children(list/children_descs, datum/ai_controller/controller) + var/datum/bt_node/resolved = controller.get_or_build_node(children_descs[1]) + if(!isnull(resolved)) + child = resolved + +/datum/bt_node/decorator/collect_reset_children(list/to_visit) + if(child) + to_visit += child + +/datum/bt_node/decorator/append_full_tree_state(list/lines, indent) + var/observer_text = "" + if(observer_abort != BT_ABORT_NONE) + var/abort_name = "" + if(observer_abort == BT_ABORT_SELF) + abort_name = "SELF" + else if(observer_abort == BT_ABORT_LOWER_PRIORITY) + abort_name = "LOWER" + else if(observer_abort == BT_ABORT_BOTH) + abort_name = "BOTH" + observer_text = " (abort-[abort_name])" + lines += "[indent][get_status_marker()] [label][observer_text]" + if(child) + child.append_full_tree_state(lines, "[indent] ") + +/datum/bt_node/decorator/tick(datum/ai_controller/controller, seconds_per_tick) + if(!observers_registered) + observers_registered = TRUE + if(observer_abort != BT_ABORT_NONE) + has_observer_signals = register_observe_signals(controller.pawn) + if(!has_observer_signals) + is_polled = TRUE + LAZYADDASSOC(controller.polling_observers, src, TRUE) + + var/child_ticked = FALSE + var/result + if(!child) + return BT_FAILURE + var/no_ticking_condition = observer_abort == BT_ABORT_NONE || has_observer_signals + if((no_ticking_condition || is_polled) && child_active) + child_ticked = TRUE + result = child.tick(controller, seconds_per_tick) + else if(check_condition(controller) == invert) + result = BT_FAILURE + else + child_ticked = TRUE + result = child.tick(controller, seconds_per_tick) + + if(controller.cancelled_during_tick) + child_active = FALSE + return BT_FAILURE + + if(no_ticking_condition || is_polled) + child_active = (result == BT_RUNNING) + if(child_ticked && !child_active) + on_child_complete(controller, result) + + return result + +/** + * Called when the child finishes (returns a non-RUNNING result after being ticked). + * NOT called when the condition gate blocks the child, or when the tree is cancelled mid-tick. + */ +/datum/bt_node/decorator/proc/on_child_complete(datum/ai_controller/controller, result) + return + +/** + * Override to implement custom condition logic. + * Return TRUE to allow child.tick() to proceed, FALSE to return BT_FAILURE immediately. + */ +/datum/bt_node/decorator/proc/check_condition(datum/ai_controller/controller) + return TRUE + +/** + * Proc called by the observer system when a watched key changes. + * Return TRUE if the decorator's condition would pass, FALSE otherwise. + */ +/datum/bt_node/decorator/proc/evaluate_for_observer(datum/ai_controller/controller) + return check_condition(controller) != invert + +/** + * Called by the controller's observer handler when a watched blackboard key changes. + * Re-evaluates evaluate_for_observer() and aborts based on observer_abort policy. + * + * BT_ABORT_SELF: condition became FALSE and we're running our children > cancel actions + * BT_ABORT_LOWER_PRIORITY: condition became TRUE and we're running lower priority nodes > cancel actions + */ +/// Called by the controller's polling loop for decorators that have no signal observers. +/// Sets a baseline on first call, then fires on_observed_change() only when the result changes. +/datum/bt_node/decorator/proc/poll_condition(datum/ai_controller/controller) + if(polling_rate && last_poll_time + polling_rate > world.time) + return + last_poll_time = world.time + var/current = evaluate_for_observer(controller) + if(last_poll_result == null) + last_poll_result = current + return + if(current != last_poll_result) + last_poll_result = current + on_observed_change(controller, null) + +/datum/bt_node/decorator/proc/on_observed_change(datum/ai_controller/controller, key) + var/condition_result = evaluate_for_observer(controller) + + if(!condition_result && (observer_abort & BT_ABORT_SELF)) + var/active = controller.active_execution_index + if(!execution_index || (active >= execution_index && active <= last_execution_index)) + EVLOG_TEXT(controller, EVLOG_CATEGORY_AI_DECISIONMAKING, "[controller.pawn] [type]: ABORT_SELF on key=[key] condition lost, replanning") + controller.cancel_current_plan() + + if(condition_result && (observer_abort & BT_ABORT_LOWER_PRIORITY)) + var/active = controller.active_execution_index + if(!execution_index || !active || active > last_execution_index) + EVLOG_TEXT(controller, EVLOG_CATEGORY_AI_DECISIONMAKING, "[controller.pawn] [type]: ABORT_LOWER on key=[key] condition gained, replanning") + controller.cancel_current_plan() + +/datum/bt_node/decorator/reset_tick_state() + if(observers_registered) + unregister_observe_signals(owning_controller?.pawn) + if(is_polled) + LAZYREMOVE(owning_controller?.polling_observers, src) + is_polled = FALSE + observers_registered = FALSE + has_observer_signals = FALSE + last_poll_result = null + last_poll_time = 0 + child_active = FALSE + ..() + +/datum/bt_node/decorator/assign_execution_indices(counter) + execution_index = counter + counter++ + if(child) + counter = child.assign_execution_indices(counter) + last_execution_index = counter - 1 + return counter + +/// Override to register all signal observers for this decorator. Return TRUE if any were registered. If a decorator does not handle this and we have an observer_abort mode that isn't BT_ABORT_NONE, the system will fall back to ticking the condition every tick, which is less efficient but allows for reactivity without signals. +/datum/bt_node/decorator/proc/register_observe_signals(atom/pawn) + return FALSE + +/// Override to unregister all observers registered by register_observe_signals(). +/datum/bt_node/decorator/proc/unregister_observe_signals(atom/pawn) + return + +/// Shared signal handler. Calls on_observed_change() with owning_controller. +/datum/bt_node/decorator/proc/on_signal_changed(atom/source, ...) + SIGNAL_HANDLER + if(owning_controller) + on_observed_change(owning_controller, null) + + +/// Returns TRUE if the blackboard key holds a non-null, non-deleted value. +/datum/bt_node/decorator/proc/bb_key_exists(datum/ai_controller/controller, key) + return controller.blackboard_key_exists(key) + +/// Gates on whether the named override slot currently has an active override installed. +/// Observes COMSIG_AI_OVERRIDE_SLOT_CHANGED so it reacts immediately when a command is set or cleared. +/datum/bt_node/decorator/override_id_set + /// SUBPLAN_ID_* constant matching the override slot to watch. + var/override_id = null + +/datum/bt_node/decorator/override_id_set/check_condition(datum/ai_controller/controller) + var/datum/bt_node/subtree/potential_subtree = LAZYACCESS(controller.override_slots, override_id) + return !isnull(potential_subtree.override_node) + +/datum/bt_node/decorator/override_id_set/register_observe_signals(atom/pawn) + if(isnull(override_id)) + return FALSE + RegisterSignal(pawn, COMSIG_AI_OVERRIDE_SLOT_CHANGED(override_id), PROC_REF(on_signal_changed)) + return TRUE + +/datum/bt_node/decorator/override_id_set/unregister_observe_signals(atom/pawn) + if(!isnull(override_id)) + UnregisterSignal(pawn, COMSIG_AI_OVERRIDE_SLOT_CHANGED(override_id)) + +/// Returns TRUE if the blackboard value at key equals the given value. +/datum/bt_node/decorator/proc/bb_key_equals(datum/ai_controller/controller, key, value) + return controller.blackboard[key] == value + +/// Returns TRUE if the blackboard value at key is strictly greater than threshold. +/datum/bt_node/decorator/proc/bb_key_greater(datum/ai_controller/controller, key, threshold) + return controller.blackboard[key] > threshold diff --git a/code/datums/ai/_ai_bt_node.dm b/code/datums/ai/_ai_bt_node.dm new file mode 100644 index 00000000000..fdff4feae20 --- /dev/null +++ b/code/datums/ai/_ai_bt_node.dm @@ -0,0 +1,109 @@ +///Base node for behavior tree nodes +/datum/bt_node + /// Node type identifier. + var/node_type = BT_NODE_LEAF + /// Pre-order depth-first index of this node in the tree. Assigned by finalize_tree(). + var/execution_index = 0 + /// Index of the last descendant node in this subtree. Equal to execution_index for leaves. + var/last_execution_index = 0 + /// Reference to this node's parent in the resolved tree. Set by ai_controller/finalize_tree(). + /// Null for root-level nodes. + var/datum/bt_node/parent_node = null + ///Owning controller for this node + var/datum/ai_controller/owning_controller = null + /// Short display label, set at New() by stripping standard path prefixes from the type. + var/label = "" + +/datum/bt_node/New() + . = ..() + if(!label) + var/t = "[type]" + t = replacetext(t, "/datum/bt_node/decorator/", "") + t = replacetext(t, "/datum/bt_node/ai_behavior/", "") + t = replacetext(t, "/datum/bt_node/subtree/", "") + label = t + +///Ticked by the ai_controller. Returns BT_SUCCESS, BT_FAILURE, or BT_RUNNING which can change how the parent responds. +/datum/bt_node/proc/tick(datum/ai_controller/controller, seconds_per_tick) + SHOULD_NOT_SLEEP(TRUE) + return BT_FAILURE + +/// Resets per-tick state for this node instance. Override in subtypes that hold tick state. +/datum/bt_node/proc/reset_tick_state() + return + +/// Resets this node and all of its descendants, cancelling any behaviors still running in the subtree. +/datum/bt_node/proc/reset_subtree_tick_states() + var/list/to_visit = list(src) + var/index = 1 + while(index <= length(to_visit)) + var/datum/bt_node/node = to_visit[index++] + node.reset_tick_state() + node.collect_reset_children(to_visit) + +/** + * Assigns pre-order depth-first execution indices to this node and its subtree. + * Called once per controller tree by finalize_tree(). + */ +/datum/bt_node/proc/assign_execution_indices(counter) + execution_index = counter + last_execution_index = counter + return counter + 1 + +/// Apply a configuration list to this node instance by assigning vars directly. +/datum/bt_node/proc/configure(list/config) + for(var/var_name in config) + vars[var_name] = config[var_name] + +/** + * Returns the list of direct child bt_node instances for tree traversal. + * Returns null for leaf nodes (default). Overridden in composites, decorators, and subtrees. + */ +/datum/bt_node/proc/get_children() + return null + +/// Returns TRUE if this node or any descendant has an active (running) ai_behavior leaf. +/datum/bt_node/proc/has_active_descendants() + return FALSE + +/// Walks descendants to find the node with the given execution_index. Returns null if not found. +/datum/bt_node/proc/find_by_index(target_index) + if(execution_index == target_index) + return src + var/list/ch = get_children() + if(!ch) + return null + for(var/datum/bt_node/child as anything in ch) + var/found = child.find_by_index(target_index) + if(found) + return found + return null + +/// Appends this node's active/upcoming state to lines for display. No-op for plain leaf nodes. +/datum/bt_node/proc/append_active_nodes(list/lines, indent) + return + +/// Called during finalize_tree() to set owning_controller, register overrides, and enqueue children. +/datum/bt_node/proc/finalize_node(datum/ai_controller/controller, list/to_visit) + owning_controller = controller + +/// Called during build_node_from_descriptor() to resolve and assign child nodes from JSON descriptors. +/datum/bt_node/proc/set_descriptor_children(list/children_descs, datum/ai_controller/controller) + return + +/// Returns a single-character status marker for display. Overridden by ai_behavior to check running. +/datum/bt_node/proc/get_status_marker() + return "o" + +/// Appends this node's full tree state (status + label + children) to lines for display. +/datum/bt_node/proc/append_full_tree_state(list/lines, indent) + lines += "[indent][get_status_marker()] [label]" + +/// Adds all children that must be visited during reset to to_visit. No-op for leaf nodes. +/datum/bt_node/proc/collect_reset_children(list/to_visit) + return + +/datum/bt_node/Destroy() + parent_node = null + owning_controller = null + return ..() diff --git a/code/datums/ai/_ai_bt_subtree.dm b/code/datums/ai/_ai_bt_subtree.dm new file mode 100644 index 00000000000..ea9874e78f7 --- /dev/null +++ b/code/datums/ai/_ai_bt_subtree.dm @@ -0,0 +1,88 @@ +/** + * Subtree node: a named, re-usable BT subgraph. + * + * Subtypes override New() to build an arbitrary internal tree and assign its root node + * to `root`. tick() delegates entirely to root.tick() and returns whatever it returns. + * + * This decouples the subtree's identity (its type path) from any specific composite + * semantics (selector/parallel/sequence). The root can be any node type. + * + * The controller's tree-walk helpers (setup_bt_observers, reset_bt_tick_states) descend + * through the `root` pointer to reach all internal nodes. + */ +/datum/bt_node/subtree + node_type = BT_NODE_SUBTREE + /// Repo-relative path to the .bt.json source file for this subtree (e.g. "code/datums/ai/bots/bot_patrol.bt.json"). + /// resolve_node_children() derives the compiled path from this and loads the tree at runtime. + var/behavior_tree_json = null + /// list of BT node descriptors defining this subtree's root. + /// resolve_node_children() builds `root` from this during tree construction. + var/list/behavior_nodes = null + /// The internal root node. Populated by resolve_node_children(). Do not set directly. + var/datum/bt_node/root = null + /// Set this to allow runtime overriding of this subtree, useful for things like pet commands! + var/override_id = null + /// Active override subtree. When set, tick() delegates to this node instead of root. + /// Set to null to deactivate the override. Managed by set_behavior_tree_override() only. + var/datum/bt_node/subtree/override_node = null + ///Any bindings this subtree has; assigned by the json + var/list/bindings = null + +/datum/bt_node/subtree/Destroy() + QDEL_NULL(root) + QDEL_NULL(override_node) + return ..() + +/datum/bt_node/subtree/tick(datum/ai_controller/controller, seconds_per_tick) + if(override_node) + return override_node.tick(controller, seconds_per_tick) + if(!root) + return BT_FAILURE + return root.tick(controller, seconds_per_tick) + +/datum/bt_node/subtree/get_children() + if(override_node) + return override_node.root ? list(override_node.root) : null + return root ? list(root) : null + +/datum/bt_node/subtree/has_active_descendants() + if(override_node) + return override_node.has_active_descendants() + return root && root.has_active_descendants() + +/datum/bt_node/subtree/finalize_node(datum/ai_controller/controller, list/to_visit) + ..() + if(!isnull(override_id)) + LAZYINITLIST(controller.override_slots) + controller.override_slots[override_id] = src + if(root) + root.parent_node = src + to_visit += root + if(override_node) + override_node.parent_node = src + to_visit += override_node + +/datum/bt_node/subtree/append_active_nodes(list/lines, indent) + if(root && root.has_active_descendants()) + root.append_active_nodes(lines, indent) + +/datum/bt_node/subtree/collect_reset_children(list/to_visit) + if(root) + to_visit += root + if(override_node) + to_visit += override_node + +/datum/bt_node/subtree/append_full_tree_state(list/lines, indent) + ..() + if(root) + root.append_full_tree_state(lines, "[indent] ") + +/datum/bt_node/subtree/assign_execution_indices(counter) + execution_index = counter + counter++ + if(root) + counter = root.assign_execution_indices(counter) + if(override_node) + counter = override_node.assign_execution_indices(counter) + last_execution_index = counter - 1 + return counter diff --git a/code/datums/ai/_ai_controller.dm b/code/datums/ai/_ai_controller.dm index 0438fa305ed..98cd64f9395 100644 --- a/code/datums/ai/_ai_controller.dm +++ b/code/datums/ai/_ai_controller.dm @@ -15,35 +15,38 @@ multiple modular subtrees with behaviors * DO NOT set values in the blackboard directly, and especially not if you're adding a datum reference to this! * Use the setters, this is important for reference handing. */ - var/list/blackboard = list() + var/alist/blackboard = alist() ///Bitfield of traits for this AI to handle extra behavior var/ai_traits = DEFAULT_AI_FLAGS - ///Current actions planned to be performed by the AI in the upcoming plan - var/list/planned_behaviors = list() - ///Current actions being performed by the AI. - var/list/current_behaviors = list() - ///Current actions and their respective last time ran as an assoc list. - var/list/behavior_cooldowns = list() ///Current status of AI (OFF/ON) var/ai_status - ///Current movement target of the AI, generally set by decision making. - var/atom/current_movement_target - ///Identifier for what last touched our movement target, so it can be cleared conditionally - var/movement_target_source - ///Stored arguments for behaviors given during their initial creation - var/list/behavior_args = list() + ///Set by force_ai_off() when an outside system deliberately disables this AI. While TRUE, get_expected_ai_status() always returns AI_STATUS_OFF, so status recalculations (stat changes, z changes, client login/logout) cannot re-enable us. Cleared via clear_forced_off(). + var/forced_off = FALSE ///Tracks recent pathing attempts, if we fail too many in a row we fail our current plans. var/consecutive_pathing_attempts ///Can the AI remain in control if there is a client? var/continue_processing_when_client = FALSE ///distance to give up on target var/max_target_distance = 14 - ///All subtrees this AI has available, will run them in order, so make sure they're in the order you want them to run. On initialization of this type, it will start as a typepath(s) and get converted to references of ai_subtrees found in SSai_controllers when init_subtrees() is called - var/list/planning_subtrees - - ///The idle behavior this AI performs when it has no actions. - var/datum/idle_behavior/idle_behavior = null + /// Repo-relative path to the .bt.json source file for this controller (e.g. "code/datums/ai/basic_mobs/cleanbot.bt.json"). + /// initialize_behavior_tree() derives the compiled path from this and loads the BT tree at runtime. + var/behavior_tree_json = null + ///The root of our tree, which will contain + var/list/behavior_nodes + /// Execution index of the leaf node currently returning BT_RUNNING. 0 = nothing active. + var/active_execution_index = 0 + /// Set to TRUE by cancel_current_plan() when it fires mid-tick. Checked by composites to abort the current tick loop early, preventing running_child_index from being re-established after a reset. Cleared at the start of SelectBehaviors(). + var/cancelled_during_tick = FALSE + /// Draining log of all leaf execution indices that fired since the last bt_viewer poll. Null when no viewer is attached. + var/list/bt_execution_log = null + /// assoc list of override_id -> /datum/bt_node/subtree for runtime subtree replacement. + /// Populated by finalize_tree() when subtrees with override_id are found. Null until then. + var/list/override_slots = null + /// Decorators in polling mode (observer_abort set, no signal registered). Iterated after each SelectBehaviors tick so their condition is re-evaluated even when skipped by composite resume logic. + var/list/polling_observers = null + /// world.time of our last SelectBehaviors() tick from SSai_controllers. Used to derive the real seconds_per_tick under load; 0 means no tick since the last status change, so the first tick falls back to the subsystem wait. + var/last_bt_tick = 0 ///our current cell grid var/datum/cell_tracker/our_cells @@ -56,8 +59,6 @@ multiple modular subtrees with behaviors // The variables below are fucking stupid and should be put into the blackboard at some point. ///AI paused time var/paused_until = 0 - ///Can this AI idle? - var/can_idle = TRUE ///What distance should we be checking for interesting things when considering idling/deidling? Defaults to AI_DEFAULT_INTERESTING_DIST var/interesting_dist = AI_DEFAULT_INTERESTING_DIST /// TRUE if we're able to run, FALSE if we aren't @@ -65,18 +66,10 @@ multiple modular subtrees with behaviors /// Make sure you hook update_able_to_run() in setup_able_to_run() to whatever parameters changing that you added /// Otherwise we will not pay attention to them changing var/able_to_run = FALSE - /// are we even able to plan? - var/able_to_plan = TRUE - /// are we currently on failed planning timeout? - var/on_failed_planning_timeout = FALSE - /datum/ai_controller/New(atom/new_pawn) change_ai_movement_type(ai_movement) - init_subtrees() - - if(idle_behavior) - idle_behavior = SSidle_ai_behaviors.idle_behaviors[idle_behavior] + initialize_behavior_tree() if(!isnull(new_pawn)) // unit tests need the ai_controller to exist in isolation due to list schenanigans i hate it here PossessPawn(new_pawn) @@ -84,51 +77,232 @@ multiple modular subtrees with behaviors /datum/ai_controller/Destroy(force) UnpossessPawn(FALSE) if(ai_status) - GLOB.ai_controllers_by_status[ai_status] -= src + SSai_controllers.ai_controllers_by_status[ai_status] -= src for(var/datum/controller/subsystem/ai_controllers/controller_subsystem in Master.subsystems) if(controller_subsystem.planning_status == ai_status) controller_subsystem.currentrun -= src break our_cells = null - set_movement_target(type, null) if(ai_movement.moving_controllers[src]) ai_movement.stop_moving_towards(src) + QDEL_LIST(behavior_nodes) return ..() -///Sets the current movement target, with an optional param to override the movement behavior -/datum/ai_controller/proc/set_movement_target(source, atom/target, datum/ai_movement/new_movement) - if(current_movement_target) - UnregisterSignal(current_movement_target, list(COMSIG_MOVABLE_MOVED, COMSIG_PREQDELETED)) - if(!isnull(target) && !isatom(target)) - stack_trace("[pawn]'s current movement target is not an atom, rather a [target.type]! Did you accidentally set it to a weakref?") - CancelActions() - return - movement_target_source = source - current_movement_target = target - if(!isnull(current_movement_target)) - RegisterSignal(current_movement_target, COMSIG_MOVABLE_MOVED, PROC_REF(on_movement_target_move)) - RegisterSignal(current_movement_target, COMSIG_PREQDELETED, PROC_REF(on_movement_target_delete)) - if(new_movement) - change_ai_movement_type(new_movement) - ///Overrides the current ai_movement of this controller with a new one /datum/ai_controller/proc/change_ai_movement_type(datum/ai_movement/new_movement) ai_movement = SSai_movement.movement_types[new_movement] -///Completely replaces the planning_subtrees with a new set based on argument provided, list provided must contain specifically typepaths -/datum/ai_controller/proc/replace_planning_subtrees(list/typepaths_of_new_subtrees) - planning_subtrees = typepaths_of_new_subtrees - init_subtrees() +///Completely replaces the behavior_nodes with a new set based on argument provided. +/datum/ai_controller/proc/replace_behavior_nodes(list/typepaths_of_new_subtrees) + var/list/old_nodes = behavior_nodes + behavior_nodes = typepaths_of_new_subtrees + initialize_behavior_tree() + QDEL_LIST(old_nodes) -///Loops over the subtrees in planning_subtrees and looks at the ai_controllers to grab a reference, ENSURE planning_subtrees ARE TYPEPATHS AND NOT INSTANCES/REFERENCES BEFORE EXECUTING THIS -/datum/ai_controller/proc/init_subtrees() - if(!LAZYLEN(planning_subtrees)) +/// Resolves the children/child of a composite or decorator node, creating configured instances. +/// Safe to call on any node type; non-composite/non-decorator nodes are a no-op. +/datum/ai_controller/proc/resolve_node_children(datum/bt_node/node) + if(istype(node, /datum/bt_node/composite)) + var/datum/bt_node/composite/comp = node + if(!LAZYLEN(comp.children_typepaths) || LAZYLEN(comp.children)) + return + var/list/resolved_children = list() + for(var/child_type in comp.children_typepaths) + var/list/config = comp.children_typepaths[child_type] + var/datum/bt_node/child = resolve_child_node(child_type, config) + if(isnull(child)) + stack_trace("BT composite [node.type] references unknown child type [child_type]") + continue + resolved_children += child + comp.children = resolved_children + else if(istype(node, /datum/bt_node/decorator)) + var/datum/bt_node/decorator/dec = node + if(isnull(dec.child_typepath) || !isnull(dec.child)) + return + dec.child = resolve_child_node(dec.child_typepath, null) + if(isnull(dec.child)) + stack_trace("BT decorator [node.type] references unknown child type [dec.child_typepath]") + else if(istype(node, /datum/bt_node/subtree)) + var/datum/bt_node/subtree/sub = node + if(!isnull(sub.behavior_nodes) && isnull(sub.root)) + sub.root = build_node_from_descriptor(sub.behavior_nodes) + else if(!isnull(sub.behavior_tree_json) && isnull(sub.root)) + var/file = file(BT_COMPILED_PATH(sub.behavior_tree_json)) + var/list/raw_desc = json_decode(file2text(file)) + if(LAZYLEN(sub.bindings) || !isnull(raw_desc[BT_DESC_BINDINGS])) + raw_desc = apply_bindings_to_descriptor(raw_desc, sub.bindings) + sub.root = build_node_from_descriptor(raw_desc) + +// Always creates a fresh instance regardless of whether config is provided. +/datum/ai_controller/proc/resolve_child_node(child_type, list/config) + if(!ispath(child_type, /datum/bt_node)) + return null + var/datum/bt_node/child = new child_type + if(config) + child.configure(config) + resolve_node_children(child) + return child + +/datum/ai_controller/proc/get_or_build_node(entry) + if(ispath(entry)) + if(!ispath(entry, /datum/bt_node)) + stack_trace("get_or_build_node() received non-BT typepath: [entry]") + return null + var/datum/bt_node/node = new entry + resolve_node_children(node) + return node + if(islist(entry)) + return build_node_from_descriptor(entry) + stack_trace("get_or_build_node() received unexpected entry type: [entry]") + return null + +///Loads and decodes a compiled BT JSON file into a node tree. +/datum/ai_controller/proc/load_tree_from_json(path) + var/file = file(path) + var/list/desc = json_decode(file2text(file)) + return build_node_from_descriptor(desc) + +/** + * Merges call-site binding overrides with the subtree's declared defaults, + * then substitutes all $name placeholders in the descriptor tree. + * Returns a new descriptor with BT_DESC_BINDINGS stripped and placeholders resolved. + */ +/datum/ai_controller/proc/apply_bindings_to_descriptor(list/desc, list/call_site_bindings) + var/list/merged = list() + var/list/declared = desc[BT_DESC_BINDINGS] + for(var/name in declared) + merged[name] = declared[name]["default"] + for(var/name in call_site_bindings) + merged[name] = call_site_bindings[name] + return _substitute_bindings(desc, merged) + +/// Recursively walks a descriptor list, replacing "$name" strings with their bound values. +/datum/ai_controller/proc/_substitute_bindings(list/desc, list/merged) + var/list/out = list() + for(var/key in desc) + if(key == BT_DESC_BINDINGS) + continue + var/value = desc[key] + if(islist(value)) + out[key] = _substitute_bindings_in_list(value, merged) + else if(istext(value) && copytext(value, 1, 2) == "$") + var/binding_name = copytext(value, 2) + out[key] = isnull(merged[binding_name]) ? value : merged[binding_name] + else + out[key] = value + return out + +/// Substitutes bindings inside a descriptor value list, preserving assoc entries +/datum/ai_controller/proc/_substitute_bindings_in_list(list/input, list/merged) + var/list/resolved_list = list() + for(var/item in input) + var/assoc_value = isnum(item) ? null : input[item] + if(!isnull(assoc_value)) + if(islist(assoc_value)) + resolved_list[item] = _substitute_bindings_in_list(assoc_value, merged) //recursion baby + else if(istext(assoc_value) && copytext(assoc_value, 1, 2) == "$") + var/binding_name = copytext(assoc_value, 2) + resolved_list[item] = isnull(merged[binding_name]) ? assoc_value : merged[binding_name] + else + resolved_list[item] = assoc_value + else if(islist(item)) + resolved_list += list(_substitute_bindings(item, merged)) + else if(istext(item) && copytext(item, 1, 2) == "$") + var/binding_name = copytext(item, 2) + var/resolved = isnull(merged[binding_name]) ? item : merged[binding_name] + if(islist(resolved)) + resolved_list += list(resolved) + else + resolved_list += resolved + else + resolved_list += item + return resolved_list + +/** + * Recursively builds a BT node tree from a descriptor list. + * BT_DESC_TYPE and BT_DESC_CHILDREN are consumed internally; all other keys are written + * as vars onto the node. String values starting with "/" are resolved via text2path so + * typepath args (e.g. "/datum/ai_movement/basic_avoidance") arrive as actual types. + * If you put / in a string then yeah that might cause issues, should probably fix that later! + */ +/datum/ai_controller/proc/build_node_from_descriptor(list/desc) + var/raw_type = desc[BT_DESC_TYPE] + if(!raw_type) // This can happen if we have an overriden type with no binding. (e.g. subtrees not being overriden and default to null) + return null + var/node_type = ispath(raw_type) ? raw_type : text2path(raw_type) + if(isnull(node_type)) + stack_trace("build_node_from_descriptor(): unknown typepath '[raw_type]'") + return null + var/datum/bt_node/node = new node_type + for(var/key in desc) + if(key == BT_DESC_TYPE || key == BT_DESC_CHILDREN || key == BT_DESC_BINDINGS) + continue + var/value = desc[key] + if(islist(value)) + var/list/resolved = value + for(var/i in 1 to length(resolved)) + if(istext(resolved[i])) + var/as_path = text2path(resolved[i]) + if(!isnull(as_path)) + resolved[i] = as_path + else if(istext(value)) + var/as_path = text2path(value) + if(!isnull(as_path)) + value = as_path + node.vars[key] = value + resolve_node_children(node) + var/list/children_descs = desc[BT_DESC_CHILDREN] + if(LAZYLEN(children_descs)) + node.set_descriptor_children(children_descs, src) + return node + +/// Builds the per-controller BT node tree from behavior_nodes typepaths or descriptors, then finalizes it. +/datum/ai_controller/proc/initialize_behavior_tree() + if(!isnull(behavior_tree_json) && !LAZYLEN(behavior_nodes)) + var/compiled_path = BT_COMPILED_PATH(behavior_tree_json) //Find the compiled version of this BT + var/datum/bt_node/root = load_tree_from_json(compiled_path) + if(isnull(root)) + stack_trace("[type] failed to load behavior tree from compiled JSON: [compiled_path]") + return + behavior_nodes = list(root) + finalize_tree() + return + if(!LAZYLEN(behavior_nodes)) return var/list/temp_subtree_list = list() - for(var/subtree in planning_subtrees) - var/subtree_instance = GLOB.ai_subtrees[subtree] - temp_subtree_list += subtree_instance - planning_subtrees = temp_subtree_list + if(!isnull(behavior_nodes[BT_DESC_TYPE])) + var/datum/bt_node/node_instance = get_or_build_node(behavior_nodes) + if(isnull(node_instance)) + stack_trace("[type]'s behavior_nodes BT descriptor could not be built") + else + temp_subtree_list += node_instance + else + for(var/entry in behavior_nodes) + var/datum/bt_node/node_instance = get_or_build_node(entry) + if(isnull(node_instance)) + stack_trace("[type]'s behavior_nodes contains unknown entry: [entry]") + continue + temp_subtree_list += node_instance + behavior_nodes = temp_subtree_list + finalize_tree() + +/// Walks the resolved tree to set owning_controller and parent_node on all nodes, populates +/// override_slots, and assigns pre-order execution indices. Called after initialize_behavior_tree() and +/// after set_behavior_tree_override() installs or removes an override node. +/datum/ai_controller/proc/finalize_tree() + if(!LAZYLEN(behavior_nodes)) + return + override_slots = null + var/list/to_visit = behavior_nodes.Copy() + for(var/datum/bt_node/root in behavior_nodes) + root.parent_node = null + var/index = 1 + while(index <= length(to_visit)) //while loop so we can recursively keep populating this list + var/datum/bt_node/node = to_visit[index++] + node.finalize_node(src, to_visit) + var/counter = 1 + for(var/datum/bt_node/root in behavior_nodes) + counter = root.assign_execution_indices(counter) ///Proc to move from one pawn to another, this will destroy the target's existing controller. /datum/ai_controller/proc/PossessPawn(atom/new_pawn) @@ -145,10 +319,11 @@ multiple modular subtrees with behaviors pawn = new_pawn pawn.ai_controller = src + set_blackboard_key(BB_MY_PAWN, pawn, FALSE) //Don't track the datum we already handle qdel of pawn here. var/turf/pawn_turf = get_turf(pawn) if(pawn_turf) - GLOB.ai_controllers_by_zlevel[pawn_turf.z] += src + SSai_controllers.ai_controllers_by_zlevel[pawn_turf.z] += src SEND_SIGNAL(src, COMSIG_AI_CONTROLLER_POSSESSED_PAWN) @@ -159,32 +334,19 @@ multiple modular subtrees with behaviors RegisterSignal(pawn, COMSIG_QDELETING, PROC_REF(on_pawn_qdeleted)) RegisterSignal(pawn, COMSIG_EVLOGGING_ENABLED, PROC_REF(on_pawn_evlogging_enabled)) RegisterSignal(pawn, COMSIG_EVLOGGING_DISABLED, PROC_REF(on_pawn_evlogging_disabled)) - update_able_to_run() - setup_able_to_run() our_cells = new(interesting_dist, interesting_dist, 1) set_new_cells() + update_able_to_run() + setup_able_to_run() + RegisterSignal(pawn, COMSIG_MOVABLE_MOVED, PROC_REF(update_grid)) /datum/ai_controller/proc/update_grid(datum/source, datum/spatial_grid_cell/new_cell) SIGNAL_HANDLER set_new_cells() - if(current_movement_target) - check_target_max_distance() - -/datum/ai_controller/proc/on_movement_target_move(atom/source) - SIGNAL_HANDLER - check_target_max_distance() - -/datum/ai_controller/proc/on_movement_target_delete(atom/source) - SIGNAL_HANDLER - set_movement_target(source = type, target = null) - -/datum/ai_controller/proc/check_target_max_distance() - if(get_dist(current_movement_target, pawn) > max_target_distance) - CancelActions() /datum/ai_controller/proc/set_new_cells() if(isnull(our_cells)) @@ -206,14 +368,16 @@ multiple modular subtrees with behaviors recalculate_idle() -/datum/ai_controller/proc/should_idle() - if(!can_idle || isnull(our_cells)) +///Returns TRUE if a living mob with a client is in one of our tracked spatial grid cells +/datum/ai_controller/proc/has_nearby_client() + if(isnull(our_cells)) return FALSE for(var/datum/spatial_grid_cell/grid as anything in our_cells.member_cells) if(locate(/mob/living) in grid.client_contents) - return FALSE - return TRUE + return TRUE + return FALSE +///Check if mob should go into idle/low-priority (from spatial cells) /datum/ai_controller/proc/recalculate_idle(datum/exited) if(ai_status == AI_STATUS_OFF) return @@ -229,17 +393,18 @@ multiple modular subtrees with behaviors if(distance <= interesting_dist) //is our target in between interesting cells? return - if(should_idle()) - set_ai_status(AI_STATUS_IDLE) + reset_ai_status() /datum/ai_controller/proc/on_client_enter(datum/source, list/target_list) SIGNAL_HANDLER + if(ai_status == AI_STATUS_ON) + return + if (!(locate(/mob/living) in target_list)) return - if(ai_status == AI_STATUS_IDLE) - set_ai_status(AI_STATUS_ON) + reset_ai_status() /datum/ai_controller/proc/on_client_exit(datum/source, datum/exited) SIGNAL_HANDLER @@ -250,12 +415,37 @@ multiple modular subtrees with behaviors /datum/ai_controller/proc/reset_ai_status() set_ai_status(get_expected_ai_status()) +/** + * Deliberately disables this AI until clear_forced_off() is called. + * Unlike a bare set_ai_status(AI_STATUS_OFF), this survives status recalculations + * from stat changes, z-level changes, client login/logout and the like. + */ +/datum/ai_controller/proc/force_ai_off(additional_flags = NONE) + forced_off = TRUE + set_ai_status(AI_STATUS_OFF, additional_flags) + +/// Undoes force_ai_off() and recalculates what status we should be in. +/datum/ai_controller/proc/clear_forced_off() + forced_off = FALSE + reset_ai_status() + /** * Gets the AI status we expect the AI controller to be on at this current moment. - * Returns AI_STATUS_OFF if it's inhabited by a Client and shouldn't be, if it's dead and cannot act while dead, or is on a z level without clients. - * Returns AI_STATUS_ON otherwise. + * Returns AI_STATUS_OFF if it has been forced off, is inhabited by a Client and shouldn't be, is dead and cannot act while dead, + * or is sleeping for performance (off-station with no nearby client and not flagged RUN_WHILE_UNWATCHED; client arrival wakes these automatically). + * Otherwise returns AI_STATUS_ON or AI_STATUS_ON_LOW, see get_active_ai_status(). */ /datum/ai_controller/proc/get_expected_ai_status() + + +/* +#ifdef AI_PERFORMANCE_TESTING + return AI_STATUS_ON +#endif +*/ + if (forced_off) + return AI_STATUS_OFF + if (isnull(get_turf(pawn))) return AI_STATUS_OFF @@ -271,16 +461,35 @@ multiple modular subtrees with behaviors return AI_STATUS_ON return AI_STATUS_OFF - var/turf/pawn_turf = get_turf(mob_pawn) #ifdef TESTING + var/turf/pawn_turf = get_turf(mob_pawn) if(!pawn_turf) CRASH("AI controller [src] controlling pawn ([pawn]) is not on a turf.") #endif - if(!length(SSmobs.clients_by_zlevel[pawn_turf.z]) || on_failed_planning_timeout || !able_to_run) + if(!able_to_run) return AI_STATUS_OFF - if(should_idle()) - return AI_STATUS_IDLE - return AI_STATUS_ON + return get_active_ai_status() + +/** + * Classifies an active AI controller into a priority tier. + * Returns AI_STATUS_ON for controllers on station/shuttle territory, with a nearby client, + * Returns AI_STATUS_ON_LOW for other active controllers unless they have ALWAYS_HIGH_PRIORITY. + * else its AI_STATUS_OFF + */ +/datum/ai_controller/proc/get_active_ai_status() + var/turf/pawn_turf = get_turf(pawn) + var/area/pawn_area = pawn_turf ? get_area(pawn_turf) : null + // AI actually standing on the station or a shuttle always stays high priority + if(istype(pawn_area, /area/station) || istype(pawn_area, /area/shuttle)) + return AI_STATUS_ON + if(has_nearby_client()) + return AI_STATUS_ON + if(ai_traits & RUN_WHILE_UNWATCHED) + if(ai_traits & ALWAYS_HIGH_PRIORITY) + return AI_STATUS_ON + else + return AI_STATUS_ON_LOW + return AI_STATUS_OFF ///Called when the AI controller pawn changes z levels, we check if there's any clients on the new one and wake up the AI if there is. /datum/ai_controller/proc/on_changed_z_level(atom/source, turf/old_turf, turf/new_turf, same_z_layer, notify_contents) @@ -290,10 +499,10 @@ multiple modular subtrees with behaviors if((mob_pawn?.client && !continue_processing_when_client)) return if(old_turf) - GLOB.ai_controllers_by_zlevel[old_turf.z] -= src + SSai_controllers.ai_controllers_by_zlevel[old_turf.z] -= src if(isnull(new_turf)) return - GLOB.ai_controllers_by_zlevel[new_turf.z] += src + SSai_controllers.ai_controllers_by_zlevel[new_turf.z] += src reset_ai_status() ///Abstract proc for initializing the pawn to the new controller @@ -307,6 +516,7 @@ multiple modular subtrees with behaviors return // instantiated without an applicable pawn, fine SEND_SIGNAL(src, COMSIG_AI_CONTROLLER_UNPOSSESSED_PAWN) + reset_bt_tick_states() set_ai_status(AI_STATUS_OFF) UnregisterSignal(pawn, list(COMSIG_MOVABLE_Z_CHANGED, COMSIG_MOB_LOGIN, COMSIG_MOB_LOGOUT, COMSIG_MOB_STATCHANGE, COMSIG_QDELETING, COMSIG_EVLOGGING_ENABLED)) clear_able_to_run() @@ -314,13 +524,54 @@ multiple modular subtrees with behaviors ai_movement.stop_moving_towards(src) var/turf/pawn_turf = get_turf(pawn) if(pawn_turf) - GLOB.ai_controllers_by_zlevel[pawn_turf.z] -= src - remove_from_unplanned_controllers() + SSai_controllers.ai_controllers_by_zlevel[pawn_turf.z] -= src pawn.ai_controller = null pawn = null if(destroy) qdel(src) +///Call reset tick state on every node in the tree. +/datum/ai_controller/proc/reset_bt_tick_states() + if(!LAZYLEN(behavior_nodes)) + return + var/list/to_visit = behavior_nodes.Copy() + var/index = 1 + while(index <= length(to_visit)) + var/datum/bt_node/node = to_visit[index++] + node.reset_tick_state() + node.collect_reset_children(to_visit) + +/** + * Installs or removes a runtime override on the subtree slot registered with the given id. + * + * id - The ID for this slot + * override_subtree - actual subtree we're setting + */ +/datum/ai_controller/proc/set_behavior_tree_override(id, override_subtree) + var/datum/bt_node/subtree/slot = LAZYACCESS(override_slots, id) + if(isnull(slot)) + return + + var/current_type = isnull(slot.override_node) ? null : slot.override_node.type + if(current_type == override_subtree) + return + + if(slot.override_node) + slot.override_node.reset_tick_state() + QDEL_NULL(slot.override_node) + + if(isnull(override_subtree)) + finalize_tree() + SEND_SIGNAL(pawn, COMSIG_AI_OVERRIDE_SLOT_CHANGED(id), null) + return + + var/datum/bt_node/subtree/new_node = new override_subtree + resolve_node_children(new_node) + slot.override_node = new_node + finalize_tree() + cancel_current_plan() // Reset, not ideal; Maybe later on we can do this more gracefully. + SEND_SIGNAL(pawn, COMSIG_AI_OVERRIDE_SLOT_CHANGED(id), override_subtree) + /datum/ai_controller/proc/setup_able_to_run() // paused_until is handled by PauseAi() manually RegisterSignals(pawn, list(SIGNAL_ADDTRAIT(TRAIT_AI_PAUSED), SIGNAL_REMOVETRAIT(TRAIT_AI_PAUSED)), PROC_REF(update_able_to_run)) @@ -333,7 +584,7 @@ multiple modular subtrees with behaviors var/run_flags = get_able_to_run() if(run_flags & AI_UNABLE_TO_RUN) able_to_run = FALSE - GLOB.move_manager.stop_looping(pawn) //stop moving + ai_movement.fail_movement(src) else able_to_run = TRUE set_ai_status(get_expected_ai_status(), run_flags) @@ -347,19 +598,16 @@ multiple modular subtrees with behaviors return NONE ///Can this pawn interact with objects? -/datum/ai_controller/proc/ai_can_interact() - SHOULD_CALL_PARENT(TRUE) - return !QDELETED(pawn) +/datum/ai_controller/proc/ai_can_interact(atom/target) + return !QDELETED(pawn) && !QDELETED(target) ///Interact with objects /datum/ai_controller/proc/ai_interact(target, combat_mode, list/modifiers) - if(!ai_can_interact()) - return FALSE - var/atom/final_target = isdatum(target) ? target : blackboard[target] //incase we got a blackboard key instead - if(QDELETED(final_target)) + if(!ai_can_interact(final_target)) return FALSE + var/params = list2params(modifiers) var/mob/living/living_pawn = pawn if(isnull(combat_mode)) @@ -372,216 +620,43 @@ multiple modular subtrees with behaviors living_pawn.set_combat_mode(old_combat_mode) return TRUE -///Runs any actions that are currently running -/datum/ai_controller/process(seconds_per_tick) - - for(var/datum/ai_behavior/current_behavior as anything in current_behaviors) - - // Convert the current behaviour action cooldown to realtime seconds from deciseconds.current_behavior - // Then pick the max of this and the seconds_per_tick passed to ai_controller.process() - // Action cooldowns cannot happen faster than seconds_per_tick, so seconds_per_tick should be the value used in this scenario. - var/action_seconds_per_tick = max(current_behavior.get_cooldown(src) * 0.1, seconds_per_tick) - - if(!(current_behavior.behavior_flags & AI_BEHAVIOR_REQUIRE_MOVEMENT)) - if(behavior_cooldowns[current_behavior] > world.time) //Still on cooldown - continue - ProcessBehavior(action_seconds_per_tick, current_behavior) - return - - if(isnull(current_movement_target)) - fail_behavior(current_behavior) - return - ///Stops pawns from performing such actions that should require the target to be adjacent. - var/atom/movable/moving_pawn = pawn - var/can_reach = !(current_behavior.behavior_flags & AI_BEHAVIOR_REQUIRE_REACH) || current_movement_target.IsReachableBy(moving_pawn) - if(can_reach && current_behavior.required_distance >= get_dist(moving_pawn, current_movement_target)) ///Are we close enough to engage? - if(ai_movement.moving_controllers[src] == current_movement_target) //We are close enough, if we're moving stop. - ai_movement.stop_moving_towards(src) - - if(behavior_cooldowns[current_behavior] > world.time) //Still on cooldown - continue - ProcessBehavior(action_seconds_per_tick, current_behavior) - return - - if(ai_movement.moving_controllers[src] != current_movement_target) //We're too far, if we're not already moving start doing it. - ai_movement.start_moving_towards(src, current_movement_target, current_behavior.required_distance) //Then start moving - - if(current_behavior.behavior_flags & AI_BEHAVIOR_MOVE_AND_PERFORM) //If we can move and perform then do so. - if(behavior_cooldowns[current_behavior] > world.time) //Still on cooldown - continue - ProcessBehavior(action_seconds_per_tick, current_behavior) - return ///This is where you decide what actions are taken by the AI. /datum/ai_controller/proc/SelectBehaviors(seconds_per_tick) - SHOULD_NOT_SLEEP(TRUE) //Fuck you don't sleep in procs like this. - planned_behaviors.Cut() - - for(var/datum/ai_planning_subtree/subtree as anything in planning_subtrees) - if(subtree.SelectBehaviors(src, seconds_per_tick) == SUBTREE_RETURN_FINISH_PLANNING) + SHOULD_NOT_SLEEP(TRUE) + cancelled_during_tick = FALSE + if(LAZYLEN(polling_observers)) + for(var/datum/bt_node/decorator/dec as anything in polling_observers.Copy()) + dec.poll_condition(src) + for(var/datum/bt_node/node as anything in behavior_nodes) + if(node.tick(src, seconds_per_tick) == BT_RUNNING) break - SEND_SIGNAL(src, COMSIG_AI_CONTROLLER_PICKED_BEHAVIORS, current_behaviors, planned_behaviors) - for(var/datum/ai_behavior/forgotten_behavior as anything in current_behaviors - planned_behaviors) - var/list/arguments = list(src, FALSE) - var/list/stored_arguments = behavior_args[type] - if(stored_arguments) - arguments += stored_arguments - forgotten_behavior.finish_action(arglist(arguments)) - - if(IS_EVLOGGING) - var/list/event_text = list() - event_text += "New plan starting!" - - for(var/datum/ai_behavior/behavior in planned_behaviors) - event_text += "Queued behavior [behavior.type]" - - EVLOG_TEXT(src, EVLOG_CATEGORY_AI_DECISIONMAKING, jointext(event_text, "\n")) - -///This proc handles changing ai status, and starts/stops processing if required. +///This proc handles changing ai status and updates the planning subsystem list. /datum/ai_controller/proc/set_ai_status(new_ai_status, additional_flags = NONE) if(ai_status == new_ai_status) return FALSE //no change //remove old status, if we've got one if(ai_status) - GLOB.ai_controllers_by_status[ai_status] -= src + SSai_controllers.ai_controllers_by_status[ai_status] -= src for(var/datum/controller/subsystem/ai_controllers/controller_subsystem in Master.subsystems) if(controller_subsystem.planning_status == ai_status) controller_subsystem.currentrun -= src break - remove_from_unplanned_controllers() - stop_previous_processing() ai_status = new_ai_status - GLOB.ai_controllers_by_status[new_ai_status] += src + last_bt_tick = 0 // don't count time spent in the previous status towards the next tick's seconds_per_tick + SSai_controllers.ai_controllers_by_status[new_ai_status] += src if(ai_status == AI_STATUS_OFF) if(!(additional_flags & AI_PREVENT_CANCEL_ACTIONS)) - CancelActions() - return - if(!length(current_behaviors)) - add_to_unplanned_controllers() - return - start_ai_processing() + cancel_current_plan() -/datum/ai_controller/proc/start_ai_processing() - switch(ai_status) - if(AI_STATUS_ON) - START_PROCESSING(SSai_behaviors, src) - if(AI_STATUS_IDLE) - START_PROCESSING(SSidle_ai_behaviors, src) -/datum/ai_controller/proc/stop_previous_processing() - switch(ai_status) - if(AI_STATUS_ON) - STOP_PROCESSING(SSai_behaviors, src) - if(AI_STATUS_IDLE) - STOP_PROCESSING(SSidle_ai_behaviors, src) -/datum/ai_controller/proc/PauseAi(time) - paused_until = world.time + time - update_able_to_run() - addtimer(CALLBACK(src, PROC_REF(update_able_to_run)), time) - -/datum/ai_controller/proc/add_to_unplanned_controllers() - if(isnull(ai_status) || ai_status == AI_STATUS_OFF || isnull(idle_behavior)) - return - GLOB.unplanned_controllers[ai_status][src] = TRUE - -/datum/ai_controller/proc/remove_from_unplanned_controllers() - if(isnull(ai_status) || ai_status == AI_STATUS_OFF) - return - GLOB.unplanned_controllers[ai_status] -= src - for(var/datum/controller/subsystem/unplanned_controllers/potential_holder as anything in GLOB.unplanned_controller_subsystems) - if(potential_holder.target_status == ai_status) - potential_holder.current_run -= src - -/datum/ai_controller/proc/modify_cooldown(datum/ai_behavior/behavior, new_cooldown) - behavior_cooldowns[behavior] = new_cooldown - -///Call this to add a behavior to the stack. -/datum/ai_controller/proc/queue_behavior(behavior_type, ...) - var/datum/ai_behavior/behavior = GET_AI_BEHAVIOR(behavior_type) - if(!behavior) - CRASH("Behavior [behavior_type] not found.") - var/list/arguments = args.Copy() - arguments[1] = src - - if(current_behaviors[behavior]) ///It's still in the plan, don't add it again to current_behaviors but do keep it in the planned behavior list so its not cancelled - planned_behaviors[behavior] = TRUE - return - - if(!behavior.setup(arglist(arguments))) - return - - var/should_exit_unplanned = !length(current_behaviors) - planned_behaviors[behavior] = TRUE - current_behaviors[behavior] = TRUE - - arguments.Cut(1, 2) - if(length(arguments)) - behavior_args[behavior_type] = arguments - else - behavior_args -= behavior_type - - if(!(behavior.behavior_flags & AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION)) //this one blocks planning! - able_to_plan = FALSE - - if(should_exit_unplanned) - exit_unplanned_mode() - - SEND_SIGNAL(src, AI_CONTROLLER_BEHAVIOR_QUEUED(behavior_type), arguments) - -/datum/ai_controller/proc/check_able_to_plan() - for(var/datum/ai_behavior/current_behavior as anything in current_behaviors) - if(!(current_behavior.behavior_flags & AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION)) //We have a behavior that blocks planning - return FALSE - return TRUE - -/datum/ai_controller/proc/dequeue_behavior(datum/ai_behavior/behavior) - current_behaviors -= behavior - able_to_plan = check_able_to_plan() - if(!length(current_behaviors)) - enter_unplanned_mode() - -/datum/ai_controller/proc/exit_unplanned_mode() - remove_from_unplanned_controllers() - start_ai_processing() - -/datum/ai_controller/proc/enter_unplanned_mode() - add_to_unplanned_controllers() - stop_previous_processing() - -/datum/ai_controller/proc/ProcessBehavior(seconds_per_tick, datum/ai_behavior/behavior) - var/list/arguments = list(seconds_per_tick, src) - var/list/stored_arguments = behavior_args[behavior.type] - if(stored_arguments) - arguments += stored_arguments - - var/process_flags = behavior.perform(arglist(arguments)) - if(process_flags & AI_BEHAVIOR_DELAY) - behavior_cooldowns[behavior] = world.time + behavior.get_cooldown(src) - if(process_flags & AI_BEHAVIOR_FAILED) - arguments[1] = src - arguments[2] = FALSE - behavior.finish_action(arglist(arguments)) - else if (process_flags & AI_BEHAVIOR_SUCCEEDED) - arguments[1] = src - arguments[2] = TRUE - behavior.finish_action(arglist(arguments)) - -/datum/ai_controller/proc/CancelActions() - if(!length(current_behaviors)) - return - for(var/datum/ai_behavior/current_behavior as anything in current_behaviors) - fail_behavior(current_behavior) - EVLOG_TEXT(src, EVLOG_CATEGORY_AI_DECISIONMAKING, "Actions were cancelled!") - -/datum/ai_controller/proc/fail_behavior(datum/ai_behavior/current_behavior) - var/list/arguments = list(src, FALSE) - var/list/stored_arguments = behavior_args[current_behavior.type] - if(stored_arguments) - arguments += stored_arguments - current_behavior.finish_action(arglist(arguments)) +/datum/ai_controller/proc/cancel_current_plan() + active_execution_index = 0 + cancelled_during_tick = TRUE + reset_bt_tick_states() /// Turn the controller on or off based on if you're alive, we only register to this if the flag is present so don't need to check again /datum/ai_controller/proc/on_stat_changed(mob/living/source, new_stat) @@ -599,14 +674,14 @@ multiple modular subtrees with behaviors /datum/ai_controller/proc/on_sentience_lost() SIGNAL_HANDLER UnregisterSignal(pawn, COMSIG_MOB_LOGOUT) - set_ai_status(AI_STATUS_IDLE) //Can't do anything while player is connected + reset_ai_status() //resume AI control now that the client is gone RegisterSignal(pawn, COMSIG_MOB_LOGIN, PROC_REF(on_sentience_gained)) // Turn the controller off if the pawn has been qdeleted -/datum/ai_controller/proc/on_pawn_qdeleted() +/datum/ai_controller/proc/on_pawn_qdeleted(datum/source) SIGNAL_HANDLER + sig_remove_from_blackboard(source) set_ai_status(AI_STATUS_OFF) - set_movement_target(type, null) if(ai_movement.moving_controllers[src]) ai_movement.stop_moving_towards(src) @@ -617,25 +692,19 @@ multiple modular subtrees with behaviors var/mob/living/living_pawn = pawn return living_pawn.get_access() -///Returns the minimum required distance to preform one of our current behaviors. Honestly this should just be cached or something but fuck you -/datum/ai_controller/proc/get_minimum_distance() - var/minimum_distance = max_target_distance - // right now I'm just taking the shortest minimum distance of our current behaviors, at some point in the future - // we should let whatever sets the current_movement_target also set the min distance and max path length - // (or at least cache it on the controller) - for(var/datum/ai_behavior/iter_behavior as anything in current_behaviors) - if(iter_behavior.required_distance < minimum_distance) - minimum_distance = iter_behavior.required_distance - return minimum_distance +/// Returns TRUE if the pawn can path to the target. minimum_distance is how close the path must get (0 = onto/adjacent to the target's turf); searches pass it from their own acquire_target leaf. +/datum/ai_controller/proc/can_reach_target(atom/target, distance = 10, minimum_distance = 0) + if(!isdatum(target)) //we dont need to check if its not a datum! + return TRUE + if(get_turf(pawn) == get_turf(target)) + return TRUE + var/list/path = get_path_to(pawn, target, simulated_only = !HAS_TRAIT(pawn, TRAIT_SPACEWALK), mintargetdist = minimum_distance, max_distance = distance, access = get_access()) + return (!!length(path)) -/datum/ai_controller/proc/planning_failed() - on_failed_planning_timeout = TRUE - set_ai_status(get_expected_ai_status()) - addtimer(CALLBACK(src, PROC_REF(resume_planning)), AI_FAILED_PLANNING_COOLDOWN) -/datum/ai_controller/proc/resume_planning() - on_failed_planning_timeout = FALSE - set_ai_status(get_expected_ai_status()) +/// Called when a target was found but couldn't be reached. Base no-op; override to record the target (e.g. add it to an ignore list). +/datum/ai_controller/proc/note_unreachable_target(atom/target) + return /// Returns true if we have a blackboard key with the provided key and it is not qdeleting /datum/ai_controller/proc/blackboard_key_exists(key) @@ -696,8 +765,9 @@ multiple modular subtrees with behaviors * * * key - A blackboard key * * thing - a value to set the blackboard key to. + * * track_datum - whether we should track this ref for deletion, this should always be TRUE unless you really know wtf you're doing */ -/datum/ai_controller/proc/set_blackboard_key(key, thing) +/datum/ai_controller/proc/set_blackboard_key(key, thing, track_datum = TRUE) // Assume it is an error when trying to set a value overtop a list if(islist(blackboard[key])) CRASH("set_blackboard_key attempting to set a blackboard value to key [key] when it's a list!") @@ -709,7 +779,8 @@ multiple modular subtrees with behaviors if(!isnull(blackboard[key])) clear_blackboard_key(key) - TRACK_AI_DATUM_TARGET(thing, key) + if(track_datum) + TRACK_AI_DATUM_TARGET(thing, key) blackboard[key] = thing post_blackboard_key_set(key) @@ -827,6 +898,7 @@ multiple modular subtrees with behaviors LAZYINITLIST(blackboard[key]) TRACK_AI_DATUM_TARGET(thing, key) blackboard[key] |= thing + post_blackboard_key_set(key) /** * Adds the value to the inner list at key with the inner key set to "thing" @@ -874,7 +946,7 @@ multiple modular subtrees with behaviors blackboard[key] = null if(isnull(pawn)) return - SEND_SIGNAL(pawn, COMSIG_AI_BLACKBOARD_KEY_CLEARED(key)) + SEND_SIGNAL(pawn, COMSIG_AI_BLACKBOARD_KEY_CLEARED(key), key) /** * Remove the passed thing from the associated blackboard key @@ -982,19 +1054,31 @@ multiple modular subtrees with behaviors . = ..() UnregisterSignal(src, COMSIG_EVLOG_EVENT_ADDED) + /// Called whenever an event is logged for this controller. Attaches a snapshot of current behaviors and blackboard state to the event via track_info. /datum/ai_controller/proc/on_evlog_event_added(datum/source, datum/event_logger_track/track, list/event_data) SIGNAL_HANDLER var/list/track_info = list() - var/list/current_behavior_info = list() - for(var/datum/ai_behavior/behavior in planned_behaviors) - if(behavior in current_behaviors) //Not really ideal; we should find a better way to do this. - current_behavior_info += "ACTIVE: [span_bold("[behavior.type]")]" - else - current_behavior_info += "[behavior.type]" - EVLOG_TRACK_INFO_ENTRY(track_info, "Behaviors", "Current Behavior", jointext(current_behavior_info, "\n")) + // Build full tree state view showing all nodes with status markers + var/list/tree_lines = list() + for(var/datum/bt_node/root_node as anything in behavior_nodes) + root_node.append_full_tree_state(tree_lines, "") + EVLOG_TRACK_INFO_ENTRY(track_info, "Behaviors", "Full Tree State", length(tree_lines) ? jointext(tree_lines, "\n") : "(none)") + // Add execution context section + var/active_node_label = "(none)" + if(active_execution_index) + for(var/datum/bt_node/root_node as anything in behavior_nodes) + var/datum/bt_node/found = root_node.find_by_index(active_execution_index) + if(found) + active_node_label = found.label + break + EVLOG_TRACK_INFO_ENTRY(track_info, "Execution Context", "Active Execution Index", "[active_execution_index] ([active_node_label])") + EVLOG_TRACK_INFO_ENTRY(track_info, "Execution Context", "AI Status", ai_status) + EVLOG_TRACK_INFO_ENTRY(track_info, "Execution Context", "Able to Run", able_to_run ? "TRUE" : "FALSE") + + // Blackboard snapshot for(var/blackboard_key_name, blackboard_value in blackboard) var/value_string if(isatom(blackboard_value)) diff --git a/code/datums/ai/_ai_planning_subtree.dm b/code/datums/ai/_ai_planning_subtree.dm deleted file mode 100644 index b3f0ae9fb53..00000000000 --- a/code/datums/ai/_ai_planning_subtree.dm +++ /dev/null @@ -1,10 +0,0 @@ -///A subtree is attached to a controller and is occasionally called by /ai_controller/SelectBehaviors(), this mainly exists to act as a way to subtype and modify SelectBehaviors() without needing to subtype the ai controller itself -/datum/ai_planning_subtree - /// A list of typepaths of "operational datums" (elements/components) we absolutely NEED to run. Checked in unit tests, as well as be a nice reminder to developers that such a thing might be needed. - /// Note that in the Attach/Inititalize/New (or any future equivalent for these procs), you will need to add the trait TRAIT_SUBTREE_REQUIRED_OPERATIONAL_DATUM to the mob in question - /// in order for unit tests to succeed. This will break obviously enough if you don't do this and declare the required datum here however. - var/list/operational_datums = null - -///Determines what behaviors should the controller try processing; if this returns SUBTREE_RETURN_FINISH_PLANNING then the controller won't go through the other subtrees should multiple exist in controller.planning_subtrees -/datum/ai_planning_subtree/proc/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - return diff --git a/code/datums/ai/_item_behaviors.dm b/code/datums/ai/_item_behaviors.dm index c9f3441fdd6..b07ccb4f5f3 100644 --- a/code/datums/ai/_item_behaviors.dm +++ b/code/datums/ai/_item_behaviors.dm @@ -1,60 +1,31 @@ -///This behavior is for obj/items, it is used to free themselves out of the hands of whoever is holding them -/datum/ai_behavior/item_escape_grasp +///yeet yourself at a thing +/datum/bt_node/ai_behavior/throw_attack + /// Sound played on each throw. + var/attack_sound = 'sound/items/haunted/ghostitemattack.ogg' + /// Maximum throws before the attack is exhausted. + var/max_attempts = 4 + /// Blackboard key holding the throw target. + var/target_key + /// Blackboard key tracking how many throws have happened. + var/throw_count_key -/datum/ai_behavior/item_escape_grasp/perform(seconds_per_tick, datum/ai_controller/controller) - var/obj/item/item_pawn = controller.pawn - var/mob/item_holder = item_pawn.loc - if(!istype(item_holder)) - //We're no longer being held. abort abort!! - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - item_pawn.visible_message(span_warning("[item_pawn] slips out of the hands of [item_holder]!")) - item_holder.dropItemToGround(item_pawn, TRUE) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - - -///This behavior is for obj/items, it is used to move closer to a target and throw themselves towards them. -/datum/ai_behavior/item_move_close_and_attack - required_distance = 3 - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT - action_cooldown = 20 - ///Sound to use - var/attack_sound - ///Max attemps to make - var/max_attempts = 3 - -/datum/ai_behavior/item_move_close_and_attack/setup(datum/ai_controller/controller, target_key, throw_count_key) - . = ..() - var/atom/target = controller.blackboard[target_key] - if (isnull(target)) - return FALSE - set_movement_target(controller, target) - -/datum/ai_behavior/item_move_close_and_attack/perform(seconds_per_tick, datum/ai_controller/controller, target_key, throw_count_key) +/datum/bt_node/ai_behavior/throw_attack/perform(seconds_per_tick, datum/ai_controller/controller) var/obj/item/item_pawn = controller.pawn var/atom/throw_target = controller.blackboard[target_key] - + if(QDELETED(throw_target)) + controller.clear_blackboard_key(target_key) + controller.set_blackboard_key(throw_count_key, 0) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED item_pawn.visible_message(span_warning("[item_pawn] hurls towards [throw_target]!")) - item_pawn.throw_at(throw_target, rand(4,5), 9) + item_pawn.throw_at(throw_target, rand(4, 5), 9) playsound(item_pawn.loc, attack_sound, 100, TRUE) controller.add_blackboard_key(throw_count_key, 1) if(controller.blackboard[throw_count_key] >= max_attempts) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - return AI_BEHAVIOR_DELAY + return on_throws_exhausted(controller, throw_target, target_key, throw_count_key) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED -/datum/ai_behavior/item_move_close_and_attack/finish_action(datum/ai_controller/controller, succeeded, target_key, throw_count_key) - . = ..() - reset_blackboard(controller, succeeded, target_key, throw_count_key) - -/datum/ai_behavior/item_move_close_and_attack/proc/reset_blackboard(datum/ai_controller/controller, succeeded, target_key, throw_count_key) +/// Clears target and resets throw count. Override to add extra on-exhaust logic. +/datum/bt_node/ai_behavior/throw_attack/proc/on_throws_exhausted(datum/ai_controller/controller, atom/throw_target, target_key, throw_count_key) controller.clear_blackboard_key(target_key) controller.set_blackboard_key(throw_count_key, 0) - -/datum/ai_behavior/item_move_close_and_attack/ghostly - attack_sound = 'sound/items/haunted/ghostitemattack.ogg' - max_attempts = 4 - -/datum/ai_behavior/item_move_close_and_attack/ghostly/haunted - -/datum/ai_behavior/item_move_close_and_attack/ghostly/haunted/finish_action(datum/ai_controller/controller, succeeded, target_key, throw_count_key) - controller.add_blackboard_key_assoc(BB_TO_HAUNT_LIST, controller.blackboard[target_key], -1) - return ..() + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED diff --git a/code/datums/ai/babies/babies_behaviors.dm b/code/datums/ai/babies/babies_behaviors.dm index c12f4acc6fa..497d72f8fa4 100644 --- a/code/datums/ai/babies/babies_behaviors.dm +++ b/code/datums/ai/babies/babies_behaviors.dm @@ -1,23 +1,30 @@ #define FIND_PARTNER_COOLDOWN 1 MINUTES -/** - * Find a compatible, living partner, if we're also alone. - */ -/datum/ai_behavior/find_partner - action_cooldown = 5 SECONDS - /// Range to look. +/// Find someone to erp with +/datum/bt_node/ai_behavior/find_partner + var/target_key + var/partner_types_key + var/child_types_key + time_between_perform = 5 SECONDS var/range = 7 - /// Maximum number of nearby pop var/max_nearby_pop = 3 -/datum/ai_behavior/find_partner/perform(seconds_per_tick, datum/ai_controller/controller, target_key, partner_types_key, child_types_key) - var/maximum_pop = controller.blackboard[BB_MAX_CHILDREN] || max_nearby_pop - var/mob/pawn_mob = controller.pawn - var/list/similar_species_types = controller.blackboard[partner_types_key] + controller.blackboard[child_types_key] +/datum/bt_node/ai_behavior/find_partner/setup(datum/ai_controller/controller) var/mob/living/living_pawn = controller.pawn - var/list/possible_partners = list() + if(living_pawn.gender == FEMALE) + return FALSE + if(!controller.blackboard[partner_types_key] || !controller.blackboard[child_types_key]) + return FALSE + return TRUE +/datum/bt_node/ai_behavior/find_partner/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/pawn_mob = controller.pawn + var/mob/living/living_pawn = controller.pawn + var/maximum_pop = controller.blackboard[BB_MAX_CHILDREN] || max_nearby_pop + var/list/similar_species_types = controller.blackboard[partner_types_key] + controller.blackboard[child_types_key] + var/list/possible_partners = list() var/nearby_pop = 0 + for(var/mob/living/other in oview(range, pawn_mob)) if(!pawn_mob.faction_check_atom(other)) return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED @@ -32,10 +39,10 @@ if(!HAS_TRAIT(other, TRAIT_MOB_BREEDER) || other.ckey) continue - if(other.stat != CONSCIOUS) //Check if it's conscious FIRST. + if(other.stat != CONSCIOUS) continue - if(other.gender != living_pawn.gender && !(other.flags_1 & HOLOGRAM_1)) //Better safe than sorry ;_; + if(other.gender != living_pawn.gender && !(other.flags_1 & HOLOGRAM_1)) possible_partners += other if(!length(possible_partners)) @@ -44,28 +51,25 @@ controller.set_blackboard_key(target_key, pick(possible_partners)) return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED -/** - * Reproduce. - */ -/datum/ai_behavior/make_babies - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH +/// I'm not saying what this behavior does :flushed: +/datum/bt_node/ai_behavior/make_babies + var/target_key + var/child_types_key -/datum/ai_behavior/make_babies/setup(datum/ai_controller/controller, target_key, child_types_key) - . = ..() - var/atom/target = controller.blackboard[target_key] - if(!target) - return FALSE - set_movement_target(controller, target) +/datum/bt_node/ai_behavior/make_babies/setup(datum/ai_controller/controller) + var/mob/target = controller.blackboard[target_key] + return !QDELETED(target) && target.stat == CONSCIOUS -/datum/ai_behavior/make_babies/perform(seconds_per_tick, datum/ai_controller/controller, target_key, child_types_key) +/datum/bt_node/ai_behavior/make_babies/perform(seconds_per_tick, datum/ai_controller/controller) var/mob/target = controller.blackboard[target_key] if(QDELETED(target) || target.stat != CONSCIOUS) return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - controller.ai_interact(target = target, combat_mode = FALSE) + INVOKE_ASYNC(controller, TYPE_PROC_REF(/datum/ai_controller, ai_interact), target, FALSE) return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED -/datum/ai_behavior/make_babies/finish_action(datum/ai_controller/controller, succeeded, target_key) +/datum/bt_node/ai_behavior/make_babies/finish_action(datum/ai_controller/controller, succeeded) . = ..() controller.clear_blackboard_key(target_key) + #undef FIND_PARTNER_COOLDOWN diff --git a/code/datums/ai/babies/babies_subtrees.dm b/code/datums/ai/babies/babies_subtrees.dm deleted file mode 100644 index 7cadd1e8c55..00000000000 --- a/code/datums/ai/babies/babies_subtrees.dm +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Reproduce with a similar mob. - */ -/datum/ai_planning_subtree/make_babies - operational_datums = list(/datum/component/breed) - /// Chance to make babies - var/chance = 5 - /// Make babies behavior we will use - var/datum/ai_behavior/reproduce_behavior = /datum/ai_behavior/make_babies - /// Find partner behavior we will use - var/datum/ai_behavior/partner_behavior = /datum/ai_behavior/find_partner - -/datum/ai_planning_subtree/make_babies/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - . = ..() - - if(!SPT_PROB(chance, seconds_per_tick) || controller.blackboard[BB_PARTNER_SEARCH_TIMEOUT] >= world.time) - return - - if(controller.blackboard_key_exists(BB_BABIES_TARGET)) - controller.queue_behavior(reproduce_behavior, BB_BABIES_TARGET, BB_BABIES_CHILD_TYPES) - return SUBTREE_RETURN_FINISH_PLANNING - - if(controller.pawn.gender == FEMALE || !controller.blackboard[BB_BREED_READY]) - return - - if(!controller.blackboard[BB_BABIES_PARTNER_TYPES] || !controller.blackboard[BB_BABIES_CHILD_TYPES]) - return - - // Find target - controller.queue_behavior(partner_behavior, BB_BABIES_TARGET, BB_BABIES_PARTNER_TYPES, BB_BABIES_CHILD_TYPES) diff --git a/code/datums/ai/babies/make_babies.bt.json b/code/datums/ai/babies/make_babies.bt.json new file mode 100644 index 00000000000..6051613a60d --- /dev/null +++ b/code/datums/ai/babies/make_babies.bt.json @@ -0,0 +1,31 @@ +{ + "dm_type": "/datum/bt_node/subtree/make_babies", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_BABIES_TARGET" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_BABIES_TARGET", + "required_dist": 1, + "finish_on_arrival": true + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/make_babies", + "vars": { + "target_key": "BB_BABIES_TARGET", + "child_types_key": "BB_BABIES_CHILD_TYPES" + } + } + ] + } +} diff --git a/code/datums/ai/bane/bane.bt.json b/code/datums/ai/bane/bane.bt.json new file mode 100644 index 00000000000..b0e92e321eb --- /dev/null +++ b/code/datums/ai/bane/bane.bt.json @@ -0,0 +1,43 @@ +{ + "dm_type": "/datum/ai_controller/bane", + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_BANE_BATMAN" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_BANE_BATMAN", + "required_dist": 1 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/break_spine/bane", + "vars": { + "target_key": "BB_BANE_BATMAN" + } + } + ] + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_BANE_BATMAN", + "target_source": "/datum/target_source/oview_single_type/living_mob", + "targeting_strategy": "/datum/targeting_strategy/conscious_mob", + "vision_range": 7 + } + } + ] +} diff --git a/code/datums/ai/bane/bane_behaviors.dm b/code/datums/ai/bane/bane_behaviors.dm deleted file mode 100644 index c47c455f7f9..00000000000 --- a/code/datums/ai/bane/bane_behaviors.dm +++ /dev/null @@ -1,9 +0,0 @@ - -/datum/ai_behavior/break_spine/bane/finish_action(datum/ai_controller/controller, succeeded, target_key) - if(succeeded) - var/list/bane_quotes = strings("bane.json", "bane") - var/mob/living/bane = controller.pawn - if(QDELETED(bane)) // pawn can be null at this point - return ..() - bane.say(pick(bane_quotes)) - return ..() diff --git a/code/datums/ai/bane/bane_controller.dm b/code/datums/ai/bane/bane_controller.dm index 580e80440a4..3144d0c21ce 100644 --- a/code/datums/ai/bane/bane_controller.dm +++ b/code/datums/ai/bane/bane_controller.dm @@ -4,8 +4,8 @@ And the only victory you achieved was a lie. Now you understand Gotham is beyond */ /datum/ai_controller/bane movement_delay = 0.4 SECONDS + behavior_tree_json = "code/datums/ai/bane/bane.bt.json" blackboard = list(BB_BANE_BATMAN = null) - planning_subtrees = list(/datum/ai_planning_subtree/bane_hunting) /datum/ai_controller/bane/TryPossessPawn(atom/new_pawn) if(!isliving(new_pawn)) diff --git a/code/datums/ai/bane/bane_subtrees.dm b/code/datums/ai/bane/bane_subtrees.dm deleted file mode 100644 index e7f651737bf..00000000000 --- a/code/datums/ai/bane/bane_subtrees.dm +++ /dev/null @@ -1,16 +0,0 @@ -///The bat is broken! -/datum/ai_planning_subtree/bane_hunting/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/mob/living/batman = controller.blackboard[BB_BANE_BATMAN] - if(QDELETED(batman)) - for(var/mob/living/possibly_the_dark_knight in oview(7, controller.pawn)) - if(IS_DEAD_OR_INCAP(possibly_the_dark_knight)) //I HAVE BROKEN THE BAT - continue - controller.set_blackboard_key(BB_BANE_BATMAN, possibly_the_dark_knight) - batman = possibly_the_dark_knight - break - - if(isnull(batman)) - return - - controller.queue_behavior(/datum/ai_behavior/break_spine/bane, BB_BANE_BATMAN) - return SUBTREE_RETURN_FINISH_PLANNING diff --git a/code/datums/ai/basic_mobs/base_basic_controller.dm b/code/datums/ai/basic_mobs/base_basic_controller.dm index 9537869bd05..136c135eb1b 100644 --- a/code/datums/ai/basic_mobs/base_basic_controller.dm +++ b/code/datums/ai/basic_mobs/base_basic_controller.dm @@ -1,5 +1,6 @@ /datum/ai_controller/basic_controller movement_delay = 0.4 SECONDS + behavior_tree_json = ABSTRACT_AI_CLASS /datum/ai_controller/basic_controller/TryPossessPawn(atom/new_pawn) if(!isliving(new_pawn)) @@ -62,9 +63,9 @@ /datum/ai_controller/proc/on_tamed(datum/source, mob/living/new_friend) SIGNAL_HANDLER forgive_target(new_friend) - clear_blackboard_key(BB_BASIC_MOB_CURRENT_TARGET) + clear_blackboard_key(BB_CURRENT_TARGET) clear_blackboard_key(BB_BASIC_MOB_RETALIATE_LIST) //we have just been tamed by a new party, clean slate for everyone! - CancelActions() + cancel_current_plan() RegisterSignal(new_friend, COMSIG_LIVING_MADE_NEW_FRIEND, PROC_REF(on_master_tame)) /datum/ai_controller/proc/on_untamed(datum/source, mob/living/old_friend) @@ -77,7 +78,7 @@ /datum/ai_controller/proc/forgive_target(atom/target) var/static/list/keys_to_check = list( - BB_BASIC_MOB_CURRENT_TARGET, + BB_CURRENT_TARGET, BB_CURRENT_PET_TARGET, ) for(var/key in keys_to_check) diff --git a/code/datums/ai/basic_mobs/basic_ai_behaviors/basic_attacking.dm b/code/datums/ai/basic_mobs/basic_ai_behaviors/basic_attacking.dm index 5b41ae5254d..2be8c0b21ee 100644 --- a/code/datums/ai/basic_mobs/basic_ai_behaviors/basic_attacking.dm +++ b/code/datums/ai/basic_mobs/basic_ai_behaviors/basic_attacking.dm @@ -1,144 +1,121 @@ /// Amount of time to wait before executing attack if not specified #define DEFAULT_ATTACK_DELAY (0.4 SECONDS) -/datum/ai_behavior/basic_melee_attack - action_cooldown = 0.2 SECONDS // We gotta check unfortunately often because we're in a race condition with nextmove - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_MOVE_AND_PERFORM | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION - ///do we finish this action after hitting once? - var/terminate_after_action = FALSE - ///do we have any alternate movement behavior? - var/movement_behavior +/// Perform a melee attack on the target specified. +/datum/bt_node/ai_behavior/basic_melee_attack + var/target_key + var/targeting_strategy = BB_TARGETING_STRATEGY + var/hiding_location_key -/datum/ai_behavior/basic_melee_attack/setup(datum/ai_controller/controller, target_key, targeting_strategy_key, hiding_location_key) +/datum/bt_node/ai_behavior/basic_melee_attack/setup(datum/ai_controller/controller) . = ..() - if(!controller.blackboard[targeting_strategy_key]) + + + if(!ispath(targeting_strategy)) + targeting_strategy = controller.blackboard[targeting_strategy] + + if(!targeting_strategy) CRASH("No targeting strategy was supplied in the blackboard for [controller.pawn]") - //Hiding location is priority var/atom/target = controller.blackboard[hiding_location_key] || controller.blackboard[target_key] if(QDELETED(target)) return FALSE - set_movement_target(controller, target, movement_behavior) - -/datum/ai_behavior/basic_melee_attack/perform(seconds_per_tick, datum/ai_controller/controller, target_key, targeting_strategy_key, hiding_location_key) +/datum/bt_node/ai_behavior/basic_melee_attack/perform(seconds_per_tick, datum/ai_controller/controller) var/atom/target = controller.blackboard[target_key] if (isnull(target)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - if (!can_attack(controller, target)) - return AI_BEHAVIOR_INSTANT - - if (isliving(controller.pawn)) - var/mob/living/pawn = controller.pawn - if (world.time < pawn.next_move) - return AI_BEHAVIOR_INSTANT - - var/datum/targeting_strategy/targeting_strategy = GET_TARGETING_STRATEGY(controller.blackboard[targeting_strategy_key]) - if(!targeting_strategy.can_attack(controller.pawn, target)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - var/hiding_target = targeting_strategy.find_hidden_mobs(controller.pawn, target) //If this is valid, theyre hidden in something! - - controller.set_blackboard_key(hiding_location_key, hiding_target) - - var/atom/final_target = hiding_target || target - controller.ai_interact(target = final_target, combat_mode = TRUE) - if(terminate_after_action) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - return AI_BEHAVIOR_DELAY - -/datum/ai_behavior/basic_melee_attack/proc/can_attack(datum/ai_controller/controller, atom/target) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED if (!target.IsReachableBy(controller.pawn)) controller.clear_blackboard_key(BB_BASIC_MOB_MELEE_COOLDOWN_TIMER) - return FALSE + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED var/can_attack_time = controller.blackboard[BB_BASIC_MOB_MELEE_COOLDOWN_TIMER] if (isnull(can_attack_time)) var/blackboard_delay = controller.blackboard[BB_BASIC_MOB_MELEE_DELAY] var/attack_delay = isnull(blackboard_delay) ? DEFAULT_ATTACK_DELAY : blackboard_delay controller.set_blackboard_key(BB_BASIC_MOB_MELEE_COOLDOWN_TIMER, world.time + attack_delay) - return FALSE - + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED if (can_attack_time > world.time) - return FALSE + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED - return TRUE + if (isliving(controller.pawn)) + var/mob/living/pawn = controller.pawn + if (world.time < pawn.next_move) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED -/datum/ai_behavior/basic_melee_attack/finish_action(datum/ai_controller/controller, succeeded, target_key, targeting_strategy_key, hiding_location_key) - . = ..() - controller.clear_blackboard_key(BB_BASIC_MOB_MELEE_COOLDOWN_TIMER) - if(movement_behavior) - controller.change_ai_movement_type(initial(controller.ai_movement)) - if(!succeeded) + var/datum/targeting_strategy/strategy = GET_TARGETING_STRATEGY(targeting_strategy) + if(!strategy.is_valid_target(controller.pawn, target, controller = controller)) controller.clear_blackboard_key(target_key) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED -/datum/ai_behavior/basic_melee_attack/interact_once - terminate_after_action = TRUE + var/hiding_target = strategy.find_hidden_mobs(controller.pawn, target) //If this is valid, theyre hidden in something! -/datum/ai_behavior/basic_melee_attack/interact_once/finish_action(datum/ai_controller/controller, succeeded, target_key, targeting_strategy_key, hiding_location_key) + controller.set_blackboard_key(hiding_location_key, hiding_target) + + var/atom/final_target = hiding_target || target + INVOKE_ASYNC(controller, TYPE_PROC_REF(/datum/ai_controller, ai_interact), final_target, TRUE) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + +/// Single-hit variant: terminates after one successful attack and always clears the target key. +/datum/bt_node/ai_behavior/basic_melee_attack/interact_once + +/datum/bt_node/ai_behavior/basic_melee_attack/interact_once/finish_action(datum/ai_controller/controller, succeeded) . = ..() controller.clear_blackboard_key(target_key) -/datum/ai_behavior/basic_ranged_attack - action_cooldown = 0.6 SECONDS - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_MOVE_AND_PERFORM - required_distance = 3 - /// range we will try chasing the target before giving up - var/chase_range = 9 - ///do we care about avoiding friendly fire? +//Basic ranged attack behavior +/datum/bt_node/ai_behavior/basic_ranged_attack + var/target_key + var/targeting_strategy = BB_TARGETING_STRATEGY + var/hiding_location_key + time_between_perform = 0.6 SECONDS + /// Max range at which we can fire. Make sure your movement actually gets you this close please + var/max_range = 9 + /// Avoid shooting through friendlies. var/avoid_friendly_fire = FALSE -/datum/ai_behavior/basic_ranged_attack/setup(datum/ai_controller/controller, target_key, targeting_strategy_key, hiding_location_key) +/datum/bt_node/ai_behavior/basic_ranged_attack/setup(datum/ai_controller/controller) . = ..() if(HAS_TRAIT(controller.pawn, TRAIT_HANDS_BLOCKED)) return FALSE var/atom/target = controller.blackboard[hiding_location_key] || controller.blackboard[target_key] if(QDELETED(target)) return FALSE - set_movement_target(controller, target) + return TRUE -/datum/ai_behavior/basic_ranged_attack/perform(seconds_per_tick, datum/ai_controller/controller, target_key, targeting_strategy_key, hiding_location_key) +/datum/bt_node/ai_behavior/basic_ranged_attack/perform(seconds_per_tick, datum/ai_controller/controller) var/mob/living/basic/basic_mob = controller.pawn - //targeting strategy will kill the action if not real anymore var/atom/target = controller.blackboard[target_key] - var/datum/targeting_strategy/targeting_strategy = GET_TARGETING_STRATEGY(controller.blackboard[targeting_strategy_key]) - if(!targeting_strategy.can_attack(basic_mob, target, chase_range)) + if(!ispath(targeting_strategy)) + targeting_strategy = controller.blackboard[targeting_strategy] + + var/datum/targeting_strategy/strategy = GET_TARGETING_STRATEGY(targeting_strategy) + + var/atom/hiding_target = strategy.find_hidden_mobs(basic_mob, target) + var/atom/final_target = hiding_target ? hiding_target : target + controller.set_blackboard_key(hiding_location_key, hiding_target) + + if(!can_see(basic_mob, final_target, max_range)) return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED - var/atom/hiding_target = targeting_strategy.find_hidden_mobs(basic_mob, target) //If this is valid, theyre hidden in something! - var/atom/final_target = hiding_target ? hiding_target : target - - if(!can_see(basic_mob, final_target, required_distance)) - return AI_BEHAVIOR_INSTANT - - if(avoid_friendly_fire && check_friendly_in_path(basic_mob, target, targeting_strategy)) + if(avoid_friendly_fire && check_friendly_in_path(basic_mob, target, strategy)) adjust_position(basic_mob, target) - return AI_BEHAVIOR_DELAY + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED - controller.set_blackboard_key(hiding_location_key, hiding_target) basic_mob.RangedAttack(final_target) - return AI_BEHAVIOR_DELAY //only start the cooldown when the shot is shot + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED -/datum/ai_behavior/basic_ranged_attack/finish_action(datum/ai_controller/controller, succeeded, target_key, targeting_strategy_key, hiding_location_key) - . = ..() - if(!succeeded) - controller.clear_blackboard_key(target_key) - -/datum/ai_behavior/basic_ranged_attack/proc/check_friendly_in_path(mob/living/source, atom/target, datum/targeting_strategy/targeting_strategy) +/datum/bt_node/ai_behavior/basic_ranged_attack/proc/check_friendly_in_path(mob/living/source, atom/target, datum/targeting_strategy/targeting_strategy) var/list/turfs_list = calculate_trajectory(source, target) for(var/turf/possible_turf as anything in turfs_list) - for(var/mob/living/potential_friend in possible_turf) - if(!targeting_strategy.can_attack(source, potential_friend)) + if(!targeting_strategy.is_valid_target(source, potential_friend)) return TRUE - return FALSE -/datum/ai_behavior/basic_ranged_attack/proc/adjust_position(mob/living/living_pawn, atom/target) +/datum/bt_node/ai_behavior/basic_ranged_attack/proc/adjust_position(mob/living/living_pawn, atom/target) var/turf/our_turf = get_turf(living_pawn) var/list/possible_turfs = list() - for(var/direction in GLOB.alldirs) var/turf/target_turf = get_step(our_turf, direction) if(isnull(target_turf)) @@ -146,13 +123,12 @@ if(target_turf.is_blocked_turf() || get_dist(target_turf, target) > get_dist(living_pawn, target)) continue possible_turfs += target_turf - if(!length(possible_turfs)) return var/turf/picked_turf = get_closest_atom(/turf, possible_turfs, target) step(living_pawn, get_dir(living_pawn, picked_turf)) -/datum/ai_behavior/basic_ranged_attack/proc/calculate_trajectory(mob/living/source , atom/target) +/datum/bt_node/ai_behavior/basic_ranged_attack/proc/calculate_trajectory(mob/living/source, atom/target) var/list/turf_list = get_line(source, target) var/list_length = length(turf_list) - 1 for(var/i in 1 to list_length) @@ -161,17 +137,14 @@ var/direction_to_turf = get_dir(current_turf, next_turf) if(!ISDIAGONALDIR(direction_to_turf)) continue - for(var/cardinal_direction in GLOB.cardinals) if(cardinal_direction & direction_to_turf) turf_list += get_step(current_turf, cardinal_direction) - turf_list -= get_turf(source) turf_list -= get_turf(target) - return turf_list -/datum/ai_behavior/basic_ranged_attack/avoid_friendly_fire +/datum/bt_node/ai_behavior/basic_ranged_attack/avoid_friendly_fire avoid_friendly_fire = TRUE #undef DEFAULT_ATTACK_DELAY diff --git a/code/datums/ai/basic_mobs/basic_ai_behaviors/befriend_target.dm b/code/datums/ai/basic_mobs/basic_ai_behaviors/befriend_target.dm index 865b028e156..cd50aa734d7 100644 --- a/code/datums/ai/basic_mobs/basic_ai_behaviors/befriend_target.dm +++ b/code/datums/ai/basic_mobs/basic_ai_behaviors/befriend_target.dm @@ -1,19 +1,23 @@ -///behavior to befriend any targets -/datum/ai_behavior/befriend_target +/datum/bt_node/ai_behavior/befriend_target + var/target_key + var/befriend_message + var/long_range_friendship = FALSE + var/forget_target = TRUE -/datum/ai_behavior/befriend_target/perform(seconds_per_tick, datum/ai_controller/controller, target_key, befriend_message) +/datum/bt_node/ai_behavior/befriend_target/perform(seconds_per_tick, datum/ai_controller/controller) var/mob/living/living_pawn = controller.pawn var/mob/living/living_target = controller.blackboard[target_key] if(QDELETED(living_target)) return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - + if(!long_range_friendship && get_dist(living_pawn, living_target) > 1) + return AI_BEHAVIOR_INSTANT living_pawn.befriend(living_target) var/befriend_text = controller.blackboard[befriend_message] if(befriend_text) to_chat(living_target, span_nicegreen("[living_pawn] [befriend_text]")) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED -/datum/ai_behavior/befriend_target/finish_action(datum/ai_controller/controller, succeeded, target_key) +/datum/bt_node/ai_behavior/befriend_target/finish_action(datum/ai_controller/controller, succeeded) . = ..() - controller.clear_blackboard_key(target_key) + if(forget_target) + controller.clear_blackboard_key(target_key) diff --git a/code/datums/ai/basic_mobs/basic_ai_behaviors/call_reinforcements.dm b/code/datums/ai/basic_mobs/basic_ai_behaviors/call_reinforcements.dm new file mode 100644 index 00000000000..4888f3d9054 --- /dev/null +++ b/code/datums/ai/basic_mobs/basic_ai_behaviors/call_reinforcements.dm @@ -0,0 +1,50 @@ +/// Emotes a reinforcement call and alerts nearby faction members, adding the current target to their retaliate lists. +/// Returns FAILURE when there is no valid target or the target is a friend. +/datum/bt_node/ai_behavior/call_reinforcements + /// How far to look for reinforcements + var/reinforcements_range = 15 + ///Target to call reinforcements on + var/target_key = BB_CURRENT_TARGET + +/datum/bt_node/ai_behavior/call_reinforcements/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/target = controller.blackboard[target_key] + if(!istype(target, /mob)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + var/mob/pawn_mob = controller.pawn + var/list/friends = controller.blackboard[BB_FRIENDS_LIST] + if(friends && (target in friends)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + + var/say_text = controller.blackboard[BB_REINFORCEMENTS_SAY] + if(!isnull(say_text)) + pawn_mob.say(say_text, forced = "AI Controller") + else + var/emote_text = controller.blackboard[BB_REINFORCEMENTS_EMOTE] + if(!isnull(emote_text)) + pawn_mob.manual_emote(emote_text) + + for(var/mob/other_mob in oview(reinforcements_range, pawn_mob)) + if(!pawn_mob.faction_check_atom(other_mob) || isnull(other_mob.ai_controller)) + continue + other_mob.ai_controller.set_blackboard_key_assoc_lazylist(BB_BASIC_MOB_RETALIATE_LIST, target, world.time) + other_mob.ai_controller.set_blackboard_key(BB_BASIC_MOB_REINFORCEMENT_TARGET, pawn_mob) + + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED + +/// Mining/swarm variant: boosts priority rather than forcing retaliation, shorter range and faster cooldown. +/datum/bt_node/ai_behavior/call_reinforcements/mining + reinforcements_range = 7 + +/datum/bt_node/ai_behavior/call_reinforcements/mining/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/target = controller.blackboard[target_key] + if(!istype(target, /mob)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + var/mob/pawn_mob = controller.pawn + for(var/mob/other_mob in oview(reinforcements_range, pawn_mob)) + if(!pawn_mob.faction_check_atom(other_mob) || isnull(other_mob.ai_controller)) + continue + var/list/existing_requests = other_mob.ai_controller.blackboard[BB_MINING_MOB_REINFORCEMENTS_REQUESTS] + if(!existing_requests || !existing_requests[target]) + other_mob.ai_controller.set_blackboard_key_assoc_lazylist(BB_MINING_MOB_REINFORCEMENTS_REQUESTS, target, list()) + other_mob.ai_controller.add_blackboard_key_assoc(BB_MINING_MOB_REINFORCEMENTS_REQUESTS, target, world.time) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED diff --git a/code/datums/ai/basic_mobs/basic_ai_behaviors/clear_key.dm b/code/datums/ai/basic_mobs/basic_ai_behaviors/clear_key.dm deleted file mode 100644 index ae0711789fa..00000000000 --- a/code/datums/ai/basic_mobs/basic_ai_behaviors/clear_key.dm +++ /dev/null @@ -1,14 +0,0 @@ -/// Clears a blackboard key (or keys), simply if you want to do this after an action without making a subtype -/datum/ai_behavior/clear_key - -/datum/ai_behavior/clear_key/perform(seconds_per_tick, datum/ai_controller/controller, list/to_clear) - return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED - -/datum/ai_behavior/clear_key/finish_action(datum/ai_controller/controller, succeeded, list/to_clear) - . = ..() - if (!to_clear) - return - if (!islist(to_clear)) - to_clear = list(to_clear) - for (var/key in to_clear) - controller.clear_blackboard_key(key) diff --git a/code/datums/ai/basic_mobs/basic_ai_behaviors/climb_tree.dm b/code/datums/ai/basic_mobs/basic_ai_behaviors/climb_tree.dm deleted file mode 100644 index c30943caf50..00000000000 --- a/code/datums/ai/basic_mobs/basic_ai_behaviors/climb_tree.dm +++ /dev/null @@ -1,35 +0,0 @@ -/datum/ai_behavior/find_and_set/valid_tree - -/datum/ai_behavior/find_and_set/valid_tree/search_tactic(datum/ai_controller/controller, locate_path, search_range = SEARCH_TACTIC_DEFAULT_RANGE) - var/list/valid_trees = list() - for (var/obj/structure/flora/tree/tree_target in oview(search_range, controller.pawn)) - if(istype(tree_target, /obj/structure/flora/tree/dead)) //no died trees - continue - valid_trees += tree_target - - if(valid_trees.len) - return pick(valid_trees) - -/datum/ai_behavior/climb_tree - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH - -/datum/ai_behavior/climb_tree/setup(datum/ai_controller/controller, target_key) - . = ..() - var/atom/target = controller.blackboard[target_key] - if(QDELETED(target)) - return FALSE - - set_movement_target(controller, target) - -/datum/ai_behavior/climb_tree/perform(seconds_per_tick, datum/ai_controller/controller, target_key) - var/obj/structure/flora/target_tree = controller.blackboard[target_key] - var/mob/living/basic/living_pawn = controller.pawn - if(QDELETED(living_pawn)) // pawn can be null at this point - return - SEND_SIGNAL(living_pawn, COMSIG_LIVING_CLIMB_TREE, target_tree) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -/datum/ai_behavior/climb_tree/finish_action(datum/ai_controller/controller, succeeded, target_key) - . = ..() - if(succeeded) - controller.clear_blackboard_key(target_key) diff --git a/code/datums/ai/basic_mobs/basic_ai_behaviors/consider_venting.bt.json b/code/datums/ai/basic_mobs/basic_ai_behaviors/consider_venting.bt.json new file mode 100644 index 00000000000..54e4af4336a --- /dev/null +++ b/code/datums/ai/basic_mobs/basic_ai_behaviors/consider_venting.bt.json @@ -0,0 +1,46 @@ +{ + "dm_type": "/datum/bt_node/subtree/consider_venting", + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/is_in_vent", + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/exit_vent" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_ENTRY_VENT_TARGET" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/cooldown", + "vars": { + "cooldown_key": "BB_VENTING_COOLDOWN", + "cooldown_duration": "BB_VENTCRAWL_COOLDOWN" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_ENTRY_VENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/enter_vent" + } + ] + } + } + } + ] +} diff --git a/code/datums/ai/basic_mobs/basic_ai_behaviors/emote_with_target.dm b/code/datums/ai/basic_mobs/basic_ai_behaviors/emote_with_target.dm deleted file mode 100644 index 7960301d704..00000000000 --- a/code/datums/ai/basic_mobs/basic_ai_behaviors/emote_with_target.dm +++ /dev/null @@ -1,28 +0,0 @@ -/datum/ai_behavior/emote_on_target - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH - - -/datum/ai_behavior/emote_on_target/setup(datum/ai_controller/controller, target_key) - . = ..() - var/atom/hunt_target = controller.blackboard[target_key] - if (isnull(hunt_target)) - return FALSE - set_movement_target(controller, hunt_target) - - -/datum/ai_behavior/emote_on_target/perform(seconds_per_tick, datum/ai_controller/controller, target_key, list/emote_list) - var/atom/target = controller.blackboard[target_key] - if(!length(emote_list) || isnull(target)) - return AI_BEHAVIOR_FAILED | AI_BEHAVIOR_DELAY - run_emote(controller.pawn, target, emote_list) - return AI_BEHAVIOR_SUCCEEDED | AI_BEHAVIOR_DELAY - - -/datum/ai_behavior/emote_on_target/finish_action(datum/ai_controller/controller, succeeded, target_key) - . = ..() - if(succeeded) - controller.clear_blackboard_key(target_key) - - -/datum/ai_behavior/emote_on_target/proc/run_emote(mob/living/living_pawn, atom/target, list/emote_list) - living_pawn.manual_emote("[pick(emote_list)] [target]") diff --git a/code/datums/ai/basic_mobs/basic_ai_behaviors/find_flee_location.dm b/code/datums/ai/basic_mobs/basic_ai_behaviors/find_flee_location.dm new file mode 100644 index 00000000000..9efe9d6fc87 --- /dev/null +++ b/code/datums/ai/basic_mobs/basic_ai_behaviors/find_flee_location.dm @@ -0,0 +1,37 @@ +/// Finds the best adjacent turf to flee to away from a threat and stores it in a blackboard key. +/// Tries get_step_away first, then falls back to shuffled directions if blocked. +/// Returns INSTANT SUCCESS if a step was found, INSTANT FAILURE if completely cornered. +/datum/bt_node/ai_behavior/find_flee_location + var/target_key + var/hiding_location_key + var/destination_key + +/datum/bt_node/ai_behavior/find_flee_location/perform(seconds_per_tick, datum/ai_controller/controller) + var/run_distance = controller.blackboard[BB_BASIC_MOB_FLEE_DISTANCE] || DEFAULT_BASIC_FLEE_DISTANCE + var/atom/target = controller.blackboard[hiding_location_key] || controller.blackboard[target_key] + if(QDELETED(target) || !can_see(controller.pawn, target, run_distance)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + + var/turf/flee_turf = get_flee_step(controller, target) + if(!flee_turf) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + + controller.set_blackboard_key(destination_key, flee_turf) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED + +/datum/bt_node/ai_behavior/find_flee_location/proc/get_flee_step(datum/ai_controller/controller, atom/target) + var/mob/living/pawn = controller.pawn + var/turf/pawn_turf = get_turf(pawn) + var/datum/can_pass_info/pass_info = new(pawn, controller.get_access()) + var/turf/next_step = get_step_away(pawn, target) + if(!isnull(next_step) && next_step != pawn_turf && !next_step.density && !pawn_turf.LinkBlockedWithAccess(next_step, pass_info)) + return next_step + var/list/all_dirs = GLOB.alldirs.Copy() + all_dirs -= get_dir(pawn, next_step) + all_dirs -= get_dir(pawn, target) + shuffle_inplace(all_dirs) + for(var/dir in all_dirs) + next_step = get_step(pawn, dir) + if(!isnull(next_step) && !next_step.density && !pawn_turf.LinkBlockedWithAccess(next_step, pass_info)) + return next_step + return null diff --git a/code/datums/ai/basic_mobs/basic_ai_behaviors/find_parent.dm b/code/datums/ai/basic_mobs/basic_ai_behaviors/find_parent.dm index 31735f99231..7e8a0a4d391 100644 --- a/code/datums/ai/basic_mobs/basic_ai_behaviors/find_parent.dm +++ b/code/datums/ai/basic_mobs/basic_ai_behaviors/find_parent.dm @@ -1,35 +1,51 @@ -/datum/ai_behavior/find_mom - ///range to look for the mom +/// Looks around for a nearby adult of one of BB_FIND_MOM_TYPES (skipping BB_IGNORE_MOM_TYPES) and stores it. +/datum/bt_node/ai_behavior/find_mom + time_between_perform = 2 SECONDS + /// How far to look for our parent. var/look_range = 7 + /// Blackboard key holding the list of typepaths we accept as parents. + var/mom_types_key = BB_FIND_MOM_TYPES + /// Blackboard key holding typepaths to skip even if they match (e.g. other babies). + var/ignore_types_key = BB_IGNORE_MOM_TYPES + /// Blackboard key to store the found parent in. + var/found_mom_key = BB_FOUND_MOM -/datum/ai_behavior/find_mom/perform(seconds_per_tick, datum/ai_controller/controller, mom_key, ignore_mom_key, found_mom) +/datum/bt_node/ai_behavior/find_mom/perform(seconds_per_tick, datum/ai_controller/controller) var/mob/living_pawn = controller.pawn - var/list/all_moms = list() - var/list/mom_types = controller.blackboard[mom_key] - var/list/ignore_types = controller.blackboard[ignore_mom_key] - + var/list/mom_types = controller.blackboard[mom_types_key] if(!length(mom_types)) return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + var/list/ignore_types = controller.blackboard[ignore_types_key] + var/list/all_moms = list() for(var/mob/mother in oview(look_range, living_pawn)) - if (is_possible_mom(mother, mom_types, ignore_types)) + if(is_possible_mom(mother, mom_types, ignore_types)) all_moms += mother if(length(all_moms)) - controller.set_blackboard_key(found_mom, pick(all_moms)) + controller.set_blackboard_key(found_mom_key, pick(all_moms)) return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED -/datum/ai_behavior/find_mom/proc/is_possible_mom(mob/mother, list/mom_types, list/ignore_types) +/datum/bt_node/ai_behavior/find_mom/proc/is_possible_mom(mob/mother, list/mom_types, list/ignore_types) if(!is_type_in_list(mother, mom_types)) return FALSE - if(is_type_in_list(mother, ignore_types)) // so the not permanent baby and the permanent baby subtype dont followed each other + if(is_type_in_list(mother, ignore_types)) return FALSE return TRUE -/datum/ai_behavior/find_mom/raptor/is_possible_mom(mob/mother, list/mom_types, list/ignore_types) - . = ..() - if (!. || !istype(mother, /mob/living/basic/raptor)) - return FALSE - var/mob/living/basic/raptor/raptor = mother - return raptor.growth_stage == RAPTOR_ADULT +/// A baby emotes at its parent: crying if the parent is dead, dancing happily otherwise. +/datum/bt_node/ai_behavior/look_to_parent + /// Blackboard key holding the parent to react to. + var/parent_key = BB_FOUND_MOM + +/datum/bt_node/ai_behavior/look_to_parent/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/parent = controller.blackboard[parent_key] + if(QDELETED(parent)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + var/mob/living/baby = controller.pawn + if(parent.stat == DEAD) + baby.manual_emote("cries for their parent!") + else + baby.manual_emote("dances around their parent!") + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED diff --git a/code/datums/ai/basic_mobs/basic_ai_behaviors/interact_with_target.dm b/code/datums/ai/basic_mobs/basic_ai_behaviors/interact_with_target.dm deleted file mode 100644 index e3b202c5e16..00000000000 --- a/code/datums/ai/basic_mobs/basic_ai_behaviors/interact_with_target.dm +++ /dev/null @@ -1,29 +0,0 @@ -///behavior for general interactions with any targets -/datum/ai_behavior/interact_with_target - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH - ///should we be clearing the target after the fact? - var/clear_target = TRUE - ///should our combat mode be off during interaction? - var/combat_mode = TRUE - -/datum/ai_behavior/interact_with_target/setup(datum/ai_controller/controller, target_key) - . = ..() - var/atom/target = controller.blackboard[target_key] - if(QDELETED(target)) - return FALSE - set_movement_target(controller, target) - -/datum/ai_behavior/interact_with_target/perform(seconds_per_tick, datum/ai_controller/controller, target_key) - var/atom/target = controller.blackboard[target_key] - if(QDELETED(target) || !pre_interact(controller, target)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - controller.ai_interact(target, combat_mode) - return AI_BEHAVIOR_SUCCEEDED | AI_BEHAVIOR_DELAY - -/datum/ai_behavior/interact_with_target/finish_action(datum/ai_controller/controller, succeeded, target_key) - . = ..() - if(clear_target || !succeeded) - controller.clear_blackboard_key(target_key) - -/datum/ai_behavior/interact_with_target/proc/pre_interact(datum/ai_controller/controller, target) - return TRUE diff --git a/code/datums/ai/basic_mobs/basic_ai_behaviors/nearest_targeting.dm b/code/datums/ai/basic_mobs/basic_ai_behaviors/nearest_targeting.dm index 8a570375cba..9a0d6d041ef 100644 --- a/code/datums/ai/basic_mobs/basic_ai_behaviors/nearest_targeting.dm +++ b/code/datums/ai/basic_mobs/basic_ai_behaviors/nearest_targeting.dm @@ -1,13 +1,6 @@ -/// Picks targets based on which one is closest to you, choice between targets at equal distance is arbitrary -/datum/ai_behavior/find_potential_targets/nearest +/// Pick nearest instead of any, we should probably move this into a datum of some kind in the future? +/datum/bt_node/ai_behavior/acquire_target/update_combat_targets/nearest -/datum/ai_behavior/find_potential_targets/nearest/pick_final_target(datum/ai_controller/controller, list/filtered_targets) +/datum/bt_node/ai_behavior/acquire_target/update_combat_targets/nearest/pick_final_target(datum/ai_controller/controller, list/filtered_targets) var/turf/our_position = get_turf(controller.pawn) return get_closest_atom(/atom/, filtered_targets, our_position) - -/// As above but targets have been filtered from the 'retaliate' blackboard list -/datum/ai_behavior/target_from_retaliate_list/nearest - -/datum/ai_behavior/target_from_retaliate_list/nearest/pick_final_target(datum/ai_controller/controller, list/enemies_list) - var/turf/our_position = get_turf(controller.pawn) - return get_closest_atom(/atom/, enemies_list, our_position) diff --git a/code/datums/ai/basic_mobs/basic_ai_behaviors/pick_up_item.dm b/code/datums/ai/basic_mobs/basic_ai_behaviors/pick_up_item.dm deleted file mode 100644 index 1b65eaa507c..00000000000 --- a/code/datums/ai/basic_mobs/basic_ai_behaviors/pick_up_item.dm +++ /dev/null @@ -1,44 +0,0 @@ -/** - * Simple behaviour for picking up an item we are already in range of. - * The blackboard storage key isn't very safe because it doesn't make sense to register signals in here. - * Use the AI held item component to manage this. - */ -/datum/ai_behavior/pick_up_item - -/datum/ai_behavior/pick_up_item/setup(datum/ai_controller/controller, target_key, storage_key) - . = ..() - var/obj/item/target = controller.blackboard[target_key] - return isitem(target) && isturf(target.loc) && !target.anchored - -/datum/ai_behavior/pick_up_item/perform(seconds_per_tick, datum/ai_controller/controller, target_key, storage_key) - var/obj/item/target = controller.blackboard[target_key] - if(QDELETED(target) || !isturf(target.loc)) // Someone picked it up or it got deleted - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - if(!controller.pawn.Adjacent(target)) // It teleported - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - pickup_item(controller, target, storage_key) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -/datum/ai_behavior/pick_up_item/finish_action(datum/ai_controller/controller, success, target_key, storage_key) - . = ..() - controller.clear_blackboard_key(target_key) - -/datum/ai_behavior/pick_up_item/proc/pickup_item(datum/ai_controller/controller, obj/item/target, storage_key) - var/atom/pawn = controller.pawn - drop_existing_item(controller, storage_key) - pawn.visible_message(span_notice("[pawn] picks up [target].")) - target.forceMove(pawn) - controller.set_blackboard_key(storage_key, target) - return TRUE - -/datum/ai_behavior/pick_up_item/proc/drop_existing_item(datum/ai_controller/controller, storage_key) - var/obj/item/carried_item = controller.blackboard[storage_key] - if(!carried_item) - return - controller.clear_blackboard_key(storage_key) - var/atom/pawn = controller.pawn - if(carried_item.loc != pawn) - return - pawn.visible_message(span_notice("[pawn] drops [carried_item].")) - carried_item.forceMove(get_turf(pawn)) - return TRUE diff --git a/code/datums/ai/basic_mobs/basic_ai_behaviors/play_dead.dm b/code/datums/ai/basic_mobs/basic_ai_behaviors/play_dead.dm new file mode 100644 index 00000000000..49f27564b97 --- /dev/null +++ b/code/datums/ai/basic_mobs/basic_ai_behaviors/play_dead.dm @@ -0,0 +1,25 @@ +/// Plays dead until a per-tick probability check (default 10%) triggers revival. +/datum/bt_node/ai_behavior/play_dead + var/probability = 10 + +/datum/bt_node/ai_behavior/play_dead/setup(datum/ai_controller/controller) + var/mob/living/basic/pawn = controller.pawn + if(!istype(pawn) || pawn.stat) + return FALSE + INVOKE_ASYNC(pawn, TYPE_PROC_REF(/mob, emote), "deathgasp", intentional = FALSE) + ADD_TRAIT(pawn, TRAIT_FAKEDEATH, BASIC_MOB_DEATH_TRAIT) + pawn.look_dead() + +/datum/bt_node/ai_behavior/play_dead/perform(seconds_per_tick, datum/ai_controller/controller) + if(SPT_PROB(probability, seconds_per_tick)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + return AI_BEHAVIOR_DELAY + +/datum/bt_node/ai_behavior/play_dead/finish_action(datum/ai_controller/controller, succeeded) + . = ..() + var/mob/living/basic/pawn = controller.pawn + if(QDELETED(pawn) || pawn.stat) + return + pawn.visible_message(span_notice("[pawn] miraculously springs back to life!")) + REMOVE_TRAIT(pawn, TRAIT_FAKEDEATH, BASIC_MOB_DEATH_TRAIT) + pawn.look_alive() diff --git a/code/datums/ai/basic_mobs/basic_ai_behaviors/pull_target.dm b/code/datums/ai/basic_mobs/basic_ai_behaviors/pull_target.dm deleted file mode 100644 index 900b122dcb3..00000000000 --- a/code/datums/ai/basic_mobs/basic_ai_behaviors/pull_target.dm +++ /dev/null @@ -1,22 +0,0 @@ -/datum/ai_behavior/pull_target - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION | AI_BEHAVIOR_REQUIRE_REACH - -/datum/ai_behavior/pull_target/setup(datum/ai_controller/controller, target_key) - . = ..() - var/atom/target = controller.blackboard[target_key] - if(QDELETED(target)) - return FALSE - set_movement_target(controller, target) - -/datum/ai_behavior/pull_target/perform(seconds_per_tick, datum/ai_controller/controller, target_key) - var/atom/movable/target = controller.blackboard[target_key] - if(QDELETED(target) || target.anchored || target.pulledby) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - var/mob/living/our_mob = controller.pawn - our_mob.start_pulling(target) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -/datum/ai_behavior/pull_target/finish_action(datum/ai_controller/controller, succeeded, target_key) - . = ..() - if(!succeeded) - controller.clear_blackboard_key(target_key) diff --git a/code/datums/ai/basic_mobs/basic_ai_behaviors/run_away_from_target.dm b/code/datums/ai/basic_mobs/basic_ai_behaviors/run_away_from_target.dm deleted file mode 100644 index f23707e72b8..00000000000 --- a/code/datums/ai/basic_mobs/basic_ai_behaviors/run_away_from_target.dm +++ /dev/null @@ -1,76 +0,0 @@ -/// Move to a position further away from your current target -/datum/ai_behavior/run_away_from_target - required_distance = 0 - action_cooldown = 0 - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_MOVE_AND_PERFORM | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION - /// How far do we try to run? Further makes for smoother running, but potentially weirder pathfinding - var/run_distance = DEFAULT_BASIC_FLEE_DISTANCE - /// Clear target if we finish the action unsuccessfully - var/clear_failed_targets = TRUE - -/datum/ai_behavior/run_away_from_target/setup(datum/ai_controller/controller, target_key, hiding_location_key) - var/atom/target = controller.blackboard[hiding_location_key] || controller.blackboard[target_key] - if(QDELETED(target)) - return FALSE - run_distance = controller.blackboard[BB_BASIC_MOB_FLEE_DISTANCE] || initial(run_distance) - if(!plot_path_away_from(controller, target)) - return FALSE - return ..() - -/datum/ai_behavior/run_away_from_target/perform(seconds_per_tick, datum/ai_controller/controller, target_key, hiding_location_key) - if (controller.blackboard[BB_BASIC_MOB_STOP_FLEEING]) - return AI_BEHAVIOR_DELAY - var/atom/target = controller.blackboard[hiding_location_key] || controller.blackboard[target_key] - if (QDELETED(target) || !can_see(controller.pawn, target, run_distance)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - if (get_dist(controller.pawn, controller.current_movement_target) > required_distance) - return AI_BEHAVIOR_DELAY // Still heading over - if (plot_path_away_from(controller, target)) - return AI_BEHAVIOR_DELAY - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - -/datum/ai_behavior/run_away_from_target/proc/plot_path_away_from(datum/ai_controller/controller, atom/target) - var/turf/target_destination = get_turf(controller.pawn) - var/static/list/offset_angles = list(45, 90, 135, 180, 225, 270) - for(var/angle in offset_angles) - var/turf/test_turf = get_furthest_turf(controller.pawn, angle, target) - if(isnull(test_turf)) - continue - var/distance_from_target = get_dist(target, test_turf) - if(distance_from_target <= get_dist(target, target_destination)) - continue - target_destination = test_turf - if(distance_from_target == run_distance) //we already got the max running distance - break - - if (target_destination == get_turf(controller.pawn)) - return FALSE - set_movement_target(controller, target_destination) - return TRUE - -/datum/ai_behavior/run_away_from_target/proc/get_furthest_turf(atom/source, angle, atom/target) - var/turf/return_turf - var/list/airlocks = SSmachines.get_machines_by_type_and_subtypes(/obj/machinery/door/airlock) - for(var/i in 1 to run_distance) - var/turf/test_destination = get_ranged_target_turf_direct(source, target, range = i, offset = angle) - if(test_destination.is_blocked_turf(source_atom = source, ignore_atoms = airlocks)) - break - return_turf = test_destination - return return_turf - -/datum/ai_behavior/run_away_from_target/finish_action(datum/ai_controller/controller, succeeded, target_key, hiding_location_key) - . = ..() - if (clear_failed_targets) - controller.clear_blackboard_key(target_key) - -/datum/ai_behavior/run_away_from_target/run_and_shoot - clear_failed_targets = FALSE - -/datum/ai_behavior/run_away_from_target/run_and_shoot/perform(seconds_per_tick, datum/ai_controller/controller, target_key, hiding_location_key) - var/atom/target = controller.blackboard[target_key] - if(QDELETED(target)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - var/mob/living/living_pawn = controller.pawn - living_pawn.RangedAttack(target) - return ..() - diff --git a/code/datums/ai/basic_mobs/basic_ai_behaviors/set_travel_destination.dm b/code/datums/ai/basic_mobs/basic_ai_behaviors/set_travel_destination.dm deleted file mode 100644 index 6bf6dbb7fdb..00000000000 --- a/code/datums/ai/basic_mobs/basic_ai_behaviors/set_travel_destination.dm +++ /dev/null @@ -1,10 +0,0 @@ -/datum/ai_behavior/set_travel_destination - -/datum/ai_behavior/set_travel_destination/perform(seconds_per_tick, datum/ai_controller/controller, target_key, location_key) - var/atom/target = controller.blackboard[target_key] - - if(QDELETED(target)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - controller.set_blackboard_key(location_key, target) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED diff --git a/code/datums/ai/basic_mobs/basic_ai_behaviors/step_towards_turf.dm b/code/datums/ai/basic_mobs/basic_ai_behaviors/step_towards_turf.dm deleted file mode 100644 index 7cb3a7b9f14..00000000000 --- a/code/datums/ai/basic_mobs/basic_ai_behaviors/step_towards_turf.dm +++ /dev/null @@ -1,74 +0,0 @@ -/** - * # Step towards turf - * Moves a short distance towards a location repeatedly until you arrive at the destination. - * You'd use this over travel_towards if you're travelling a long distance over a long time, because the AI controller has a maximum range. - */ -/datum/ai_behavior/step_towards_turf - required_distance = 0 - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION - /// How far ahead do we plot movement per action? Further means longer until we return to the decision tree, fewer means jerkier movement - /// This can still result in long moves because this is "a tile x tiles away" not "only move x tiles", you might path around some walls - var/step_distance = 3 - -/datum/ai_behavior/step_towards_turf/setup(datum/ai_controller/controller, turf_key) - var/turf/target_turf = controller.blackboard[turf_key] - if (QDELETED(target_turf) || target_turf.is_blocked_turf(exclude_mobs = TRUE)) - target_turf = find_destination_turf(args) - if (!target_turf) - return FALSE - controller.set_blackboard_key(turf_key, target_turf) - - if (target_turf.z != controller.pawn.z) - return FALSE - - var/turf/destination = plot_movement(controller, target_turf) - if (!destination) - return FALSE - set_movement_target(controller, destination) - return ..() - -/** - * Get a turf to aim towards if we don't already have one, the default behaviour is actually to not do this but we want to extend it - * Gets passed all of the arguments from `setup` - */ -/datum/ai_behavior/step_towards_turf/proc/find_destination_turf() - return null - -/** - * Figure out where we're going to move to, which isn't all the way to the destination in one go - */ -/datum/ai_behavior/step_towards_turf/proc/plot_movement(datum/ai_controller/controller, turf/target_turf) - var/distance_to_destination = get_dist(controller.pawn, target_turf) - if (distance_to_destination <= step_distance) - return target_turf - - var/direction_to_destination = get_dir(controller.pawn, target_turf) - return get_ranged_target_turf(controller.pawn, direction_to_destination, step_distance) - -// We actually only wanted the movement so if we've arrived we're done -/datum/ai_behavior/step_towards_turf/perform(seconds_per_tick, datum/ai_controller/controller, area_key, turf_key) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -/** - * # Step towards turf in area - * Moves a short distance towards a location in an area - * Unlike step_towards_turf it will reacquire a new turf from the area if it loses its target - */ -/datum/ai_behavior/step_towards_turf/in_area - -/datum/ai_behavior/step_towards_turf/in_area/setup(datum/ai_controller/controller, turf_key, area_key) - var/area/target_area = controller.blackboard[area_key] - if (!target_area) - return FALSE - - return ..() - -// Return the first valid turf in the area to replace a lost target -/datum/ai_behavior/step_towards_turf/in_area/find_destination_turf(datum/ai_controller/controller, turf_key, area_key) - var/area/target_area = controller.blackboard[area_key] - var/list/target_area_turfs = get_area_turfs(target_area.type) - for (var/turf/potential_target as anything in target_area_turfs) - if (potential_target.is_blocked_turf(exclude_mobs = TRUE)) - continue - return potential_target - return null diff --git a/code/datums/ai/basic_mobs/basic_ai_behaviors/stop_and_stare.dm b/code/datums/ai/basic_mobs/basic_ai_behaviors/stop_and_stare.dm index 4ce1f877b18..a5a60d72638 100644 --- a/code/datums/ai/basic_mobs/basic_ai_behaviors/stop_and_stare.dm +++ b/code/datums/ai/basic_mobs/basic_ai_behaviors/stop_and_stare.dm @@ -1,27 +1,29 @@ -/// Makes a mob simply stop and stare at a movable... yea... -/datum/ai_behavior/stop_and_stare - behavior_flags = AI_BEHAVIOR_MOVE_AND_PERFORM +/// Faces a nearby scary atom and holds still for a while. +/datum/bt_node/ai_behavior/stop_and_stare + /// Blackboard key holding the atom we're staring at. + var/target_key = BB_STATIONARY_CAUSE + /// Blackboard key holding how long (in deciseconds) to stay frozen for. + var/stare_duration_key = BB_STATIONARY_SECONDS -/datum/ai_behavior/stop_and_stare/setup(datum/ai_controller/controller, target_key) - . = ..() +/datum/bt_node/ai_behavior/stop_and_stare/setup(datum/ai_controller/controller) var/atom/movable/target = controller.blackboard[target_key] return ismovable(target) && isturf(target.loc) && ismob(controller.pawn) -/datum/ai_behavior/stop_and_stare/get_cooldown(datum/ai_controller/cooldown_for) - return cooldown_for.blackboard[BB_STATIONARY_COOLDOWN] +/datum/bt_node/ai_behavior/stop_and_stare/get_cooldown(datum/ai_controller/cooldown_for) + return cooldown_for.blackboard[stare_duration_key] || ..() -/datum/ai_behavior/stop_and_stare/perform(seconds_per_tick, datum/ai_controller/controller, target_key) +/datum/bt_node/ai_behavior/stop_and_stare/perform(seconds_per_tick, datum/ai_controller/controller) var/atom/movable/target = controller.blackboard[target_key] - if(!ismovable(target) || !isturf(target.loc)) // just to make sure that nothing funky happened between setup and perform - return AI_BEHAVIOR_DELAY + if(!ismovable(target) || !isturf(target.loc)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED var/mob/pawn_mob = controller.pawn - var/turf/pawn_turf = get_turf(pawn_mob) - pawn_mob.face_atom(target) pawn_mob.balloon_alert_to_viewers("stops and stares...") - set_movement_target(controller, pawn_turf, /datum/ai_movement/complete_stop) + // Returning a long cooldown keeps this leaf RUNNING (and thus the mob standing still) for the stare. + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - if(controller.blackboard[BB_STATIONARY_MOVE_TO_TARGET]) - addtimer(CALLBACK(src, PROC_REF(set_movement_target), controller, target, initial(controller.ai_movement)), (controller.blackboard[BB_STATIONARY_SECONDS] + 1 SECONDS)) - return AI_BEHAVIOR_DELAY +/datum/bt_node/ai_behavior/stop_and_stare/finish_action(datum/ai_controller/controller, succeeded) + . = ..() + // Forget the cause so we can be spooked fresh next time it wanders into view. + controller.clear_blackboard_key(target_key) diff --git a/code/datums/ai/basic_mobs/basic_ai_behaviors/targeted_mob_ability.dm b/code/datums/ai/basic_mobs/basic_ai_behaviors/targeted_mob_ability.dm index 1e438ea0979..6b1f18e2441 100644 --- a/code/datums/ai/basic_mobs/basic_ai_behaviors/targeted_mob_ability.dm +++ b/code/datums/ai/basic_mobs/basic_ai_behaviors/targeted_mob_ability.dm @@ -1,55 +1,55 @@ -/** - * # Targeted Mob Ability - * Attempts to use a mob's cooldown ability on a target - */ -/datum/ai_behavior/targeted_mob_ability +/// Tries to use a specified ability on the current target +/datum/bt_node/ai_behavior/targeted_mob_ability + var/ability_key = BB_GENERIC_ACTION + var/target_key + /// Maximum distance at which the ability can fire (inclusive cuz this is tg :) ) + var/maximum_distance = 0 + ///Does this require adjacency? + var/require_adjacency = FALSE + +/datum/bt_node/ai_behavior/targeted_mob_ability/perform(seconds_per_tick, datum/ai_controller/controller) + var/async_flags = handle_async() + if(async_flags) + return async_flags -/datum/ai_behavior/targeted_mob_ability/perform(seconds_per_tick, datum/ai_controller/controller, ability_key, target_key) var/datum/action/cooldown/ability = controller.blackboard[ability_key] var/mob/living/target = controller.blackboard[target_key] if(QDELETED(ability) || QDELETED(target)) return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED - var/mob/pawn = controller.pawn - pawn.face_atom(target) + if(maximum_distance && get_dist(controller.pawn, target) > maximum_distance) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + if(require_adjacency && !controller.pawn.Adjacent(target)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + if(!ability.IsAvailable()) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + + var/mob/pawn_mob = controller.pawn + pawn_mob.face_atom(target) + return start_async() + +/datum/bt_node/ai_behavior/targeted_mob_ability/perform_async(datum/ai_controller/controller) + var/datum/action/cooldown/ability = controller.blackboard[ability_key] + var/atom/target = controller.blackboard[target_key] var/result = ability.Trigger(target = target) - if(result) - return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED - return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + if(!async_still_valid()) + return + finish_async(result ? AI_BEHAVIOR_SUCCEEDED : AI_BEHAVIOR_FAILED) -/** - * # Try Mob Ability and plan execute - * Attempts to use a mob's cooldown ability on a target and then move the target into a special target blackboard datum - * Doesn't need another subtype to clear BB_BASIC_MOB_EXECUTION_TARGET because it will be the target key for the normal action - */ -/datum/ai_behavior/targeted_mob_ability/and_plan_execute +/// Variant for abilities that require adjacency (distance ≤ 1). +/datum/bt_node/ai_behavior/targeted_mob_ability/melee + require_adjacency = TRUE -/datum/ai_behavior/targeted_mob_ability/and_plan_execute/finish_action(datum/ai_controller/controller, succeeded, ability_key, target_key) + +/datum/bt_node/ai_behavior/targeted_mob_ability/and_plan_execute + +/datum/bt_node/ai_behavior/targeted_mob_ability/and_plan_execute/finish_action(datum/ai_controller/controller, succeeded) controller.set_blackboard_key(BB_BASIC_MOB_EXECUTION_TARGET, controller.blackboard[target_key]) return ..() -/** - * # Try Mob Ability and clear target - * Attempts to use a mob's cooldown ability on a target and releases the target when the action completes - */ -/datum/ai_behavior/targeted_mob_ability/and_clear_target +/datum/bt_node/ai_behavior/targeted_mob_ability/and_clear_target -/datum/ai_behavior/targeted_mob_ability/and_clear_target/finish_action(datum/ai_controller/controller, succeeded, ability_key, target_key) +/datum/bt_node/ai_behavior/targeted_mob_ability/and_clear_target/finish_action(datum/ai_controller/controller, succeeded) . = ..() controller.clear_blackboard_key(target_key) -/** - * Attempts to move into the provided range and then use a mob's cooldown ability on a target - */ -/datum/ai_behavior/targeted_mob_ability/min_range - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT - required_distance = 6 -/datum/ai_behavior/targeted_mob_ability/min_range/setup(datum/ai_controller/controller, ability_key, target_key) - . = ..() - var/atom/target = controller.blackboard[target_key] - if(QDELETED(target)) - return FALSE - set_movement_target(controller, target) - -/datum/ai_behavior/targeted_mob_ability/min_range/short - required_distance = 3 diff --git a/code/datums/ai/basic_mobs/basic_ai_behaviors/targeting.dm b/code/datums/ai/basic_mobs/basic_ai_behaviors/targeting.dm index ce46d4c1ab9..2fd2e494ef1 100644 --- a/code/datums/ai/basic_mobs/basic_ai_behaviors/targeting.dm +++ b/code/datums/ai/basic_mobs/basic_ai_behaviors/targeting.dm @@ -5,101 +5,74 @@ GLOBAL_ALIST_EMPTY(hostile_machines_by_z) /// Must be kept up to date with the contents of hostile_machines GLOBAL_LIST_INIT(target_interested_atoms, typecacheof(list(/mob, /obj/machinery/porta_turret, /obj/vehicle/sealed/mecha))) -/datum/ai_behavior/find_potential_targets - action_cooldown = 2 SECONDS - behavior_flags = AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION - /// How far can we see stuff? - var/vision_range = 9 + +///Used to find combat targets; Allow finding things hidden in things such as lockers too. +/datum/bt_node/ai_behavior/acquire_target/update_combat_targets + target_source = /datum/target_source/hearers + targeting_strategy = BB_TARGETING_STRATEGY + vision_range = 9 + target_loss_distance = 16 /// Blackboard key for aggro range, uses vision range if not specified var/aggro_range_key = BB_AGGRO_RANGE - /// Range in which we can acquire a new target - var/aggro_grab_range_key = BB_AGGRO_GRAB_RANGE - /// Blackboard key for the target priority strategy + /// Blackboard key holding the hiding-location atom (e.g. closet the target ducked into) + var/hiding_location_key + /// Blackboard key holding the /datum/target_priority_strategy typepath for selection var/priority_strategy_key = BB_TARGET_PRIORITY_STRATEGY /// If we have a priority strategy set, how often do we refresh our target search? var/priority_refresh_cooldown = 6 SECONDS -/datum/ai_behavior/find_potential_targets/get_cooldown(datum/ai_controller/cooldown_for) - if(cooldown_for.blackboard[BB_FIND_TARGETS_FIELD(type)]) +/datum/bt_node/ai_behavior/acquire_target/update_combat_targets/get_cooldown(datum/ai_controller/controller) + if(controller.blackboard[BB_FIND_TARGETS_FIELD(type)]) return 60 SECONDS return ..() -/datum/ai_behavior/find_potential_targets/perform(seconds_per_tick, datum/ai_controller/controller, target_key, targeting_strategy_key, hiding_location_key) - var/mob/living/living_mob = controller.pawn - var/datum/targeting_strategy/targeting_strategy = GET_TARGETING_STRATEGY(controller.blackboard[targeting_strategy_key]) +/datum/bt_node/ai_behavior/acquire_target/update_combat_targets/can_search(datum/ai_controller/controller) + return !(controller.blackboard[BB_FIND_TARGETS_FIELD(type)]) - if(!targeting_strategy) - CRASH("No target datum was supplied in the blackboard for [controller.pawn]") - - var/atom/current_target = controller.blackboard[target_key] +/datum/bt_node/ai_behavior/acquire_target/update_combat_targets/should_keep_target(datum/ai_controller/controller, datum/targeting_strategy/strategy, atom/current_target) + if(!current_target) + return FALSE + if(!strategy.is_valid_target(controller.pawn, current_target, vision_range)) + return FALSE var/datum/target_priority_strategy/priority_strategy = GET_TARGET_PRIORITY_STRATEGY(controller.blackboard[priority_strategy_key]) - if((!priority_strategy || controller.blackboard[BB_BASIC_MOB_TARGET_REFRESH_COOLDOWN] > world.time) && current_target && targeting_strategy.can_attack(living_mob, current_target, vision_range)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + if(!priority_strategy) + return TRUE + return controller.blackboard[BB_BASIC_MOB_TARGET_REFRESH_COOLDOWN] > world.time - var/aggro_range = vision_range - if(isnull(current_target) && !isnull(controller.blackboard[aggro_grab_range_key])) - aggro_range = controller.blackboard[aggro_grab_range_key] - else if(!isnull(controller.blackboard[aggro_range_key])) - aggro_range = controller.blackboard[aggro_range_key] +/datum/bt_node/ai_behavior/acquire_target/update_combat_targets/on_no_candidates(datum/ai_controller/controller, atom/current_target, datum/targeting_strategy/strategy, range) + if(current_target && strategy.can_keep_target(controller.pawn, current_target, target_loss_distance)) + return list(current_target) + if(!current_target) + failed_to_find_anyone(controller, target_key, targeting_strategy, hiding_location_key) + return list() - controller.clear_blackboard_key(target_key) +/datum/bt_node/ai_behavior/acquire_target/update_combat_targets/on_no_valid_candidates(datum/ai_controller/controller, atom/current_target) + if(!current_target) + failed_to_find_anyone(controller, target_key, targeting_strategy, hiding_location_key) - // If we're using a field rn, just don't do anything yeah? - if(controller.blackboard[BB_FIND_TARGETS_FIELD(type)]) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - var/list/potential_targets = hearers(aggro_range, get_turf(controller.pawn)) - living_mob //Remove self, so we don't suicide - - var/turf/mob_turf = get_turf(living_mob) - if(mob_turf?.z) - for (var/atom/hostile_machine as anything in GLOB.hostile_machines_by_z[mob_turf.z]) - if (can_see(living_mob, hostile_machine, aggro_range)) - potential_targets += hostile_machine - - if(!potential_targets.len) - if(!current_target) - failed_to_find_anyone(controller, target_key, targeting_strategy_key, hiding_location_key) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - var/list/filtered_targets = list() - var/current_priority = 0 - if(priority_strategy) - current_priority = priority_strategy.get_target_priority(controller, current_target) - - for(var/atom/pot_target in potential_targets) - if(!targeting_strategy.can_attack(living_mob, pot_target)) +/datum/bt_node/ai_behavior/acquire_target/update_combat_targets/filter_candidates(datum/ai_controller/controller, list/candidates, datum/targeting_strategy/strategy, atom/current_target) + var/mob/living/pawn = controller.pawn + var/datum/target_priority_strategy/priority_strategy = GET_TARGET_PRIORITY_STRATEGY(controller.blackboard[priority_strategy_key]) + var/current_priority = priority_strategy ? priority_strategy.get_target_priority(controller, current_target) : 0 + var/list/filtered = list() + for(var/atom/candidate as anything in candidates) + if(!strategy.is_valid_target(pawn, candidate, vision_range, controller)) continue - if (priority_strategy && priority_strategy.get_target_priority(controller, pot_target) < current_priority) + if(priority_strategy && priority_strategy.get_target_priority(controller, candidate) < current_priority) continue - filtered_targets += pot_target + filtered += candidate + return filtered - if(!filtered_targets.len) - if(!current_target) - failed_to_find_anyone(controller, target_key, targeting_strategy_key, hiding_location_key) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - var/atom/target = pick_final_target(controller, filtered_targets) - - EVLOG_MAPTEXT(controller, EVLOG_CATEGORY_AI_TARGETING, "[controller.pawn] has selected [target] as a target for blackboard key [target_key]! Behavior: [src]", get_turf(target), "Target: [target]") - EVLOG_LINES(controller, EVLOG_CATEGORY_AI_TARGETING, "Line to target", get_turf(controller.pawn), get_turf(target)) - - controller.set_blackboard_key(target_key, target) +/datum/bt_node/ai_behavior/acquire_target/update_combat_targets/on_target_found(datum/ai_controller/controller, atom/target, datum/targeting_strategy/strategy) controller.set_blackboard_key(BB_BASIC_MOB_TARGET_REFRESH_COOLDOWN, world.time + priority_refresh_cooldown) - - var/atom/potential_hiding_location = targeting_strategy.find_hidden_mobs(living_mob, target) - - if(potential_hiding_location) //If they're hiding inside of something, we need to know so we can go for that instead initially. + var/atom/potential_hiding_location = strategy.find_hidden_mobs(controller.pawn, target) + if(potential_hiding_location) controller.set_blackboard_key(hiding_location_key, potential_hiding_location) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -/datum/ai_behavior/find_potential_targets/proc/failed_to_find_anyone(datum/ai_controller/controller, target_key, targeting_strategy_key, hiding_location_key) - var/aggro_range = vision_range - if(!isnull(controller.blackboard[aggro_grab_range_key])) - aggro_range = controller.blackboard[aggro_grab_range_key] - else if(!isnull(controller.blackboard[aggro_range_key])) - aggro_range = controller.blackboard[aggro_range_key] - +/datum/bt_node/ai_behavior/acquire_target/update_combat_targets/proc/failed_to_find_anyone(datum/ai_controller/controller, target_key, targeting_strategy, hiding_location_key) + // TEMP DISABLED nuke this if performance improves + /* + var/aggro_range = controller.blackboard[aggro_range_key] || vision_range // takes the larger between our range() input and our implicit hearers() input (world.view) aggro_range = max(aggro_range, ROUND_UP(max(getviewsize(world.view)) / 2)) // Alright, here's the interesting bit @@ -112,13 +85,16 @@ GLOBAL_LIST_INIT(target_interested_atoms, typecacheof(list(/mob, /obj/machinery/ src, controller, target_key, - targeting_strategy_key, + targeting_strategy, hiding_location_key, ) // We're gonna store this field in our blackboard, so we can clear it away if we end up finishing successsfully controller.set_blackboard_key(BB_FIND_TARGETS_FIELD(type), detection_field) + */ + controller.clear_blackboard_key(target_key) -/datum/ai_behavior/find_potential_targets/proc/new_turf_found(turf/found, datum/ai_controller/controller, datum/targeting_strategy/strategy) + +/datum/bt_node/ai_behavior/acquire_target/update_combat_targets/proc/new_turf_found(turf/found, datum/ai_controller/controller, datum/targeting_strategy/strategy) var/valid_found = FALSE var/mob/pawn = controller.pawn for(var/maybe_target in found) @@ -126,7 +102,7 @@ GLOBAL_LIST_INIT(target_interested_atoms, typecacheof(list(/mob, /obj/machinery/ continue if(!is_type_in_typecache(maybe_target, GLOB.target_interested_atoms)) continue - if(!strategy.can_attack(pawn, maybe_target)) + if(!strategy.is_valid_target(pawn, maybe_target)) continue valid_found = TRUE break @@ -136,18 +112,18 @@ GLOBAL_LIST_INIT(target_interested_atoms, typecacheof(list(/mob, /obj/machinery/ var/datum/proximity_monitor/field = controller.blackboard[BB_FIND_TARGETS_FIELD(type)] qdel(field) // autoclears so it's fine // Fire instantly, you should find something I hope - controller.modify_cooldown(src, world.time) + modify_cooldown(world.time) -/datum/ai_behavior/find_potential_targets/proc/atom_allowed(atom/movable/checking, datum/targeting_strategy/strategy, mob/pawn) +/datum/bt_node/ai_behavior/acquire_target/update_combat_targets/proc/atom_allowed(atom/movable/checking, datum/targeting_strategy/strategy, mob/pawn) if(checking == pawn) return FALSE if(!ismob(checking) && !is_type_in_typecache(checking, GLOB.target_interested_atoms)) return FALSE - if(!strategy.can_attack(pawn, checking)) + if(!strategy.is_valid_target(pawn, checking)) return FALSE return TRUE -/datum/ai_behavior/find_potential_targets/proc/new_atoms_found(list/atom/movable/found, datum/ai_controller/controller, target_key, datum/targeting_strategy/strategy, hiding_location_key) +/datum/bt_node/ai_behavior/acquire_target/update_combat_targets/proc/new_atoms_found(list/atom/movable/found, datum/ai_controller/controller, target_key, datum/targeting_strategy/strategy, hiding_location_key) var/mob/pawn = controller.pawn var/list/accepted_targets = list() for(var/maybe_target in found) @@ -156,7 +132,7 @@ GLOBAL_LIST_INIT(target_interested_atoms, typecacheof(list(/mob, /obj/machinery/ // Need to better handle viewers here if(!ismob(maybe_target) && !is_type_in_typecache(maybe_target, GLOB.target_interested_atoms)) continue - if(!strategy.can_attack(pawn, maybe_target)) + if(!strategy.is_valid_target(pawn, maybe_target)) continue accepted_targets += maybe_target @@ -165,36 +141,46 @@ GLOBAL_LIST_INIT(target_interested_atoms, typecacheof(list(/mob, /obj/machinery/ EVLOG_MAPTEXT(controller, EVLOG_CATEGORY_AI_TARGETING, "[controller.pawn] has selected [target] as a target for blackboard key [target_key]! Behavior: [src]", get_turf(target), "Target: [target]") EVLOG_LINES(controller, EVLOG_CATEGORY_AI_TARGETING, "Line to target", get_turf(controller.pawn), get_turf(target)) controller.set_blackboard_key(target_key, target) - - var/atom/potential_hiding_location = strategy.find_hidden_mobs(pawn, target) - - if(potential_hiding_location) //If they're hiding inside of something, we need to know so we can go for that instead initially. - controller.set_blackboard_key(hiding_location_key, potential_hiding_location) + on_target_found(controller, target, strategy) finish_action(controller, succeeded = TRUE) -/datum/ai_behavior/find_potential_targets/finish_action(datum/ai_controller/controller, succeeded, target_key, targeting_strategy_key, hiding_location_key) +/datum/bt_node/ai_behavior/acquire_target/update_combat_targets/finish_action(datum/ai_controller/controller, succeeded) . = ..() if (succeeded) var/datum/proximity_monitor/field = controller.blackboard[BB_FIND_TARGETS_FIELD(type)] qdel(field) // autoclears so it's fine - controller.CancelActions() // On retarget cancel any further queued actions so that they will setup again with new target - controller.modify_cooldown(src, get_cooldown(controller)) + modify_cooldown(get_cooldown(controller)) -/// Returns the desired final target from the filtered list of targets -/datum/ai_behavior/find_potential_targets/proc/pick_final_target(datum/ai_controller/controller, list/filtered_targets) +/// Picks the final target, preferring higher-priority candidates when a priority strategy is set. +/datum/bt_node/ai_behavior/acquire_target/update_combat_targets/pick_final_target(datum/ai_controller/controller, list/filtered_targets) var/datum/target_priority_strategy/priority_strategy = GET_TARGET_PRIORITY_STRATEGY(controller.blackboard[priority_strategy_key]) - if (!priority_strategy) - return pick(filtered_targets) + if(!priority_strategy) + return filtered_targets[1] return priority_strategy.select_target(controller, filtered_targets) -/// Targets with the trait specified by the BB_TARGET_PRIORITY_TRAIT blackboard key will be prioritized over the rest. -/datum/ai_behavior/find_potential_targets/prioritize_trait +/// Picks targets based on which one has the lowest health. +/datum/bt_node/ai_behavior/acquire_target/update_combat_targets/most_wounded -/datum/ai_behavior/find_potential_targets/prioritize_trait/pick_final_target(datum/ai_controller/controller, list/filtered_targets) - var/priority_targets = list() +/datum/bt_node/ai_behavior/acquire_target/update_combat_targets/most_wounded/pick_final_target(datum/ai_controller/controller, list/filtered_targets) + var/list/living_targets = list() + for(var/mob/living/living_target in filtered_targets) + living_targets += living_target + if(living_targets.len) + sortTim(living_targets, GLOBAL_PROC_REF(cmp_mob_health)) + return living_targets[living_targets.len] + return ..() + +/// Prioritizes targets carrying the trait named by our trait_key blackboard key over the rest. +/datum/bt_node/ai_behavior/acquire_target/update_combat_targets/prioritize_trait + /// Blackboard key holding the trait that marks a target as high-priority. + var/trait_key = BB_TARGET_PRIORITY_TRAIT + +/datum/bt_node/ai_behavior/acquire_target/update_combat_targets/prioritize_trait/pick_final_target(datum/ai_controller/controller, list/filtered_targets) + var/list/priority_targets = list() + var/priority_trait = controller.blackboard[trait_key] for(var/atom/target as anything in filtered_targets) - if(HAS_TRAIT(target, controller.blackboard[BB_TARGET_PRIORITY_TRAIT])) + if(HAS_TRAIT(target, priority_trait)) priority_targets += target if(length(priority_targets)) return ..(controller, priority_targets) diff --git a/code/datums/ai/basic_mobs/basic_ai_behaviors/tipped_reaction.dm b/code/datums/ai/basic_mobs/basic_ai_behaviors/tipped_reaction.dm index c734d961b5e..288276b358c 100644 --- a/code/datums/ai/basic_mobs/basic_ai_behaviors/tipped_reaction.dm +++ b/code/datums/ai/basic_mobs/basic_ai_behaviors/tipped_reaction.dm @@ -1,15 +1,13 @@ +///A tipped-over mob looks to a nearby person for help, then resigns itself to its fate. +/datum/bt_node/ai_behavior/tipped_reaction + /// Blackboard key holding the mob that tipped us over. + var/tipper_key = BB_BASIC_MOB_TIPPER + /// Blackboard key holding whether we are still reacting to being tipped. + var/reacting_key = BB_BASIC_MOB_TIP_REACTING -///type of tipped reaction that is akin to puppy dog eyes -/datum/ai_behavior/tipped_reaction - -/datum/ai_behavior/tipped_reaction/perform(seconds_per_tick, datum/ai_controller/controller, tipper_key, reacting_key) +/datum/bt_node/ai_behavior/tipped_reaction/perform(seconds_per_tick, datum/ai_controller/controller) var/mob/living/carbon/tipper = controller.blackboard[tipper_key] - // visible part of the visible message - var/seen_message = "" - // self part of the visible message - var/self_message = "" - // the mob we're looking to for aid var/mob/living/carbon/savior // look for someone in a radius around us for help. If our original tipper is in range, prioritize them for(var/mob/living/carbon/potential_aid in oview(3, get_turf(controller.pawn))) @@ -18,6 +16,8 @@ break savior = potential_aid + var/seen_message + var/self_message if(prob(75) && savior) var/text = pick("imploringly", "pleadingly", "with a resigned expression") seen_message = "[controller.pawn] looks at [savior] [text]." @@ -28,7 +28,7 @@ controller.pawn.visible_message(span_notice("[seen_message]"), span_notice("[self_message]")) return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED -/datum/ai_behavior/tipped_reaction/finish_action(datum/ai_controller/controller, succeeded, tipper_key, reacting_key) +/datum/bt_node/ai_behavior/tipped_reaction/finish_action(datum/ai_controller/controller, succeeded) . = ..() //I'VE SAID MY PEACE... controller.set_blackboard_key(reacting_key, FALSE) diff --git a/code/datums/ai/basic_mobs/basic_ai_behaviors/travel_towards.dm b/code/datums/ai/basic_mobs/basic_ai_behaviors/travel_towards.dm deleted file mode 100644 index f78eb3db4ec..00000000000 --- a/code/datums/ai/basic_mobs/basic_ai_behaviors/travel_towards.dm +++ /dev/null @@ -1,53 +0,0 @@ -/** - * # Travel Towards - * Moves towards the atom in the passed blackboard key. - * Planning continues during this action so it can be interrupted by higher priority actions. - */ -/datum/ai_behavior/travel_towards - required_distance = 0 - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION - /// If true we will get rid of our target on completion - var/clear_target = FALSE - ///should we use a different movement type? - var/new_movement_type - -/datum/ai_behavior/travel_towards/setup(datum/ai_controller/controller, target_key) - . = ..() - var/atom/target = controller.blackboard[target_key] - if(QDELETED(target)) - return FALSE - set_movement_target(controller, target, new_movement_type) - -/datum/ai_behavior/travel_towards/perform(seconds_per_tick, datum/ai_controller/controller, target_key) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -/datum/ai_behavior/travel_towards/finish_action(datum/ai_controller/controller, succeeded, target_key) - . = ..() - if (clear_target) - controller.clear_blackboard_key(target_key) - if(new_movement_type) - controller.change_ai_movement_type(initial(controller.ai_movement)) - -/datum/ai_behavior/travel_towards/stop_on_arrival - clear_target = TRUE - -/datum/ai_behavior/travel_towards/adjacent - required_distance = 1 - -/** - * # Travel Towards Atom - * Travel towards an atom you pass directly from the controller rather than a blackboard key. - * You might need to do this to avoid repeating some checks in both a controller and an action. - */ -/datum/ai_behavior/travel_towards_atom - required_distance = 0 - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT - -/datum/ai_behavior/travel_towards_atom/setup(datum/ai_controller/controller, atom/target_atom) - . = ..() - if(isnull(target_atom)) - return FALSE - set_movement_target(controller, target_atom) - -/datum/ai_behavior/travel_towards_atom/perform(seconds_per_tick, datum/ai_controller/controller, atom/target_atom) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED diff --git a/code/datums/ai/basic_mobs/basic_ai_behaviors/unbuckle_mob.dm b/code/datums/ai/basic_mobs/basic_ai_behaviors/unbuckle_mob.dm deleted file mode 100644 index 34e651f8e52..00000000000 --- a/code/datums/ai/basic_mobs/basic_ai_behaviors/unbuckle_mob.dm +++ /dev/null @@ -1,11 +0,0 @@ -/datum/ai_behavior/unbuckle_mob - -/datum/ai_behavior/unbuckle_mob/perform(seconds_per_tick, datum/ai_controller/controller) - var/mob/living/living_pawn = controller.pawn - var/atom/movable/buckled_to = living_pawn.buckled - - if(isnull(buckled_to)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - buckled_to.unbuckle_mob(living_pawn) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED diff --git a/code/datums/ai/basic_mobs/basic_ai_behaviors/use_mob_ability.dm b/code/datums/ai/basic_mobs/basic_ai_behaviors/use_mob_ability.dm new file mode 100644 index 00000000000..847a54758df --- /dev/null +++ b/code/datums/ai/basic_mobs/basic_ai_behaviors/use_mob_ability.dm @@ -0,0 +1,39 @@ +/// Triggers a mob ability stored in a blackboard key. Returns INSTANT SUCCESS if triggered, INSTANT FAILURE if unavailable or trigger fails. +/datum/bt_node/ai_behavior/use_mob_ability + var/ability_key = BB_GENERIC_ACTION + +/datum/bt_node/ai_behavior/use_mob_ability/perform(seconds_per_tick, datum/ai_controller/controller) + var/async_flags = handle_async() + if(async_flags) + return async_flags + + var/datum/action/using_action = get_valid_ability(controller) + if(QDELETED(using_action)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + return start_async() + +/// Returns the action to trigger, or null if it isn't available. Override to prep the action before it fires. +/datum/bt_node/ai_behavior/use_mob_ability/proc/get_valid_ability(datum/ai_controller/controller) + var/datum/action/using_action = controller.blackboard[ability_key] + if(QDELETED(using_action) || !using_action.IsAvailable()) + return null + return using_action + +/datum/bt_node/ai_behavior/use_mob_ability/perform_async(datum/ai_controller/controller) + var/datum/action/using_action = controller.blackboard[ability_key] + var/result = using_action.Trigger() + if(!async_still_valid()) + return + finish_async(result ? AI_BEHAVIOR_SUCCEEDED : AI_BEHAVIOR_FAILED) + +/// Triggers a shapeshift ability, picking a random shape if none has been selected yet (AI can't use context wheels). +/datum/bt_node/ai_behavior/use_mob_ability/shapeshift + ability_key = BB_SHAPESHIFT_ACTION + +/datum/bt_node/ai_behavior/use_mob_ability/shapeshift/get_valid_ability(datum/ai_controller/controller) + var/datum/action/cooldown/spell/shapeshift/using_action = ..() + if(QDELETED(using_action)) + return null + if(isnull(using_action.shapeshift_type)) + using_action.shapeshift_type = pick(using_action.possible_shapes) + return using_action diff --git a/code/datums/ai/basic_mobs/basic_ai_behaviors/ventcrawling.dm b/code/datums/ai/basic_mobs/basic_ai_behaviors/ventcrawling.dm index 06fcfa14726..7e1f3a76372 100644 --- a/code/datums/ai/basic_mobs/basic_ai_behaviors/ventcrawling.dm +++ b/code/datums/ai/basic_mobs/basic_ai_behaviors/ventcrawling.dm @@ -1,143 +1,190 @@ -/// We hop into the vents through a vent outlet, and then crawl around a bit. Jolly good times. -/// This also assumes that we are on the turf that the vent outlet is on. If it isn't, shit. +///uhm...sus? +/datum/bt_node/subtree/consider_venting + behavior_tree_json = "code/datums/ai/basic_mobs/basic_ai_behaviors/consider_venting.bt.json" -/// Warning: this was really snowflake code lifted from an obscure feature that likely has not been touched for over five years years. -/// Something that isn't implemented is the ability to actually crawl through vents ourselves because I think that's just a waste of time for the same effect (instead of psuedo-teleportation, do REAL forceMoving) -/// If you are seriously considering using this component, it would be a great idea to extend this proc to be more versatile/less overpowered - the mobs that currently implement this benefit the most -/// since they are weak as shit with only five health. Up to you though, don't take what's written here as gospel. -/datum/ai_behavior/crawl_through_vents - action_cooldown = 10 SECONDS -/datum/ai_behavior/crawl_through_vents/get_cooldown(datum/ai_controller/cooldown_for) - return cooldown_for.blackboard[BB_VENTCRAWL_COOLDOWN] || initial(action_cooldown) +/// Enters a vent stored in entry_vent_key. Sets BB_EXIT_VENT_TARGET and BB_VENT_ENTRY_TIME on success. +/datum/bt_node/ai_behavior/enter_vent + var/entry_vent_key = BB_ENTRY_VENT_TARGET + /// TRUE while the async crawl-in is running. perform() holds at DELAY until it resolves. + var/is_starting_crawl = FALSE + /// Set by the async action when the crawl finished but we did not end up in the vent. + var/failed_ventcrawl = FALSE -/datum/ai_behavior/crawl_through_vents/setup(datum/ai_controller/controller, target_key) - . = ..() - var/obj/machinery/atmospherics/components/unary/vent_pump/target = controller.blackboard[target_key] || controller.blackboard[BB_ENTRY_VENT_TARGET] - return istype(target) && isliving(controller.pawn) // only mobs can vent crawl in the current framework - -/datum/ai_behavior/crawl_through_vents/perform(seconds_per_tick, datum/ai_controller/controller, target_key) - var/obj/machinery/atmospherics/components/unary/vent_pump/entry_vent = controller.blackboard[target_key] || controller.blackboard[BB_ENTRY_VENT_TARGET] +/datum/bt_node/ai_behavior/enter_vent/perform(seconds_per_tick, datum/ai_controller/controller) var/mob/living/cached_pawn = controller.pawn - if(HAS_TRAIT(cached_pawn, TRAIT_MOVE_VENTCRAWLING) || !controller.blackboard[BB_CURRENTLY_TARGETING_VENT] || !is_vent_valid(entry_vent)) - return AI_BEHAVIOR_DELAY - if(!cached_pawn.can_enter_vent(entry_vent, provide_feedback = FALSE)) // we're an AI we scoff at feedback - // "never enter a hole you can't get out of" - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + // We kicked off the crawl on a previous tick; report its result once it resolves. Flags reset in finish_action. + if(failed_ventcrawl) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + if(is_starting_crawl) + if(!HAS_TRAIT(cached_pawn, TRAIT_MOVE_VENTCRAWLING)) + return AI_BEHAVIOR_DELAY // still climbing in + controller.set_blackboard_key(BB_VENT_ENTRY_TIME, world.time) + if(prob(50)) + cached_pawn.visible_message( + span_warning("[cached_pawn] scrambles into the ventilation ducts!"), + span_hear("You hear something scampering through the ventilation ducts."), + ) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED - var/vent_we_exit_out_of = calculate_exit_vent(controller, target_key) - if(isnull(vent_we_exit_out_of)) // don't get into the vents if we can't get out of them, that's SILLY. - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + if(HAS_TRAIT(cached_pawn, TRAIT_MOVE_VENTCRAWLING)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED - controller.set_blackboard_key(BB_CURRENTLY_TARGETING_VENT, FALSE) // must be done here because we have a do_after sleep in handle_ventcrawl unfortunately and double dipping could lead to erroneous suicide pill calls. - cached_pawn.handle_ventcrawl(entry_vent) - if(!HAS_TRAIT(cached_pawn, TRAIT_MOVE_VENTCRAWLING)) //something failed and we ARE NOT IN THE VENT even though the earlier check said we were good to go! odd. - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + var/obj/machinery/atmospherics/components/unary/vent_pump/entry_vent = controller.blackboard[entry_vent_key] + if(!is_vent_valid(entry_vent) || !isliving(cached_pawn)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED - controller.set_blackboard_key(BB_EXIT_VENT_TARGET, vent_we_exit_out_of) + if(!cached_pawn.can_enter_vent(entry_vent, provide_feedback = FALSE)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED - if(prob(50)) - cached_pawn.visible_message( - span_warning("[src] scrambles into the ventilation ducts!"), - span_hear("You hear something scampering through the ventilation ducts."), - ) + var/obj/machinery/atmospherics/components/unary/vent_pump/exit_vent = calculate_exit_vent(controller) + if(isnull(exit_vent)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED - var/lower_vent_time_limit = controller.blackboard[BB_LOWER_VENT_TIME_LIMIT] // the least amount of time we spend in the vents - var/upper_vent_time_limit = controller.blackboard[BB_UPPER_VENT_TIME_LIMIT] // the most amount of time we spend in the vents - - addtimer(CALLBACK(src, PROC_REF(exit_the_vents), controller), rand(lower_vent_time_limit, upper_vent_time_limit)) - controller.set_blackboard_key(BB_GIVE_UP_ON_VENT_PATHING_TIMER_ID, addtimer(CALLBACK(src, PROC_REF(delayed_suicide_pill), controller, target_key), controller.blackboard[BB_TIME_TO_GIVE_UP_ON_VENT_PATHING], TIMER_STOPPABLE)) + controller.set_blackboard_key(BB_EXIT_VENT_TARGET, exit_vent) + is_starting_crawl = TRUE + INVOKE_ASYNC(src, PROC_REF(perform_ventcrawl_action), controller, entry_vent) return AI_BEHAVIOR_DELAY -/// Figure out an exit vent that we should head towards. If we don't have one, default to the entry vent. If they're all kaput, we die. -/datum/ai_behavior/crawl_through_vents/proc/calculate_exit_vent(datum/ai_controller/controller, target_key) - var/obj/machinery/atmospherics/components/unary/vent_pump/returnable_vent - var/obj/machinery/atmospherics/components/unary/vent_pump/vent_we_entered_through = controller.blackboard[target_key] || controller.blackboard[BB_ENTRY_VENT_TARGET] +/// Runs the sleeping ventcrawl off the tick. Flags failure if we didn't end up in the vent, so perform() never has to sleep. +/datum/bt_node/ai_behavior/enter_vent/proc/perform_ventcrawl_action(datum/ai_controller/controller, obj/machinery/atmospherics/components/unary/vent_pump/entry_vent) + var/mob/living/cached_pawn = controller.pawn + cached_pawn.handle_ventcrawl(entry_vent) + if(!HAS_TRAIT(cached_pawn, TRAIT_MOVE_VENTCRAWLING)) //something failed and we ARE NOT IN THE VENT even though the earlier check said we were good to go! odd. + failed_ventcrawl = TRUE - var/datum/pipeline/entry_vent_parent = vent_we_entered_through.parents[1] - var/list/potential_exits = list() +/datum/bt_node/ai_behavior/enter_vent/finish_action(datum/ai_controller/controller, succeeded) + . = ..() + is_starting_crawl = FALSE + failed_ventcrawl = FALSE + if(!succeeded) + controller.clear_blackboard_key(entry_vent_key) - for(var/obj/machinery/atmospherics/components/unary/vent_pump/vent in entry_vent_parent.other_atmos_machines) - if(is_vent_valid(vent)) - potential_exits.Add(vent) +/// Returns TRUE if the vent exists and isn't welded shut. +/datum/bt_node/ai_behavior/enter_vent/proc/is_vent_valid(obj/machinery/atmospherics/components/unary/vent_pump/vent) + return !QDELETED(vent) && !vent.welded - if(length(potential_exits)) - returnable_vent = pick(potential_exits) - return returnable_vent - - // if we're here, we're in "what the flarp" mode... okay maybe we can default to the vent we entered in. - returnable_vent = vent_we_entered_through - if(is_vent_valid(vent_we_entered_through)) - // AH WHAT THE FUCK. okay, maybe we're not inside the vents yet? let's return null and we can pick up on that based on the wider context of the proc that invokes it. +/// Picks a random valid vent on the same pipeline as the entry vent. Falls back to the entry vent itself; returns null if nothing is usable. +/datum/bt_node/ai_behavior/enter_vent/proc/calculate_exit_vent(datum/ai_controller/controller) + var/obj/machinery/atmospherics/components/unary/vent_pump/entry_vent = controller.blackboard[entry_vent_key] + if(QDELETED(entry_vent)) return null - return returnable_vent // we return null in case something yonked between then and now so it's all good man + var/datum/pipeline/parent_pipe = entry_vent.parents[1] + var/list/candidates = list() + for(var/obj/machinery/atmospherics/components/unary/vent_pump/vent in parent_pipe.other_atmos_machines) + if(is_vent_valid(vent)) + candidates += vent -/// We've had enough horsing around in the vents, it's time to get out. -/datum/ai_behavior/crawl_through_vents/proc/exit_the_vents(datum/ai_controller/controller, target_key) - var/obj/machinery/atmospherics/components/unary/vent_pump/emergency_vent // vent we will scramble to search for in case plan A is a bust (exit vent) - var/obj/machinery/atmospherics/components/unary/vent_pump/exit_vent = controller.blackboard[BB_EXIT_VENT_TARGET] - var/mob/living/living_pawn = controller.pawn + if(length(candidates)) + return pick(candidates) - if(!HAS_TRAIT(living_pawn, TRAIT_MOVE_VENTCRAWLING) && isturf(get_turf(living_pawn))) // we're out of the vents, so no need to do an exit - // assume that we got yeeted out somehow and return this so we can halt the suicide pill timer. - finish_action(controller, TRUE, target_key) - return + if(is_vent_valid(entry_vent)) + return null // in the pipeline already; let the caller handle it - living_pawn.forceMove(exit_vent) - if(!living_pawn.can_enter_vent(exit_vent, provide_feedback = FALSE)) - // oh shit, something happened while we were waiting on that timer. let's figure out a different way to get out of here. - emergency_vent = calculate_exit_vent(controller) - if(isnull(emergency_vent)) - // it's joever. we cooked too hard. - suicide_pill(controller) - return + return entry_vent - controller.set_blackboard_key(BB_EXIT_VENT_TARGET, emergency_vent) // assign and go again - addtimer(CALLBACK(src, PROC_REF(exit_the_vents), controller), (rand(controller.blackboard[BB_LOWER_VENT_TIME_LIMIT], controller.blackboard[BB_UPPER_VENT_TIME_LIMIT]) / 2)) // we're in danger mode, so scurry out at half the time it would normally take. - return - living_pawn.handle_ventcrawl(exit_vent) - if(HAS_TRAIT(living_pawn, TRAIT_MOVE_VENTCRAWLING)) // how'd we fail? what the fuck - stack_trace("We failed to exit the vents, even though we should have been fine? This is very weird.") - suicide_pill(controller) - return +/// Waits inside a vent for a randomised duration, then exits. Handles the give-up timeout. +/datum/bt_node/ai_behavior/exit_vent + time_between_perform = 1 SECONDS + var/target_exit_time = 0 + /// TRUE while the async crawl-out is running. perform() holds at DELAY until it resolves. + var/is_exiting_crawl = FALSE + /// Set by the async action when the crawl finished but we are somehow still in the vent. + var/failed_ventcrawl = FALSE - finish_action(controller, TRUE, target_key) - return - -/// Incredibly stripped down version of the overarching `can_enter_vent` proc on `/mob, just meant for rapid rechecking of a vent. Will be TRUE if not blocked, FALSE otherwise. -/datum/ai_behavior/crawl_through_vents/proc/is_vent_valid(obj/machinery/atmospherics/components/unary/vent_pump/checkable) - return !QDELETED(checkable) && !checkable.welded - -/// Wraps a delayed defeat, so we gotta handle the return value properly ya feel? -/datum/ai_behavior/crawl_through_vents/proc/delayed_suicide_pill(datum/ai_controller/controller, target_key) - if(suicide_pill(controller) & AI_BEHAVIOR_FAILED) - finish_action(controller, FALSE, target_key) - -/// Aw fuck, we may have been bested somehow. Regardless of what we do, we can't exit through a vent! Let's end our misery and prevent useless endless calculations. -/datum/ai_behavior/crawl_through_vents/proc/suicide_pill(datum/ai_controller/controller) - var/mob/living/living_pawn = controller.pawn - - if(istype(living_pawn)) - if(isnull(living_pawn.client)) // only call death if we don't have a client because maybe their natural intelligence can pick up where our AI calculations have failed - living_pawn.death(TRUE) // call gibbed as true because we are never coming back it is so fucking joever - - return AI_BEHAVIOR_FAILED - - if(QDELETED(living_pawn)) // we got deleted by some other means, just presume the action is a wash and get outta here - return NONE - - qdel(living_pawn) // failover, we really should've been caught in the istype() but lets just bow out of existing at this point - return NONE - -/datum/ai_behavior/crawl_through_vents/finish_action(datum/ai_controller/controller, succeeded, target_key) +/datum/bt_node/ai_behavior/exit_vent/setup(datum/ai_controller/controller) . = ..() + var/lower = controller.blackboard[BB_LOWER_VENT_TIME_LIMIT] + var/upper = controller.blackboard[BB_UPPER_VENT_TIME_LIMIT] + var/entry_time = controller.blackboard[BB_VENT_ENTRY_TIME] || world.time + target_exit_time = entry_time + rand(lower, upper) + return TRUE - deltimer(controller.blackboard[BB_GIVE_UP_ON_VENT_PATHING_TIMER_ID]) - controller.clear_blackboard_key(target_key) - controller.clear_blackboard_key(BB_ENTRY_VENT_TARGET) +/datum/bt_node/ai_behavior/exit_vent/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/cached_pawn = controller.pawn + + // We kicked off the crawl-out on a previous tick; report its result once it resolves. Flags reset in finish_action. + if(failed_ventcrawl) + return suicide_pill(cached_pawn) + if(is_exiting_crawl) + if(HAS_TRAIT(cached_pawn, TRAIT_MOVE_VENTCRAWLING)) + return AI_BEHAVIOR_DELAY // still climbing out + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED + + if(world.time < target_exit_time) + var/give_up = controller.blackboard[BB_TIME_TO_GIVE_UP_ON_VENT_PATHING] + var/entry_time = controller.blackboard[BB_VENT_ENTRY_TIME] + if(give_up && entry_time && world.time > entry_time + give_up) + return suicide_pill(cached_pawn) + return AI_BEHAVIOR_DELAY + + var/obj/machinery/atmospherics/components/unary/vent_pump/exit_vent = controller.blackboard[BB_EXIT_VENT_TARGET] + if(!is_vent_valid(exit_vent)) + exit_vent = calculate_exit_vent(controller) + if(isnull(exit_vent)) + return suicide_pill(cached_pawn) + controller.set_blackboard_key(BB_EXIT_VENT_TARGET, exit_vent) + + cached_pawn.forceMove(exit_vent) + if(!cached_pawn.can_enter_vent(exit_vent, provide_feedback = FALSE)) + // vent became unusable while we waited; try an emergency exit next tick + var/emergency = calculate_exit_vent(controller) + if(isnull(emergency)) + return suicide_pill(cached_pawn) + controller.set_blackboard_key(BB_EXIT_VENT_TARGET, emergency) + target_exit_time = world.time // retry immediately next tick + return AI_BEHAVIOR_DELAY + + is_exiting_crawl = TRUE + INVOKE_ASYNC(src, PROC_REF(perform_ventcrawl_action), controller, exit_vent) + return AI_BEHAVIOR_DELAY + +/// Runs the sleeping ventcrawl off the tick. Flags failure if we're somehow still in the vent, so perform() never has to sleep. +/datum/bt_node/ai_behavior/exit_vent/proc/perform_ventcrawl_action(datum/ai_controller/controller, obj/machinery/atmospherics/components/unary/vent_pump/exit_vent) + var/mob/living/cached_pawn = controller.pawn + cached_pawn.handle_ventcrawl(exit_vent) + if(HAS_TRAIT(cached_pawn, TRAIT_MOVE_VENTCRAWLING)) + stack_trace("[cached_pawn] [type]: exited vent but still has TRAIT_MOVE_VENTCRAWLING") + failed_ventcrawl = TRUE + +/datum/bt_node/ai_behavior/exit_vent/finish_action(datum/ai_controller/controller, succeeded) + . = ..() + is_exiting_crawl = FALSE + failed_ventcrawl = FALSE + controller.clear_blackboard_key(BB_VENT_ENTRY_TIME) controller.clear_blackboard_key(BB_EXIT_VENT_TARGET) - controller.set_blackboard_key(BB_CURRENTLY_TARGETING_VENT, FALSE) // just in case + controller.clear_blackboard_key(BB_ENTRY_VENT_TARGET) + +/// Kills the pawn if it has no client, then returns INSTANT FAILED. +/datum/bt_node/ai_behavior/exit_vent/proc/suicide_pill(mob/living/pawn) + if(istype(pawn) && isnull(pawn.client)) + pawn.death(TRUE) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + +/// Returns TRUE if the vent exists and isn't welded shut. +/datum/bt_node/ai_behavior/exit_vent/proc/is_vent_valid(obj/machinery/atmospherics/components/unary/vent_pump/vent) + return !QDELETED(vent) && !vent.welded + +/// Picks a random valid vent on the same pipeline as BB_ENTRY_VENT_TARGET. Returns null if nothing is usable. +/datum/bt_node/ai_behavior/exit_vent/proc/calculate_exit_vent(datum/ai_controller/controller) + var/obj/machinery/atmospherics/components/unary/vent_pump/entry_vent = controller.blackboard[BB_ENTRY_VENT_TARGET] + if(QDELETED(entry_vent)) + return null + + var/datum/pipeline/parent_pipe = entry_vent.parents[1] + var/list/candidates = list() + for(var/obj/machinery/atmospherics/components/unary/vent_pump/vent in parent_pipe.other_atmos_machines) + if(is_vent_valid(vent)) + candidates += vent + + if(length(candidates)) + return pick(candidates) + + if(is_vent_valid(entry_vent)) + return null + + return entry_vent diff --git a/code/datums/ai/basic_mobs/basic_ai_behaviors/wounded_targeting.dm b/code/datums/ai/basic_mobs/basic_ai_behaviors/wounded_targeting.dm deleted file mode 100644 index 46037fdc076..00000000000 --- a/code/datums/ai/basic_mobs/basic_ai_behaviors/wounded_targeting.dm +++ /dev/null @@ -1,11 +0,0 @@ -/// Picks targets based on which one has the lowest health -/datum/ai_behavior/find_potential_targets/most_wounded - -/datum/ai_behavior/find_potential_targets/most_wounded/pick_final_target(datum/ai_controller/controller, list/filtered_targets) - var/list/living_targets = list() - for(var/mob/living/living_target in filtered_targets) - living_targets += filtered_targets - if(living_targets.len) - sortTim(living_targets, GLOBAL_PROC_REF(cmp_mob_health)) - return pop(living_targets) - return ..() diff --git a/code/datums/ai/basic_mobs/basic_ai_behaviors/write_on_paper.dm b/code/datums/ai/basic_mobs/basic_ai_behaviors/write_on_paper.dm index 51826676e9e..e5dab148bd5 100644 --- a/code/datums/ai/basic_mobs/basic_ai_behaviors/write_on_paper.dm +++ b/code/datums/ai/basic_mobs/basic_ai_behaviors/write_on_paper.dm @@ -1,15 +1,23 @@ -/datum/ai_behavior/write_on_paper +/// Scrawls a random line from the writing list onto the carried paper, then drops it. Clears the carry key on finish. +/datum/bt_node/ai_behavior/write_on_paper + /// Blackboard key holding the paper to write on (also the virtual carry slot). + var/paper_key + /// Blackboard key holding the list of phrases to choose from. + var/writing_list_key -/datum/ai_behavior/write_on_paper/perform(seconds_per_tick, datum/ai_controller/controller, found_paper, list_of_writings) +/datum/bt_node/ai_behavior/write_on_paper/perform(seconds_per_tick, datum/ai_controller/controller) var/mob/living/wizard = controller.pawn - var/list/writing_list = controller.blackboard[list_of_writings] - var/obj/item/paper/target = controller.blackboard[found_paper] + var/obj/item/paper/target = controller.blackboard[paper_key] + if(QDELETED(target)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + var/list/writing_list = controller.blackboard[writing_list_key] if(length(writing_list)) target.add_raw_text(pick(writing_list)) target.update_appearance() - wizard.dropItemToGround(target) + if(target.loc == wizard) + target.forceMove(get_turf(wizard)) return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED -/datum/ai_behavior/write_on_paper/finish_action(datum/ai_controller/controller, succeeded, target_key) +/datum/bt_node/ai_behavior/write_on_paper/finish_action(datum/ai_controller/controller, succeeded) . = ..() - controller.clear_blackboard_key(target_key) + controller.clear_blackboard_key(paper_key) diff --git a/code/datums/ai/basic_mobs/basic_subtrees/attack_adjacent_target.dm b/code/datums/ai/basic_mobs/basic_subtrees/attack_adjacent_target.dm deleted file mode 100644 index 98cf1654f71..00000000000 --- a/code/datums/ai/basic_mobs/basic_subtrees/attack_adjacent_target.dm +++ /dev/null @@ -1,35 +0,0 @@ -/// Attack something which is already adjacent to us, without ending planning -/datum/ai_planning_subtree/basic_melee_attack_subtree/opportunistic - melee_attack_behavior = /datum/ai_behavior/basic_melee_attack/opportunistic - end_planning = FALSE - -/datum/ai_planning_subtree/basic_melee_attack_subtree/opportunistic/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - . = ..() - var/atom/target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] - if(QDELETED(target) || !controller.pawn.Adjacent(target)) - return - if (isliving(controller.pawn)) - var/mob/living/pawn = controller.pawn - if (LAZYLEN(pawn.do_afters)) - return - controller.queue_behavior(melee_attack_behavior, BB_BASIC_MOB_CURRENT_TARGET, BB_TARGETING_STRATEGY, BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION) - -/// Attack something which is already adjacent to us without moving -/datum/ai_behavior/basic_melee_attack/opportunistic - action_cooldown = 0.2 SECONDS // We gotta check unfortunately often because we're in a race condition with nextmove - behavior_flags = AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION - -/datum/ai_behavior/basic_melee_attack/opportunistic/setup(datum/ai_controller/controller, target_key, targeting_strategy_key, hiding_location_key) - if (!controller.blackboard_key_exists(targeting_strategy_key)) - CRASH("No target datum was supplied in the blackboard for [controller.pawn]") - return controller.blackboard_key_exists(target_key) - -/datum/ai_behavior/basic_melee_attack/opportunistic/perform(seconds_per_tick, datum/ai_controller/controller, target_key, targeting_strategy_key, hiding_location_key) - var/atom/movable/atom_pawn = controller.pawn - var/atom/atom_target = controller.blackboard[target_key] - if (QDELETED(atom_target)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - if(!atom_target.IsReachableBy(atom_pawn)) - return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED - . = ..() - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED diff --git a/code/datums/ai/basic_mobs/basic_subtrees/attack_obstacle_in_path.dm b/code/datums/ai/basic_mobs/basic_subtrees/attack_obstacle_in_path.dm deleted file mode 100644 index bc8efdeb4ff..00000000000 --- a/code/datums/ai/basic_mobs/basic_subtrees/attack_obstacle_in_path.dm +++ /dev/null @@ -1,85 +0,0 @@ -/// If there's something between us and our target then we need to queue a behaviour to make it not be there -/datum/ai_planning_subtree/attack_obstacle_in_path - /// Blackboard key containing current target - var/target_key = BB_BASIC_MOB_CURRENT_TARGET - /// The action to execute, extend to add a different cooldown or something - var/attack_behaviour = /datum/ai_behavior/attack_obstructions - -/datum/ai_planning_subtree/attack_obstacle_in_path/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - . = ..() - var/atom/target = controller.blackboard[target_key] - if(QDELETED(target)) - return - - var/turf/next_step = get_step_towards(controller.pawn, target) - if (!next_step.is_blocked_turf(exclude_mobs = TRUE, source_atom = controller.pawn)) - return - - controller.queue_behavior(attack_behaviour, target_key) - // Don't cancel future planning, maybe we can move now - -/// Something is in our way, get it outta here -/datum/ai_behavior/attack_obstructions - action_cooldown = 2 SECONDS - /// If we should attack walls, be prepared for complaints about breaches - var/can_attack_turfs = FALSE - /// For if you want your mob to be able to attack dense objects - var/can_attack_dense_objects = FALSE - -/datum/ai_behavior/attack_obstructions/perform(seconds_per_tick, datum/ai_controller/controller, target_key) - var/mob/living/basic/basic_mob = controller.pawn - var/atom/target = controller.blackboard[target_key] - - if (QDELETED(target)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - var/turf/next_step = get_step_towards(basic_mob, target) - var/dir_to_next_step = get_dir(basic_mob, next_step) - // If moving diagonally we need to punch both ways, or more accurately the one we are blocked in - var/list/dirs_to_move = list() - if (ISDIAGONALDIR(dir_to_next_step)) - for(var/direction in GLOB.cardinals) - if(direction & dir_to_next_step) - dirs_to_move += direction - else - dirs_to_move += dir_to_next_step - - for (var/direction in dirs_to_move) - if (attack_in_direction(controller, basic_mob, direction)) - return AI_BEHAVIOR_DELAY - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -/datum/ai_behavior/attack_obstructions/proc/attack_in_direction(datum/ai_controller/controller, mob/living/basic/basic_mob, direction) - var/turf/next_step = get_step(basic_mob, direction) - if (!next_step.is_blocked_turf(exclude_mobs = TRUE, source_atom = controller.pawn)) - return FALSE - - for (var/obj/object as anything in next_step.contents) - if (!can_smash_object(basic_mob, object)) - continue - basic_mob.melee_attack(object) - return TRUE - - if (can_attack_turfs) - basic_mob.melee_attack(next_step) - return TRUE - return FALSE - -/datum/ai_behavior/attack_obstructions/proc/can_smash_object(mob/living/basic/basic_mob, obj/object) - if (!object.density && !can_attack_dense_objects) - return FALSE - if (object.IsObscured()) - return FALSE - if (basic_mob.see_invisible < object.invisibility) - return FALSE - var/list/whitelist = basic_mob.ai_controller.blackboard[BB_OBSTACLE_TARGETING_WHITELIST] - if(whitelist && !is_type_in_typecache(object, whitelist)) - return FALSE - - return TRUE // It's in our way, let's get it out of our way - -/datum/ai_planning_subtree/attack_obstacle_in_path/low_priority_target - target_key = BB_LOW_PRIORITY_HUNTING_TARGET - -/datum/ai_planning_subtree/attack_obstacle_in_path/pet_target - target_key = BB_CURRENT_PET_TARGET diff --git a/code/datums/ai/basic_mobs/basic_subtrees/call_reinforcements.dm b/code/datums/ai/basic_mobs/basic_subtrees/call_reinforcements.dm deleted file mode 100644 index 46886680d52..00000000000 --- a/code/datums/ai/basic_mobs/basic_subtrees/call_reinforcements.dm +++ /dev/null @@ -1,68 +0,0 @@ -/// Calls all nearby mobs that share a faction to give backup in combat -/datum/ai_planning_subtree/call_reinforcements - /// Blackboard key containing something to say when calling reinforcements (takes precedence over emotes) - var/say_key = BB_REINFORCEMENTS_SAY - /// Blackboard key containing an emote to perform when calling reinforcements - var/emote_key = BB_REINFORCEMENTS_EMOTE - /// Reinforcement-calling behavior to use - var/call_type = /datum/ai_behavior/call_reinforcements - -/datum/ai_planning_subtree/call_reinforcements/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - . = ..() - if (!decide_to_call(controller) || controller.blackboard[BB_BASIC_MOB_REINFORCEMENTS_COOLDOWN] > world.time) - return - - var/call_say = controller.blackboard[BB_REINFORCEMENTS_SAY] - var/call_emote = controller.blackboard[BB_REINFORCEMENTS_EMOTE] - - if(!isnull(call_say)) - controller.queue_behavior(/datum/ai_behavior/perform_speech, call_say) - else if(!isnull(call_emote)) - controller.queue_behavior(/datum/ai_behavior/perform_emote, call_emote) - - controller.queue_behavior(call_type) - -/// Decides when to call reinforcements, can be overridden for alternate behavior -/datum/ai_planning_subtree/call_reinforcements/proc/decide_to_call(datum/ai_controller/controller) - return controller.blackboard_key_exists(BB_BASIC_MOB_CURRENT_TARGET) && istype(controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET], /mob) - -/datum/ai_planning_subtree/call_reinforcements/mining - call_type = /datum/ai_behavior/call_reinforcements/mining - -/// Call out to all mobs in the specified range for help -/datum/ai_behavior/call_reinforcements - /// How frequently can we call for reinforcements? - var/cooldown = 30 SECONDS - /// Range to call reinforcements from - var/reinforcements_range = 15 - -/datum/ai_behavior/call_reinforcements/perform(seconds_per_tick, datum/ai_controller/controller) - var/mob/pawn_mob = controller.pawn - for(var/mob/other_mob in oview(reinforcements_range, pawn_mob)) - if(!pawn_mob.faction_check_atom(other_mob) || isnull(other_mob.ai_controller)) - continue - // Add our current target to their retaliate list so that they'll attack our aggressor - other_mob.ai_controller.set_blackboard_key_assoc_lazylist(BB_BASIC_MOB_RETALIATE_LIST, controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET], world.time) - other_mob.ai_controller.set_blackboard_key(BB_BASIC_MOB_REINFORCEMENT_TARGET, pawn_mob) - - controller.set_blackboard_key(BB_BASIC_MOB_REINFORCEMENTS_COOLDOWN, world.time + cooldown) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -/// Does not force retaliation, but increases targeting priority instead -/datum/ai_behavior/call_reinforcements/mining - cooldown = 1 SECONDS - reinforcements_range = 7 - -/datum/ai_behavior/call_reinforcements/mining/perform(seconds_per_tick, datum/ai_controller/controller) - var/mob/pawn_mob = controller.pawn - var/atom/target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] - for(var/mob/other_mob in oview(reinforcements_range, pawn_mob)) - if(!pawn_mob.faction_check_atom(other_mob) || isnull(other_mob.ai_controller)) - continue - var/list/existing_requests = other_mob.ai_controller.blackboard[BB_MINING_MOB_REINFORCEMENTS_REQUESTS] - if (!existing_requests || !existing_requests[target]) - other_mob.ai_controller.set_blackboard_key_assoc_lazylist(BB_MINING_MOB_REINFORCEMENTS_REQUESTS, target, list()) - other_mob.ai_controller.add_blackboard_key_assoc(BB_MINING_MOB_REINFORCEMENTS_REQUESTS, target, world.time) - - controller.set_blackboard_key(BB_BASIC_MOB_REINFORCEMENTS_COOLDOWN, world.time + cooldown) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED diff --git a/code/datums/ai/basic_mobs/basic_subtrees/capricious_retaliate.dm b/code/datums/ai/basic_mobs/basic_subtrees/capricious_retaliate.dm index 211e01be1f2..54578a21d38 100644 --- a/code/datums/ai/basic_mobs/basic_subtrees/capricious_retaliate.dm +++ b/code/datums/ai/basic_mobs/basic_subtrees/capricious_retaliate.dm @@ -1,65 +1,59 @@ -/// Add or remove people to our retaliation shitlist just on an arbitrary whim -/datum/ai_planning_subtree/capricious_retaliate - /// Blackboard key which tells us how to select valid targets - var/targeting_strategy_key = BB_TARGETING_STRATEGY - /// Whether we should skip checking faction for our decision - var/ignore_faction = TRUE +///Random chance to add things to our retaliate list +/datum/bt_node/ai_behavior/capricious_retaliate + var/targeting_strategy = BB_TARGETING_STRATEGY + var/ignore_faction + time_between_perform = 1 SECONDS -/datum/ai_planning_subtree/capricious_retaliate/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - . = ..() - controller.queue_behavior(/datum/ai_behavior/capricious_retaliate, targeting_strategy_key, ignore_faction) - -/// Add or remove people to our retaliation shitlist just on an arbitrary whim -/datum/ai_behavior/capricious_retaliate - action_cooldown = 1 SECONDS - -/datum/ai_behavior/capricious_retaliate/perform(seconds_per_tick, datum/ai_controller/controller, targeting_strategy_key, ignore_faction) +/datum/bt_node/ai_behavior/capricious_retaliate/perform(seconds_per_tick, datum/ai_controller/controller) var/atom/pawn = controller.pawn - if (controller.blackboard_key_exists(BB_BASIC_MOB_RETALIATE_LIST)) + + if(controller.blackboard_key_exists(BB_BASIC_MOB_RETALIATE_LIST)) var/deaggro_chance = controller.blackboard[BB_RANDOM_DEAGGRO_CHANCE] || 10 - if (!SPT_PROB(deaggro_chance, seconds_per_tick)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - pawn.visible_message(span_notice("[pawn] calms down.")) // We can blackboard key this if anyone else actually wants to customise it - controller.clear_blackboard_key(BB_BASIC_MOB_RETALIATE_LIST) - controller.clear_blackboard_key(BB_BASIC_MOB_CURRENT_TARGET) - controller.CancelActions() // Otherwise they will try and get one last kick in - return AI_BEHAVIOR_DELAY + if(prob(deaggro_chance)) //Chance to chill the fuck out. This prob() should be matched with the frequency of calling. + pawn.visible_message(span_notice("[pawn] calms down.")) + controller.clear_blackboard_key(BB_BASIC_MOB_RETALIATE_LIST) + controller.clear_blackboard_key(BB_CURRENT_TARGET) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED // De-aggroed var/aggro_chance = controller.blackboard[BB_RANDOM_AGGRO_CHANCE] || 0.5 - if (!SPT_PROB(aggro_chance, seconds_per_tick)) + if(!prob(aggro_chance)) //Check if we should get pissed at someone REEE return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED var/aggro_range = controller.blackboard[BB_AGGRO_RANGE] || 9 var/list/potential_targets = hearers(aggro_range, get_turf(pawn)) - pawn - if (!length(potential_targets)) + if(!length(potential_targets)) failed_targeting(pawn) return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - var/datum/targeting_strategy/target_helper = GET_TARGETING_STRATEGY(controller.blackboard[targeting_strategy_key]) + if(!ispath(targeting_strategy)) + targeting_strategy = controller.blackboard[targeting_strategy] + + var/datum/targeting_strategy/target_helper = GET_TARGETING_STRATEGY(targeting_strategy) + + if(ignore_faction) + controller.set_blackboard_key(BB_TEMPORARILY_IGNORE_FACTION, TRUE) var/mob/living/final_target = null - if (ignore_faction) - controller.set_blackboard_key(BB_TEMPORARILY_IGNORE_FACTION, TRUE) - while (isnull(final_target) && length(potential_targets)) + while(isnull(final_target) && length(potential_targets)) var/mob/living/test_target = pick_n_take(potential_targets) - if (target_helper.can_attack(pawn, test_target, vision_range = aggro_range)) + if(target_helper.is_valid_target(pawn, test_target, vision_range = aggro_range)) final_target = test_target - if (isnull(final_target)) + if(isnull(final_target)) failed_targeting(pawn) return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + // Add to shitlist set_blackboard_key_assoc_lazylist calls post_blackboard_key_set, waking the combat branch controller.set_blackboard_key_assoc_lazylist(BB_BASIC_MOB_RETALIATE_LIST, final_target, world.time) pawn.visible_message(span_warning("[pawn] glares grumpily at [final_target]!")) return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED -/// Called if we try but fail to target something -/datum/ai_behavior/capricious_retaliate/proc/failed_targeting(atom/pawn) - pawn.visible_message(span_notice("[pawn] grumbles.")) // We're pissed off but with no outlet to vent our frustration upon +/datum/bt_node/ai_behavior/capricious_retaliate/proc/failed_targeting(atom/pawn) + pawn.visible_message(span_notice("[pawn] grumbles.")) -/datum/ai_behavior/capricious_retaliate/finish_action(datum/ai_controller/controller, succeeded, ignore_faction) +/datum/bt_node/ai_behavior/capricious_retaliate/finish_action(datum/ai_controller/controller, succeeded) . = ..() - if (succeeded || !ignore_faction) + if(succeeded || !ignore_faction) return var/usually_ignores_faction = controller.blackboard[BB_ALWAYS_IGNORE_FACTION] || FALSE controller.set_blackboard_key(BB_TEMPORARILY_IGNORE_FACTION, usually_ignores_faction) diff --git a/code/datums/ai/basic_mobs/basic_subtrees/climb_tree.dm b/code/datums/ai/basic_mobs/basic_subtrees/climb_tree.dm deleted file mode 100644 index bad349030f1..00000000000 --- a/code/datums/ai/basic_mobs/basic_subtrees/climb_tree.dm +++ /dev/null @@ -1,15 +0,0 @@ -/datum/ai_planning_subtree/climb_trees - operational_datums = list(/datum/component/tree_climber) - ///chance to climb a tree - var/climb_chance = 35 - -/datum/ai_planning_subtree/climb_trees/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - - if(!SPT_PROB(climb_chance, seconds_per_tick)) - return - - if(controller.blackboard_key_exists(BB_CLIMBED_TREE)) - controller.queue_behavior(/datum/ai_behavior/climb_tree, BB_CLIMBED_TREE) - return SUBTREE_RETURN_FINISH_PLANNING - - controller.queue_behavior(/datum/ai_behavior/find_and_set/valid_tree, BB_CLIMBED_TREE, /obj/structure/flora/tree) diff --git a/code/datums/ai/basic_mobs/basic_subtrees/drag_items.dm b/code/datums/ai/basic_mobs/basic_subtrees/drag_items.dm deleted file mode 100644 index 3bdb1ab8f58..00000000000 --- a/code/datums/ai/basic_mobs/basic_subtrees/drag_items.dm +++ /dev/null @@ -1,60 +0,0 @@ - -///simple behavior to make mobs randomly drag things around -/datum/ai_planning_subtree/steal_items/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/mob/living/living_pawn = controller.pawn - if(living_pawn.pulling) - if(prob(controller.blackboard[BB_GUILTY_CONSCIOUS_CHANCE])) - controller.queue_behavior(/datum/ai_behavior/stop_dragging) - return - if(!prob(controller.blackboard[BB_STEAL_CHANCE])) - return - if(!controller.blackboard_key_exists(BB_ITEM_TO_STEAL)) - controller.queue_behavior(/datum/ai_behavior/find_and_set/find_stealable, BB_ITEM_TO_STEAL, /obj/item) - return - controller.queue_behavior(/datum/ai_behavior/drag_target, BB_ITEM_TO_STEAL) - return SUBTREE_RETURN_FINISH_PLANNING - -/datum/ai_behavior/find_and_set/find_stealable - behavior_flags = AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION - action_cooldown = 2 MINUTES - -/datum/ai_behavior/find_and_set/find_stealable/search_tactic(datum/ai_controller/controller, locate_path, search_range = SEARCH_TACTIC_DEFAULT_RANGE) - var/mob/living/living_pawn = controller.pawn - - var/list/possible_items = shuffle_inplace(oview(search_range, controller.pawn)) - for(var/obj/item/possible_item in possible_items) - if(possible_item.pulledby || possible_item.anchored) - continue - if(can_see(living_pawn, possible_item)) - return possible_item - - -/datum/ai_behavior/stop_dragging - behavior_flags = AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION - -/datum/ai_behavior/stop_dragging/perform(seconds_per_tick, datum/ai_controller/controller) - var/mob/living/living_pawn = controller.pawn - living_pawn.stop_pulling() - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -/datum/ai_behavior/drag_target - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION | AI_BEHAVIOR_REQUIRE_REACH - -/datum/ai_behavior/drag_target/setup(datum/ai_controller/controller, target_key) - . = ..() - var/atom/target = controller.blackboard[target_key] - if(QDELETED(target)) - return FALSE - set_movement_target(controller, target) - -/datum/ai_behavior/drag_target/perform(seconds_per_tick, datum/ai_controller/controller, target_key) - var/atom/movable/target = controller.blackboard[target_key] - if(QDELETED(target) || target.anchored || target.pulledby) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - var/mob/living/our_mob = controller.pawn - our_mob.start_pulling(target) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -/datum/ai_behavior/drag_target/finish_action(datum/ai_controller/controller, succeeded, target_key) - . = ..() - controller.clear_blackboard_key(target_key) diff --git a/code/datums/ai/basic_mobs/basic_subtrees/enrage.dm b/code/datums/ai/basic_mobs/basic_subtrees/enrage.dm index cf3a922a5a4..8523adb9ef5 100644 --- a/code/datums/ai/basic_mobs/basic_subtrees/enrage.dm +++ b/code/datums/ai/basic_mobs/basic_subtrees/enrage.dm @@ -1,44 +1,33 @@ -// Performs the enrage behavior when health is below given threshold, and calm down behavior if above that value afterwards -/datum/ai_planning_subtree/enrage +/// Halves the basic mob's melee attack cooldown while its health is at or below a threshold, and restores it once recovered. +/datum/bt_node/ai_behavior/enrage + /// Fraction of max health at or below which the mob becomes enraged. var/health_threshold = 0.5 - var/enrage_behavior = /datum/ai_behavior/enrage -/datum/ai_planning_subtree/enrage/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) +/datum/bt_node/ai_behavior/enrage/perform(seconds_per_tick, datum/ai_controller/controller) if(!isbasicmob(controller.pawn)) - return + return AI_BEHAVIOR_FAILED + var/mob/living/basic/basic_pawn = controller.pawn var/low_health = (basic_pawn.health / basic_pawn.maxHealth) <= health_threshold - var/is_enraged = controller.blackboard_key_exists(BB_BASIC_MOB_ENRAGE) + if(low_health && !is_enraged) - controller.queue_behavior(enrage_behavior, FALSE) - else if(!low_health && is_enraged) - controller.queue_behavior(enrage_behavior, TRUE) + var/current_cooldown = basic_pawn.melee_attack_cooldown + controller.set_blackboard_key(BB_BASIC_MOB_ENRAGE, TRUE) + controller.set_blackboard_key(BB_BASIC_MOB_PREVIOUS_MELEE_COOLDOWN, current_cooldown) + basic_pawn.melee_attack_cooldown = current_cooldown / 2 + if(controller.blackboard_key_exists(BB_CURRENT_TARGET)) + basic_pawn.visible_message(span_danger("\The [basic_pawn] gets an enraged look at [controller.blackboard[BB_CURRENT_TARGET]]!")) + else + basic_pawn.visible_message(span_danger("\The [basic_pawn] gets an enraged look!")) + return AI_BEHAVIOR_SUCCEEDED -/// Cuts down basic mob's melee attack cooldown in half -/datum/ai_behavior/enrage - -/datum/ai_behavior/enrage/perform(seconds_per_tick, datum/ai_controller/controller, calm_down) - var/mob/living/basic/basic_pawn = controller.pawn - if(calm_down) - var/previous_delay = controller.blackboard[BB_BASIC_MOB_PREVIOUS_MELEE_COOLDOWN] + if(!low_health && is_enraged) // Technically something else could have modified the cooldown before/after but that requires further consideration so don't use this behavior in these scenarios - basic_pawn.melee_attack_cooldown = previous_delay + basic_pawn.melee_attack_cooldown = controller.blackboard[BB_BASIC_MOB_PREVIOUS_MELEE_COOLDOWN] controller.clear_blackboard_key(BB_BASIC_MOB_PREVIOUS_MELEE_COOLDOWN) controller.clear_blackboard_key(BB_BASIC_MOB_ENRAGE) return AI_BEHAVIOR_SUCCEEDED - var/current_cooldown = basic_pawn.melee_attack_cooldown - var/new_attack_cooldown = current_cooldown / 2 - - controller.set_blackboard_key(BB_BASIC_MOB_ENRAGE, TRUE) - controller.set_blackboard_key(BB_BASIC_MOB_PREVIOUS_MELEE_COOLDOWN, current_cooldown) - basic_pawn.melee_attack_cooldown = new_attack_cooldown - - if(controller.blackboard_key_exists(BB_BASIC_MOB_CURRENT_TARGET)) - var/current_target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] - controller.pawn.visible_message(span_danger("\The [controller.pawn] gets an enraged look at [current_target]!")) - else - controller.pawn.visible_message(span_danger("\The [controller.pawn] gets an enraged look!")) - return AI_BEHAVIOR_SUCCEEDED + return AI_BEHAVIOR_FAILED diff --git a/code/datums/ai/basic_mobs/basic_subtrees/escape_captivity.bt.json b/code/datums/ai/basic_mobs/basic_subtrees/escape_captivity.bt.json new file mode 100644 index 00000000000..9bdb05c4b05 --- /dev/null +++ b/code/datums/ai/basic_mobs/basic_subtrees/escape_captivity.bt.json @@ -0,0 +1,82 @@ +{ + "dm_type": "/datum/bt_node/subtree/escape_captivity", + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/pawn_buckled_to_obj", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY" + }, + "child": { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/buckle_target_dangerous", + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/break_out_of_object/from_bb", + "vars": { + "target_key": "BB_BASIC_MOB_ESCAPE_TARGET" + } + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/resist" + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/pawn_contained_in_obj", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY" + }, + "child": { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/container_attackable", + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/break_out_of_object/from_bb", + "vars": { + "target_key": "BB_BASIC_MOB_ESCAPE_TARGET" + } + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/resist" + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/pawn_grabbed_by_enemy", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/resist" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/pawn_is_restrained", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/resist" + } + } + ] +} diff --git a/code/datums/ai/basic_mobs/basic_subtrees/escape_captivity.dm b/code/datums/ai/basic_mobs/basic_subtrees/escape_captivity.dm index 759a5c69eb8..eeb05455c1b 100644 --- a/code/datums/ai/basic_mobs/basic_subtrees/escape_captivity.dm +++ b/code/datums/ai/basic_mobs/basic_subtrees/escape_captivity.dm @@ -1,70 +1,7 @@ -/// Generically try to escape from being trapped -/datum/ai_planning_subtree/escape_captivity - /// Targeting strategy for use deciding if we can attack a mob grabbing us - var/targeting_strategy_key = BB_TARGETING_STRATEGY - /// If true we will never attack objects - var/pacifist = FALSE +///Tries to escape activity, has observers to cancel if needed +/datum/bt_node/subtree/escape_captivity + behavior_tree_json = "code/datums/ai/basic_mobs/basic_subtrees/escape_captivity.bt.json" -/datum/ai_planning_subtree/escape_captivity/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/mob/living/living_pawn = controller.pawn - - if (isobj(living_pawn.buckled)) - // we can just stand up we don't need to freak out - if (pacifist || !HAS_TRAIT(living_pawn.buckled, TRAIT_DANGEROUS_BUCKLE)) - controller.queue_behavior(/datum/ai_behavior/resist) - // otherwise beat the shit out of we we gotta get out NOW - else - controller.queue_behavior(/datum/ai_behavior/break_out_of_object, living_pawn.buckled) - return SUBTREE_RETURN_FINISH_PLANNING - - if (!isturf(living_pawn.loc) && !ismob(living_pawn.loc) && !istype(living_pawn.loc, /obj/item/mob_holder)) - var/atom/contained_in = living_pawn.loc - var/attack_effective = FALSE - if (!pacifist) - if (isbasicmob(living_pawn)) // Currently this literally only works for basic mobs because it's hard to check for anyone else but it's ok because only they use this subtree - var/mob/living/basic/basic_pawn = living_pawn - attack_effective = basic_pawn.obj_damage > contained_in.damage_deflection - if (attack_effective) - controller.queue_behavior(/datum/ai_behavior/break_out_of_object, contained_in) - else - controller.queue_behavior(/datum/ai_behavior/resist) - return SUBTREE_RETURN_FINISH_PLANNING - - var/mob/puller = living_pawn.pulledby - if (puller && puller.grab_state > GRAB_PASSIVE) - var/datum/targeting_strategy/targeting_strategy = GET_TARGETING_STRATEGY(controller.blackboard[targeting_strategy_key]) - var/friends_list = controller.blackboard[BB_FRIENDS_LIST] || list() - // Only resist grabs from mobs that aren't in our faction - if (targeting_strategy?.can_attack(living_pawn, puller) && !(puller in friends_list)) - controller.queue_behavior(/datum/ai_behavior/resist) - return SUBTREE_RETURN_FINISH_PLANNING - - if (HAS_TRAIT(living_pawn, TRAIT_RESTRAINED)) - controller.queue_behavior(/datum/ai_behavior/resist) - return SUBTREE_RETURN_FINISH_PLANNING - -/// Keep attacking an object while it is our loc or while we are buckled to it -/datum/ai_behavior/break_out_of_object - action_cooldown = 0.2 SECONDS - -/datum/ai_behavior/break_out_of_object/setup(datum/ai_controller/controller, atom/target) - if (!should_attack_target(controller, target)) - return FALSE - return TRUE - -/datum/ai_behavior/break_out_of_object/perform(seconds_per_tick, datum/ai_controller/controller, atom/target_atom) - if (!should_attack_target(controller, target_atom)) - return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED - controller.ai_interact(target = target_atom, combat_mode = TRUE) - return AI_BEHAVIOR_DELAY - -/datum/ai_behavior/break_out_of_object/proc/should_attack_target(datum/ai_controller/controller, atom/target) - if (QDELETED(target)) - return FALSE - var/mob/living/pawn = controller.pawn - if (!target.IsReachableBy(pawn)) - return FALSE - return pawn.loc == target || pawn.buckled == target - -/datum/ai_planning_subtree/escape_captivity/pacifist - pacifist = TRUE +/// Pacifist variant: never attacks objects, only resists. +/datum/bt_node/subtree/escape_captivity/pacifist + behavior_tree_json = "code/datums/ai/basic_mobs/basic_subtrees/escape_captivity_pacifist.bt.json" diff --git a/code/datums/ai/basic_mobs/basic_subtrees/escape_captivity_pacifist.bt.json b/code/datums/ai/basic_mobs/basic_subtrees/escape_captivity_pacifist.bt.json new file mode 100644 index 00000000000..59684c19d86 --- /dev/null +++ b/code/datums/ai/basic_mobs/basic_subtrees/escape_captivity_pacifist.bt.json @@ -0,0 +1,50 @@ +{ + "dm_type": "/datum/bt_node/subtree/escape_captivity/pacifist", + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/pawn_buckled_to_obj", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/resist" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/pawn_contained_in_obj", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/resist" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/pawn_grabbed_by_enemy", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/resist" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/pawn_is_restrained", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/resist" + } + } + ] +} diff --git a/code/datums/ai/basic_mobs/basic_subtrees/express_happiness.dm b/code/datums/ai/basic_mobs/basic_subtrees/express_happiness.dm deleted file mode 100644 index 4d7a3e7ad30..00000000000 --- a/code/datums/ai/basic_mobs/basic_subtrees/express_happiness.dm +++ /dev/null @@ -1,44 +0,0 @@ -#define HIGH_HAPPINESS_THRESHOLD 0.7 -#define MODERATE_HAPPINESS_THRESHOLD 0.5 - -/datum/ai_planning_subtree/express_happiness - operational_datums = list(/datum/component/happiness) - ///the key storing our happiness value - var/happiness_key = BB_BASIC_HAPPINESS - ///list of emotions we relay when happy - var/static/list/happy_emotions = list( - "celebrates happily!", - "dances around in excitement!", - ) - ///our moderate emotions - var/static/list/moderate_emotions = list( - "looks satisfied.", - "trots around.", - ) - ///emotions we display when we are sad - var/static/list/depressed_emotions = list( - "looks depressed...", - "turns its back and sulks...", - "looks towards the floor in dissapointment...", - ) - -/datum/ai_planning_subtree/express_happiness/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(!SPT_PROB(5, seconds_per_tick)) - return - var/happiness_value = controller.blackboard[happiness_key] - if(isnull(happiness_value)) - return - var/list/final_list - switch(happiness_value) - if(HIGH_HAPPINESS_THRESHOLD to INFINITY) - final_list = controller.blackboard[BB_HAPPY_EMOTIONS] || happy_emotions - if(MODERATE_HAPPINESS_THRESHOLD to HIGH_HAPPINESS_THRESHOLD) - final_list = controller.blackboard[BB_MODERATE_EMOTIONS] || moderate_emotions - else - final_list = controller.blackboard[BB_SAD_EMOTIONS] || depressed_emotions - if(!length(final_list)) - return - controller.queue_behavior(/datum/ai_behavior/perform_emote, pick(final_list)) - -#undef HIGH_HAPPINESS_THRESHOLD -#undef MODERATE_HAPPINESS_THRESHOLD diff --git a/code/datums/ai/basic_mobs/basic_subtrees/find_food.dm b/code/datums/ai/basic_mobs/basic_subtrees/find_food.dm deleted file mode 100644 index 4ddc354797d..00000000000 --- a/code/datums/ai/basic_mobs/basic_subtrees/find_food.dm +++ /dev/null @@ -1,42 +0,0 @@ -/// similar to finding a target but looks for food types in the // the what? -/datum/ai_planning_subtree/find_food - ///behavior we use to find the food - var/datum/ai_behavior/finding_behavior = /datum/ai_behavior/find_and_set/in_list - ///key of foods list - var/food_list_key = BB_BASIC_FOODS - ///key where we store our food - var/found_food_key = BB_TARGET_FOOD - ///key holding any emotes we play after eating food - var/emotes_blackboard_list = BB_EAT_EMOTES - ///key where we store our search range - var/search_range = BB_SEARCH_RANGE - -/datum/ai_planning_subtree/find_food/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/list/foods_list = controller.blackboard[food_list_key] - if(!length(foods_list)) - CRASH("the types of food has not been supplied in the [food_list_key] key!") - if(controller.blackboard[BB_NEXT_FOOD_EAT] > world.time) - return - if(!controller.blackboard_key_exists(found_food_key)) - controller.queue_behavior(finding_behavior, found_food_key, foods_list, controller.blackboard[BB_SEARCH_RANGE]) - return - - controller.queue_behavior(/datum/ai_behavior/interact_with_target/eat_food, found_food_key, emotes_blackboard_list) - return SUBTREE_RETURN_FINISH_PLANNING - -/datum/ai_behavior/interact_with_target/eat_food - ///default list of actions we take after eating - var/list/food_actions = list( - "eats up happily!", - "chomps with glee!", - ) - -/datum/ai_behavior/interact_with_target/eat_food/perform(seconds_per_tick, datum/ai_controller/controller, target_key, emotes_blackboard_list) - . = ..() - if(. & AI_BEHAVIOR_FAILED) - return - var/list/emotes_to_pick = controller.blackboard[emotes_blackboard_list] || food_actions - if(!length(emotes_to_pick)) - return - var/mob/living/living_pawn = controller.pawn - living_pawn.manual_emote(pick(emotes_to_pick)) diff --git a/code/datums/ai/basic_mobs/basic_subtrees/find_paper_and_write.bt.json b/code/datums/ai/basic_mobs/basic_subtrees/find_paper_and_write.bt.json new file mode 100644 index 00000000000..d6b902983f9 --- /dev/null +++ b/code/datums/ai/basic_mobs/basic_subtrees/find_paper_and_write.bt.json @@ -0,0 +1,73 @@ +{ + "dm_type": "/datum/bt_node/subtree/find_paper_and_write", + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_SELF", + "key": "BB_SIMPLE_CARRY_ITEM" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/write_on_paper", + "vars": { + "paper_key": "BB_SIMPLE_CARRY_ITEM", + "writing_list_key": "BB_WRITING_LIST" + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "key": "BB_FOUND_PAPER" + }, + "child": { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "sequence", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/attack_obstructions", + "vars": { + "time_between_perform": "0.4 SECONDS", + "target_key": "BB_FOUND_PAPER", + "can_attack_turfs": true, + "can_attack_dense_objects": true + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_FOUND_PAPER", + "required_dist": 0 + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/pick_up_item_virtual", + "vars": { + "target_key": "BB_FOUND_PAPER", + "storage_key": "BB_SIMPLE_CARRY_ITEM" + } + } + ] + } + ] + } + } + ] +} diff --git a/code/datums/ai/basic_mobs/basic_subtrees/find_paper_and_write.dm b/code/datums/ai/basic_mobs/basic_subtrees/find_paper_and_write.dm index 0d2d0c3e3b4..947fbec7b8d 100644 --- a/code/datums/ai/basic_mobs/basic_subtrees/find_paper_and_write.dm +++ b/code/datums/ai/basic_mobs/basic_subtrees/find_paper_and_write.dm @@ -1,22 +1,3 @@ -/datum/ai_planning_subtree/find_paper_and_write - -/datum/ai_planning_subtree/find_paper_and_write/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/mob/living/basic/wizard = controller.pawn - - if(controller.blackboard_key_exists(BB_SIMPLE_CARRY_ITEM)) - controller.queue_behavior(/datum/ai_behavior/write_on_paper, BB_SIMPLE_CARRY_ITEM, BB_WRITING_LIST) - return SUBTREE_RETURN_FINISH_PLANNING - - var/obj/item/paper/target = controller.blackboard[BB_FOUND_PAPER] - - if(QDELETED(target)) - controller.queue_behavior(/datum/ai_behavior/find_and_set/empty_paper, BB_FOUND_PAPER, /obj/item/paper) - return - - if(get_turf(wizard) != get_turf(target)) - controller.queue_behavior(/datum/ai_behavior/travel_towards, BB_FOUND_PAPER) - return SUBTREE_RETURN_FINISH_PLANNING - - if(!(target in wizard.contents)) - controller.queue_behavior(/datum/ai_behavior/pick_up_item, BB_FOUND_PAPER, BB_SIMPLE_CARRY_ITEM) - return SUBTREE_RETURN_FINISH_PLANNING +/// Idle behaviour: hunt down a nearby blank paper, fetch it, scrawl a threat on it and drop it. +/datum/bt_node/subtree/find_paper_and_write + behavior_tree_json = "code/datums/ai/basic_mobs/basic_subtrees/find_paper_and_write.bt.json" diff --git a/code/datums/ai/basic_mobs/basic_subtrees/find_parent.dm b/code/datums/ai/basic_mobs/basic_subtrees/find_parent.dm deleted file mode 100644 index 7b35053ff38..00000000000 --- a/code/datums/ai/basic_mobs/basic_subtrees/find_parent.dm +++ /dev/null @@ -1,31 +0,0 @@ -/datum/ai_planning_subtree/look_for_adult - ///how far we must be from the mom - var/minimum_distance = 1 - -/datum/ai_planning_subtree/look_for_adult/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/mob/target = controller.blackboard[BB_FOUND_MOM] - var/mob/baby = controller.pawn - - if(QDELETED(target)) - find_mom(controller) - return - - if(get_dist(target, baby) > minimum_distance) - controller.queue_behavior(/datum/ai_behavior/travel_towards/stop_on_arrival, BB_FOUND_MOM) - return SUBTREE_RETURN_FINISH_PLANNING - - if(!SPT_PROB(15, seconds_per_tick)) - return - - if(target.stat == DEAD) - controller.queue_behavior(/datum/ai_behavior/perform_emote, "cries for their parent!") - else - controller.queue_behavior(/datum/ai_behavior/perform_emote, "dances around their parent!") - - return SUBTREE_RETURN_FINISH_PLANNING - -/datum/ai_planning_subtree/look_for_adult/proc/find_mom(datum/ai_controller/controller) - controller.queue_behavior(/datum/ai_behavior/find_mom, BB_FIND_MOM_TYPES, BB_IGNORE_MOM_TYPES, BB_FOUND_MOM) - -/datum/ai_planning_subtree/look_for_adult/raptor/find_mom(datum/ai_controller/controller) - controller.queue_behavior(/datum/ai_behavior/find_mom/raptor, BB_FIND_MOM_TYPES, BB_FOUND_MOM) diff --git a/code/datums/ai/basic_mobs/basic_subtrees/find_targets_prioritize_traits.dm b/code/datums/ai/basic_mobs/basic_subtrees/find_targets_prioritize_traits.dm deleted file mode 100644 index 6c83469960a..00000000000 --- a/code/datums/ai/basic_mobs/basic_subtrees/find_targets_prioritize_traits.dm +++ /dev/null @@ -1,6 +0,0 @@ -/// Find something with a specific trait to run from -/datum/ai_planning_subtree/find_target_prioritize_traits - -/datum/ai_planning_subtree/find_target_prioritize_traits/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - . = ..() - controller.queue_behavior(/datum/ai_behavior/find_potential_targets/prioritize_trait, BB_BASIC_MOB_CURRENT_TARGET, BB_TARGETING_STRATEGY, BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION, BB_TARGET_PRIORITY_TRAIT) diff --git a/code/datums/ai/basic_mobs/basic_subtrees/fishing.dm b/code/datums/ai/basic_mobs/basic_subtrees/fishing.dm deleted file mode 100644 index e17f50740fa..00000000000 --- a/code/datums/ai/basic_mobs/basic_subtrees/fishing.dm +++ /dev/null @@ -1,44 +0,0 @@ -#define FISHING_COOLDOWN 45 SECONDS - -///subtree for fishing and eating food! -/datum/ai_planning_subtree/fish - ///behavior we use to find fishable objects - var/datum/ai_behavior/find_fishable_behavior = /datum/ai_behavior/find_and_set/in_list - ///behavior we use to fish! - var/datum/ai_behavior/fishing_behavior = /datum/ai_behavior/interact_with_target/fishing - ///blackboard key storing things we can fish from - var/fishable_list_key = BB_FISHABLE_LIST - ///key where we store found fishable items - var/fishing_target_key = BB_FISHING_TARGET - ///key where we store our fishing cooldown - var/fishing_cooldown_key = BB_FISHING_COOLDOWN - ///our fishing range - var/fishing_range = 5 - -/datum/ai_planning_subtree/fish/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(controller.blackboard[BB_ONLY_FISH_WHILE_HUNGRY] && controller.blackboard[BB_NEXT_FOOD_EAT] > world.time) - return - if(controller.blackboard[BB_FISHING_TIMER] > world.time) - return - if(!controller.blackboard_key_exists(fishing_target_key)) - controller.queue_behavior(find_fishable_behavior, fishing_target_key, controller.blackboard[fishable_list_key], fishing_range) - return - controller.queue_behavior(/datum/ai_behavior/interact_with_target/fishing, fishing_target_key, fishing_cooldown_key) - return SUBTREE_RETURN_FINISH_PLANNING - -///less expensive fishing behavior! -/datum/ai_planning_subtree/fish/fish_from_turfs - find_fishable_behavior = /datum/ai_behavior/find_and_set/in_list/closest_turf - -/datum/ai_behavior/interact_with_target/fishing - clear_target = FALSE - combat_mode = FALSE - -/datum/ai_behavior/interact_with_target/fishing/finish_action(datum/ai_controller/controller, succeeded, fishing_target_key, fishing_cooldown_key) - . = ..() - if(!succeeded) - return - var/cooldown = controller.blackboard[fishing_cooldown_key] || FISHING_COOLDOWN - controller.set_blackboard_key(BB_FISHING_TIMER, world.time + cooldown) - -#undef FISHING_COOLDOWN diff --git a/code/datums/ai/basic_mobs/basic_subtrees/flee_target.dm b/code/datums/ai/basic_mobs/basic_subtrees/flee_target.dm deleted file mode 100644 index 3ed8b2df2b2..00000000000 --- a/code/datums/ai/basic_mobs/basic_subtrees/flee_target.dm +++ /dev/null @@ -1,39 +0,0 @@ -/// Try to escape from your current target, without performing any other actions. -/datum/ai_planning_subtree/flee_target - /// Behaviour to execute in order to flee - var/flee_behaviour = /datum/ai_behavior/run_away_from_target - /// Blackboard key in which to store selected target - var/target_key = BB_BASIC_MOB_CURRENT_TARGET - /// Blackboard key in which to store selected target's hiding place - var/hiding_place_key = BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION - -/datum/ai_planning_subtree/flee_target/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - . = ..() - var/atom/flee_from = controller.blackboard[target_key] - if(!should_flee(controller, flee_from)) - return - var/flee_distance = controller.blackboard[BB_BASIC_MOB_FLEE_DISTANCE] || DEFAULT_BASIC_FLEE_DISTANCE - if (get_dist(controller.pawn, flee_from) >= flee_distance) - return - - controller.queue_behavior(flee_behaviour, target_key, hiding_place_key) - return SUBTREE_RETURN_FINISH_PLANNING //we gotta get out of here. - -/datum/ai_planning_subtree/flee_target/proc/should_flee(datum/ai_controller/controller, atom/flee_from) - if (controller.blackboard[BB_BASIC_MOB_STOP_FLEEING] || QDELETED(flee_from)) - return FALSE - return TRUE - -/// Try to escape from your current target, without performing any other actions. -/// Reads from some fleeing-specific targeting keys rather than the current mob target. -/datum/ai_planning_subtree/flee_target/from_flee_key - target_key = BB_BASIC_MOB_FLEE_TARGET - hiding_place_key = BB_BASIC_MOB_FLEE_TARGET_HIDING_LOCATION - -/// A subtype that forces the mob to flee from targets with the scary fisherman trait anyway. -/datum/ai_planning_subtree/flee_target/from_fisherman - -/datum/ai_planning_subtree/flee_target/from_fisherman/should_flee(datum/ai_controller/controller, atom/flee_from) - if (!QDELETED(flee_from) && HAS_TRAIT(flee_from, TRAIT_SCARY_FISHERMAN)) - return TRUE - return ..() diff --git a/code/datums/ai/basic_mobs/basic_subtrees/generic_hunger.dm b/code/datums/ai/basic_mobs/basic_subtrees/generic_hunger.dm new file mode 100644 index 00000000000..38ceaf78659 --- /dev/null +++ b/code/datums/ai/basic_mobs/basic_subtrees/generic_hunger.dm @@ -0,0 +1,2 @@ +/datum/bt_node/subtree/generic_hunger + behavior_tree_json = "code/datums/ai/generic_hunger.bt.json" diff --git a/code/datums/ai/basic_mobs/basic_subtrees/generic_play_instrument.dm b/code/datums/ai/basic_mobs/basic_subtrees/generic_play_instrument.dm new file mode 100644 index 00000000000..7b7e3d7a8ee --- /dev/null +++ b/code/datums/ai/basic_mobs/basic_subtrees/generic_play_instrument.dm @@ -0,0 +1,2 @@ +/datum/bt_node/subtree/generic_play_instrument + behavior_tree_json = "code/datums/ai/generic_play_instrument.bt.json" diff --git a/code/datums/ai/basic_mobs/basic_subtrees/go_for_swim.bt.json b/code/datums/ai/basic_mobs/basic_subtrees/go_for_swim.bt.json new file mode 100644 index 00000000000..1524aad1a2d --- /dev/null +++ b/code/datums/ai/basic_mobs/basic_subtrees/go_for_swim.bt.json @@ -0,0 +1,70 @@ +{ + "dm_type": "/datum/bt_node/subtree/go_for_swim", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "1 SECONDS", + "finish_on_primary": true, + "children": [ + { + "type": "sequence", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_SWIM_ALTERNATE_TURF" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_SWIM_ALTERNATE_TURF", + "required_dist": 0, + "finish_on_arrival": true + } + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/set_bb_cooldown", + "vars": { + "cooldown_key": "BB_KEY_SWIMMER_COOLDOWN", + "cooldown_duration": "30 SECONDS" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/wait", + "vars": { + "duration": "30 SECONDS" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_SWIM_ALTERNATE_TURF", + "targeting_strategy": "/datum/targeting_strategy/walkable_turf", + "target_source": "/datum/target_source/oview_land_turfs" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_SWIM_ALTERNATE_TURF", + "required_dist": 0, + "finish_on_arrival": true + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/swim_splash" + } + ] +} diff --git a/code/datums/ai/basic_mobs/basic_subtrees/go_for_swim.dm b/code/datums/ai/basic_mobs/basic_subtrees/go_for_swim.dm index ba50023567f..89eb9829dcb 100644 --- a/code/datums/ai/basic_mobs/basic_subtrees/go_for_swim.dm +++ b/code/datums/ai/basic_mobs/basic_subtrees/go_for_swim.dm @@ -1,59 +1,15 @@ -#define DEFAULT_TIME_SWIMMER 30 SECONDS +/// Wander between water and land, splashing about now and then. +/datum/bt_node/subtree/go_for_swim + behavior_tree_json = "code/datums/ai/basic_mobs/basic_subtrees/go_for_swim.bt.json" -///subtree to go and swim! -/datum/ai_planning_subtree/go_for_swim - -/datum/ai_planning_subtree/go_for_swim/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(controller.blackboard_key_exists(BB_SWIM_ALTERNATE_TURF)) - controller.queue_behavior(/datum/ai_behavior/travel_towards/swimming, BB_SWIM_ALTERNATE_TURF) - - if(isnull(controller.blackboard[BB_KEY_SWIM_TIME])) - controller.set_blackboard_key(BB_KEY_SWIM_TIME, DEFAULT_TIME_SWIMMER) +/// Splashes about while standing in water. +/datum/bt_node/ai_behavior/swim_splash +/datum/bt_node/ai_behavior/swim_splash/perform(seconds_per_tick, datum/ai_controller/controller) var/mob/living/living_pawn = controller.pawn - var/turf/our_turf = get_turf(living_pawn) - - // we have been taken out of water! - controller.set_blackboard_key(BB_CURRENTLY_SWIMMING, iswaterturf(our_turf)) - - if(controller.blackboard[BB_KEY_SWIM_TIME] < world.time) - controller.queue_behavior(/datum/ai_behavior/find_and_set/swim_alternate, BB_SWIM_ALTERNATE_TURF, /turf/open) - return - - // have some fun in the water - if(controller.blackboard[BB_CURRENTLY_SWIMMING] && SPT_PROB(5, seconds_per_tick)) - controller.queue_behavior(/datum/ai_behavior/perform_emote, "splashes water all around!") - - -///find land if its time to get out of water, otherwise find water -/datum/ai_behavior/find_and_set/swim_alternate - -/datum/ai_behavior/find_and_set/swim_alternate/search_tactic(datum/ai_controller/controller, locate_path, search_range = SEARCH_TACTIC_DEFAULT_RANGE) - var/mob/living/living_pawn = controller.pawn - if(QDELETED(living_pawn)) - return null - var/look_for_land = controller.blackboard[BB_CURRENTLY_SWIMMING] - var/list/possible_turfs = list() - for(var/turf/possible_turf in oview(search_range, living_pawn)) - if(isclosedturf(possible_turf) || is_space_or_openspace(possible_turf)) - continue - if(possible_turf.is_blocked_turf()) - continue - if(look_for_land == iswaterturf(possible_turf)) - continue - possible_turfs += possible_turf - - if(!length(possible_turfs)) - return null - - return(pick(possible_turfs)) - -/datum/ai_behavior/travel_towards/swimming - clear_target = TRUE - -/datum/ai_behavior/travel_towards/swimming/finish_action(datum/ai_controller/controller, succeeded, target_key) - . = ..() - var/time_to_add = controller.blackboard[BB_KEY_SWIMMER_COOLDOWN] ? controller.blackboard[BB_KEY_SWIMMER_COOLDOWN] : DEFAULT_TIME_SWIMMER - controller.set_blackboard_key(BB_KEY_SWIM_TIME, world.time + time_to_add ) - -#undef DEFAULT_TIME_SWIMMER + if(!istype(living_pawn) || !iswaterturf(get_turf(living_pawn))) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + if(!SPT_PROB(5, seconds_per_tick)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + living_pawn.manual_emote("splashes water all around!") + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED diff --git a/code/datums/ai/basic_mobs/basic_subtrees/maintain_distance.dm b/code/datums/ai/basic_mobs/basic_subtrees/maintain_distance.dm deleted file mode 100644 index 8b8b0d0bf6f..00000000000 --- a/code/datums/ai/basic_mobs/basic_subtrees/maintain_distance.dm +++ /dev/null @@ -1,119 +0,0 @@ -/// Step away if too close, or towards if too far -/datum/ai_planning_subtree/maintain_distance - /// Blackboard key holding atom we want to stay away from - var/target_key = BB_BASIC_MOB_CURRENT_TARGET - /// How far do we look for our target? - var/view_distance = 10 - /// the run away behavior we will use - var/run_away_behavior = /datum/ai_behavior/step_away - -/datum/ai_planning_subtree/maintain_distance/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - . = ..() - - var/mob/living/living_pawn = controller.pawn - if(LAZYLEN(living_pawn.do_afters)) - return - - var/atom/target = controller.blackboard[target_key] - if (!isliving(target) || !can_see(controller.pawn, target, view_distance)) - return // Don't run away from cucumbers, they're not snakes - var/range = get_dist(controller.pawn, target) - - var/minimum_distance = controller.blackboard[BB_RANGED_SKIRMISH_MIN_DISTANCE] || 4 - var/maximum_distance = controller.blackboard[BB_RANGED_SKIRMISH_MAX_DISTANCE] || 6 - - if (range < minimum_distance) - controller.queue_behavior(run_away_behavior, target_key, minimum_distance) - return - if (range > maximum_distance) - controller.queue_behavior(/datum/ai_behavior/pursue_to_range, target_key, maximum_distance) - return - -/datum/ai_planning_subtree/maintain_distance/cover_minimum_distance - run_away_behavior = /datum/ai_behavior/cover_minimum_distance - -/// Take one step away -/datum/ai_behavior/step_away - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION - required_distance = 0 - action_cooldown = 0.2 SECONDS - -/datum/ai_behavior/step_away/setup(datum/ai_controller/controller, target_key) - . = ..() - var/atom/current_target = controller.blackboard[target_key] - if (QDELETED(current_target)) - return FALSE - - var/mob/living/our_pawn = controller.pawn - our_pawn.face_atom(current_target) - - var/turf/next_step = get_step_away(controller.pawn, current_target) - if (!isnull(next_step) && !next_step.is_blocked_turf(exclude_mobs = TRUE)) - set_movement_target(controller, target = next_step, new_movement = /datum/ai_movement/basic_avoidance/backstep) - return TRUE - - var/list/all_dirs = GLOB.alldirs.Copy() - all_dirs -= get_dir(controller.pawn, next_step) - all_dirs -= get_dir(controller.pawn, current_target) - shuffle_inplace(all_dirs) - - for (var/dir in all_dirs) - next_step = get_step(controller.pawn, dir) - if (!isnull(next_step) && !next_step.is_blocked_turf(exclude_mobs = TRUE)) - set_movement_target(controller, target = next_step, new_movement = /datum/ai_movement/basic_avoidance/backstep) - return TRUE - return FALSE - -/datum/ai_behavior/step_away/perform(seconds_per_tick, datum/ai_controller/controller) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -/datum/ai_behavior/step_away/finish_action(datum/ai_controller/controller, succeeded) - . = ..() - controller.change_ai_movement_type(initial(controller.ai_movement)) - -/// Pursue a target until we are within a provided range -/datum/ai_behavior/pursue_to_range - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION | AI_BEHAVIOR_MOVE_AND_PERFORM - -/datum/ai_behavior/pursue_to_range/setup(datum/ai_controller/controller, target_key, range) - . = ..() - var/atom/current_target = controller.blackboard[target_key] - if (QDELETED(current_target)) - return FALSE - if (get_dist(controller.pawn, current_target) <= range) - return FALSE - set_movement_target(controller, current_target) - -/datum/ai_behavior/pursue_to_range/perform(seconds_per_tick, datum/ai_controller/controller, target_key, range) - var/atom/current_target = controller.blackboard[target_key] - if (!QDELETED(current_target) && get_dist(controller.pawn, current_target) > range) - return AI_BEHAVIOR_INSTANT - return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED - -///instead of taking a single step, we cover the entire distance -/datum/ai_behavior/cover_minimum_distance - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION - required_distance = 0 - action_cooldown = 0.2 SECONDS - -/datum/ai_behavior/cover_minimum_distance/setup(datum/ai_controller/controller, target_key, minimum_distance) - . = ..() - var/atom/target = controller.blackboard[target_key] - if(QDELETED(target)) - return FALSE - var/required_distance = minimum_distance - get_dist(controller.pawn, target) //the distance we need to move - var/distance = 0 - var/turf/chosen_turf - for(var/turf/open/potential_turf in oview(required_distance, controller.pawn)) - var/new_distance_from_target = get_dist(potential_turf, target) - if(potential_turf.is_blocked_turf()) - continue - if(new_distance_from_target > distance) - chosen_turf = potential_turf - distance = new_distance_from_target - if(isnull(chosen_turf)) - return FALSE - set_movement_target(controller, target = chosen_turf) - -/datum/ai_behavior/cover_minimum_distance/perform(seconds_per_tick, datum/ai_controller/controller, target_key) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED diff --git a/code/datums/ai/basic_mobs/basic_subtrees/mine_walls.dm b/code/datums/ai/basic_mobs/basic_subtrees/mine_walls.dm deleted file mode 100644 index 6edc631c7a1..00000000000 --- a/code/datums/ai/basic_mobs/basic_subtrees/mine_walls.dm +++ /dev/null @@ -1,68 +0,0 @@ -//behavior to find mineable mineral walls - -/datum/ai_planning_subtree/mine_walls - var/find_wall_behavior = /datum/ai_behavior/find_mineral_wall - -/datum/ai_planning_subtree/mine_walls/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(controller.blackboard_key_exists(BB_TARGET_MINERAL_WALL)) - controller.queue_behavior(/datum/ai_behavior/mine_wall, BB_TARGET_MINERAL_WALL) - return SUBTREE_RETURN_FINISH_PLANNING - controller.queue_behavior(find_wall_behavior, BB_TARGET_MINERAL_WALL) - -/datum/ai_behavior/mine_wall - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION - action_cooldown = 15 SECONDS - -/datum/ai_behavior/mine_wall/setup(datum/ai_controller/controller, target_key) - . = ..() - var/turf/target = controller.blackboard[target_key] - if(QDELETED(target)) - return FALSE - set_movement_target(controller, target) - -/datum/ai_behavior/mine_wall/perform(seconds_per_tick, datum/ai_controller/controller, target_key) - . = ..() - var/mob/living/basic/living_pawn = controller.pawn - var/turf/closed/mineral/target = controller.blackboard[target_key] - var/is_gibtonite_turf = istype(target, /turf/closed/mineral/gibtonite) - if(!controller.ai_interact(target = target)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - if(is_gibtonite_turf) - living_pawn.manual_emote("sighs...") //accept whats about to happen to us - - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -/datum/ai_behavior/mine_wall/finish_action(datum/ai_controller/controller, success, target_key) - . = ..() - controller.clear_blackboard_key(target_key) - -/datum/ai_behavior/find_mineral_wall - -/datum/ai_behavior/find_mineral_wall/perform(seconds_per_tick, datum/ai_controller/controller, found_wall_key) - var/mob/living_pawn = controller.pawn - - for(var/turf/closed/mineral/potential_wall in oview(9, living_pawn)) - if(!check_if_mineable(controller, potential_wall)) //check if its surrounded by walls - continue - controller.set_blackboard_key(found_wall_key, potential_wall) //closest wall first! - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - -/datum/ai_behavior/find_mineral_wall/proc/check_if_mineable(datum/ai_controller/controller, turf/target_wall) - var/mob/living/source = controller.pawn - var/direction_to_turf = get_dir(target_wall, source) - if(!ISDIAGONALDIR(direction_to_turf)) - return TRUE - var/list/directions_to_check = list() - for(var/direction_check in GLOB.cardinals) - if(direction_check & direction_to_turf) - directions_to_check += direction_check - - for(var/direction in directions_to_check) - var/turf/test_turf = get_step(target_wall, direction) - if(isnull(test_turf)) - continue - if(!test_turf.is_blocked_turf(ignore_atoms = list(source))) - return TRUE - return FALSE diff --git a/code/datums/ai/basic_mobs/basic_subtrees/move_to_cardinal.dm b/code/datums/ai/basic_mobs/basic_subtrees/move_to_cardinal.dm index bcf5c829025..c36fe040fcf 100644 --- a/code/datums/ai/basic_mobs/basic_subtrees/move_to_cardinal.dm +++ b/code/datums/ai/basic_mobs/basic_subtrees/move_to_cardinal.dm @@ -1,69 +1,64 @@ -/// Try to line up with a cardinal direction of your target -/datum/ai_planning_subtree/move_to_cardinal - /// Behaviour to execute to line ourselves up - var/move_behaviour = /datum/ai_behavior/move_to_cardinal - /// Blackboard key in which to store selected target - var/target_key = BB_BASIC_MOB_CURRENT_TARGET - -/datum/ai_planning_subtree/move_to_cardinal/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - . = ..() - if(!controller.blackboard_key_exists(target_key)) - return - controller.queue_behavior(move_behaviour, target_key) - -/// Try to line up with a cardinal direction of your target -/datum/ai_behavior/move_to_cardinal - required_distance = 0 - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_MOVE_AND_PERFORM | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION - /// How close to our target is too close? +/// Moves to line up with the target along a cardinal direction, so a directional ability can fire down the lane. +/// Reports SUCCESS once lined up and within range, FAILURE when the target is gone, too far, or pathing gives up. +/datum/bt_node/ai_behavior/move_to_cardinal + time_between_perform = 0 + /// Blackboard key holding the atom to line up with. + var/target_key = BB_CURRENT_TARGET + /// How close to our target is too close. var/minimum_distance = 1 - /// How far away is too far? + /// How far away is too far. var/maximum_distance = 9 + /// The cardinal tile of our target we are currently moving toward. + var/atom/destination + /// Set by on_movement_failed() when the movement system gives up pathing. + var/movement_failed = FALSE -/datum/ai_behavior/move_to_cardinal/setup(datum/ai_controller/controller, target_key) +/datum/bt_node/ai_behavior/move_to_cardinal/setup(datum/ai_controller/controller) var/atom/target = controller.blackboard[target_key] if(QDELETED(target)) return FALSE - target_nearest_cardinal(controller, target) + RegisterSignal(controller.pawn, COMSIG_MOB_AI_MOVEMENT_FAILED, PROC_REF(on_movement_failed)) + move_towards_nearest_cardinal(controller, target) return TRUE -/// Set our movement target to the closest cardinal space to our target -/datum/ai_behavior/move_to_cardinal/proc/target_nearest_cardinal(datum/ai_controller/controller, atom/target) +/datum/bt_node/ai_behavior/move_to_cardinal/proc/on_movement_failed(atom/source) + SIGNAL_HANDLER + movement_failed = TRUE + +/// Begin moving toward the closest unblocked cardinal tile of our target. +/datum/bt_node/ai_behavior/move_to_cardinal/proc/move_towards_nearest_cardinal(datum/ai_controller/controller, atom/target) var/atom/move_target var/closest = INFINITY - - for (var/dir in GLOB.cardinals) + for(var/dir in GLOB.cardinals) var/turf/cardinal_turf = get_ranged_target_turf(target, dir, minimum_distance) - if (cardinal_turf.is_blocked_turf()) + if(cardinal_turf.is_blocked_turf()) continue var/distance_to = get_dist(controller.pawn, cardinal_turf) - if (distance_to >= closest) + if(distance_to >= closest) continue closest = distance_to move_target = cardinal_turf - - if (isnull(move_target)) + if(isnull(move_target)) move_target = target - if (controller.current_movement_target == move_target) - return - set_movement_target(controller, move_target) + controller.ai_movement.start_moving_towards(controller, move_target, 0) + destination = move_target -/datum/ai_behavior/move_to_cardinal/perform(seconds_per_tick, datum/ai_controller/controller, target_key) - var/atom/target = controller.blackboard[target_key] - if (QDELETED(target)) +/datum/bt_node/ai_behavior/move_to_cardinal/perform(seconds_per_tick, datum/ai_controller/controller) + if(movement_failed) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + var/atom/target = controller.blackboard[target_key] + if(QDELETED(target)) return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED - if (!(get_dir(controller.pawn, target) in GLOB.cardinals)) - target_nearest_cardinal(controller, target) - return AI_BEHAVIOR_INSTANT var/distance_to_target = get_dist(controller.pawn, target) - if (distance_to_target < minimum_distance) - target_nearest_cardinal(controller, target) - return AI_BEHAVIOR_INSTANT - if (distance_to_target > maximum_distance) + if(distance_to_target > maximum_distance) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + if(!(get_dir(controller.pawn, target) in GLOB.cardinals) || distance_to_target < minimum_distance) + move_towards_nearest_cardinal(controller, target) return AI_BEHAVIOR_INSTANT return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED -/datum/ai_behavior/move_to_cardinal/finish_action(datum/ai_controller/controller, succeeded, target_key) - if (!succeeded) - controller.clear_blackboard_key(target_key) +/datum/bt_node/ai_behavior/move_to_cardinal/finish_action(datum/ai_controller/controller, succeeded) + UnregisterSignal(controller.pawn, COMSIG_MOB_AI_MOVEMENT_FAILED) + movement_failed = FALSE + controller.ai_movement.stop_moving_towards(controller) return ..() diff --git a/code/datums/ai/basic_mobs/basic_subtrees/opportunistic_ventcrawler.dm b/code/datums/ai/basic_mobs/basic_subtrees/opportunistic_ventcrawler.dm deleted file mode 100644 index 240272d1ef4..00000000000 --- a/code/datums/ai/basic_mobs/basic_subtrees/opportunistic_ventcrawler.dm +++ /dev/null @@ -1,20 +0,0 @@ -/// Opportunistically searches for and hides/scurries through vents. -/datum/ai_planning_subtree/opportunistic_ventcrawler - -/datum/ai_planning_subtree/opportunistic_ventcrawler/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(HAS_TRAIT(controller.pawn, TRAIT_MOVE_VENTCRAWLING)) - return SUBTREE_RETURN_FINISH_PLANNING // hold on let me cook - - var/obj/machinery/atmospherics/components/unary/vent_pump/target = controller.blackboard[BB_ENTRY_VENT_TARGET] - - if(QDELETED(target)) - controller.queue_behavior(/datum/ai_behavior/find_and_set, BB_ENTRY_VENT_TARGET, /obj/machinery/atmospherics/components/unary/vent_pump) // keep looking otherwise they KILL US AND WE DIE - return - - if(get_turf(controller.pawn) != get_turf(target)) - controller.queue_behavior(/datum/ai_behavior/travel_towards, BB_ENTRY_VENT_TARGET) - return - - controller.set_blackboard_key(BB_CURRENTLY_TARGETING_VENT, TRUE) - controller.queue_behavior(/datum/ai_behavior/crawl_through_vents, BB_ENTRY_VENT_TARGET) - return SUBTREE_RETURN_FINISH_PLANNING // we are going into this vent... no distractions diff --git a/code/datums/ai/basic_mobs/basic_subtrees/play_with_owners.dm b/code/datums/ai/basic_mobs/basic_subtrees/play_with_owners.dm deleted file mode 100644 index f78574a110a..00000000000 --- a/code/datums/ai/basic_mobs/basic_subtrees/play_with_owners.dm +++ /dev/null @@ -1,21 +0,0 @@ -/datum/ai_planning_subtree/find_and_hunt_target/play_with_owner - target_key = BB_OWNER_TARGET - hunting_behavior = /datum/ai_behavior/hunt_target/play_with_owner - finding_behavior = /datum/ai_behavior/find_hunt_target/find_owner - hunt_targets = list(/mob/living) - hunt_chance = 80 - hunt_range = 9 - -/datum/ai_behavior/find_hunt_target/find_owner - action_cooldown = 1 MINUTES - behavior_flags = AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION - -/datum/ai_behavior/find_hunt_target/find_owner/valid_dinner(mob/living/source, atom/friend, radius, datum/ai_controller/controller, seconds_per_tick) - return (friend != source) && (source.has_ally(friend)) && can_see(source, friend, radius) - -/datum/ai_behavior/hunt_target/play_with_owner - -/datum/ai_behavior/hunt_target/play_with_owner/target_caught(mob/living/hunter, atom/hunted) - var/list/interactions_list = hunter.ai_controller.blackboard[BB_INTERACTIONS_WITH_OWNER] - var/interaction_message = length(interactions_list) ? pick(interactions_list) : "Plays with" - hunter.manual_emote("[interaction_message] [hunted]!") diff --git a/code/datums/ai/basic_mobs/basic_subtrees/prepare_travel_to_destination.dm b/code/datums/ai/basic_mobs/basic_subtrees/prepare_travel_to_destination.dm deleted file mode 100644 index 2718ca630ff..00000000000 --- a/code/datums/ai/basic_mobs/basic_subtrees/prepare_travel_to_destination.dm +++ /dev/null @@ -1,23 +0,0 @@ - -///Subtree that checks if we are on the target atom's tile, and sets it as a travel target if not -///The target is taken from the blackboard. This one always requires a specific implementation. -/datum/ai_planning_subtree/prepare_travel_to_destination - var/target_key - var/travel_destination_key = BB_TRAVEL_DESTINATION - -/datum/ai_planning_subtree/prepare_travel_to_destination/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/atom/target = controller.blackboard[target_key] - - //Target is deleted, or we are already standing on it - if(QDELETED(target) || (isturf(target) && controller.pawn.loc == target) || (target.loc == controller.pawn.loc)) - return - - //Already set with this value, return - if(controller.blackboard[target_key] == controller.blackboard[travel_destination_key]) - return - - controller.queue_behavior(/datum/ai_behavior/set_travel_destination, target_key, travel_destination_key) - return //continue planning regardless of success - -/datum/ai_planning_subtree/prepare_travel_to_destination/trader - target_key = BB_SHOP_SPOT diff --git a/code/datums/ai/basic_mobs/basic_subtrees/random_speech_loop.bt.json b/code/datums/ai/basic_mobs/basic_subtrees/random_speech_loop.bt.json new file mode 100644 index 00000000000..57d616c63bd --- /dev/null +++ b/code/datums/ai/basic_mobs/basic_subtrees/random_speech_loop.bt.json @@ -0,0 +1,19 @@ +{ + "dm_type": "/datum/bt_node/subtree/random_speech_loop", + "bindings": { + "bqdqne64": { + "label": "speech_behavior", + "default": "/datum/bt_node/ai_behavior/random_speech_blackboard" + } + }, + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "loop_delay": "1 SECONDS", + "children": [ + { + "type": "leaf", + "behavior": "$bqdqne64" + } + ] +} diff --git a/code/datums/ai/basic_mobs/basic_subtrees/random_speech_loop.dm b/code/datums/ai/basic_mobs/basic_subtrees/random_speech_loop.dm new file mode 100644 index 00000000000..2a3db065a1e --- /dev/null +++ b/code/datums/ai/basic_mobs/basic_subtrees/random_speech_loop.dm @@ -0,0 +1,2 @@ +/datum/bt_node/subtree/random_speech_loop + behavior_tree_json = "code/datums/ai/basic_mobs/basic_subtrees/random_speech_loop.bt.json" diff --git a/code/datums/ai/basic_mobs/basic_subtrees/ranged_skirmish.dm b/code/datums/ai/basic_mobs/basic_subtrees/ranged_skirmish.dm deleted file mode 100644 index 3004199a70f..00000000000 --- a/code/datums/ai/basic_mobs/basic_subtrees/ranged_skirmish.dm +++ /dev/null @@ -1,50 +0,0 @@ -/// Fire a ranged attack without interrupting movement. -/datum/ai_planning_subtree/ranged_skirmish - operational_datums = list(/datum/component/ranged_attacks) - /// Blackboard key holding target atom - var/target_key = BB_BASIC_MOB_CURRENT_TARGET - /// What AI behaviour do we actually run? - var/attack_behavior = /datum/ai_behavior/ranged_skirmish - /// If target is further away than this we don't fire - var/max_range = 9 - /// If target is closer than this we don't fire - var/min_range = 2 - -/datum/ai_planning_subtree/ranged_skirmish/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - . = ..() - if(!controller.blackboard_key_exists(target_key)) - return - controller.queue_behavior(attack_behavior, target_key, BB_TARGETING_STRATEGY, BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION, max_range, min_range) - -/// How often will we try to perform our ranged attack? -/datum/ai_behavior/ranged_skirmish - action_cooldown = 0.5 SECONDS - -/datum/ai_behavior/ranged_skirmish/setup(datum/ai_controller/controller, target_key, targeting_strategy_key, hiding_location_key, max_range, min_range) - . = ..() - var/atom/target = controller.blackboard[hiding_location_key] || controller.blackboard[target_key] - return !QDELETED(target) - -/datum/ai_behavior/ranged_skirmish/perform(seconds_per_tick, datum/ai_controller/controller, target_key, targeting_strategy_key, hiding_location_key, max_range, min_range) - var/atom/target = controller.blackboard[target_key] - if (QDELETED(target)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - var/datum/targeting_strategy/targeting_strategy = GET_TARGETING_STRATEGY(controller.blackboard[targeting_strategy_key]) - if(!targeting_strategy.can_attack(controller.pawn, target)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - var/hiding_target = targeting_strategy.find_hidden_mobs(controller.pawn, target) - controller.set_blackboard_key(hiding_location_key, hiding_target) - - target = hiding_target || target - - var/distance = get_dist(controller.pawn, target) - if (distance > max_range || distance < min_range) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - controller.ai_interact(target = target, combat_mode = TRUE) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -/datum/ai_planning_subtree/ranged_skirmish/no_minimum - min_range = 0 diff --git a/code/datums/ai/basic_mobs/basic_subtrees/run_away_from_target.bt.json b/code/datums/ai/basic_mobs/basic_subtrees/run_away_from_target.bt.json new file mode 100644 index 00000000000..e3bbd798047 --- /dev/null +++ b/code/datums/ai/basic_mobs/basic_subtrees/run_away_from_target.bt.json @@ -0,0 +1,50 @@ +{ + "dm_type": "/datum/bt_node/subtree/run_away_from_target", + "bindings": { + "byk9gqj4": { + "label": "target_key", + "default": "BB_CURRENT_TARGET" + } + }, + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_BASIC_MOB_STOP_FLEEING", + "invert": true, + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/find_flee_location", + "vars": { + "target_key": "$byk9gqj4", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION", + "destination_key": "BB_FLEE_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_FLEE_LOCATION", + "required_dist": 0, + "finish_on_arrival": true + } + } + ] + } +} diff --git a/code/datums/ai/basic_mobs/basic_subtrees/run_away_from_target.dm b/code/datums/ai/basic_mobs/basic_subtrees/run_away_from_target.dm new file mode 100644 index 00000000000..bfdd9515736 --- /dev/null +++ b/code/datums/ai/basic_mobs/basic_subtrees/run_away_from_target.dm @@ -0,0 +1,8 @@ +/// Flee from BB_CURRENT_TARGET, gated by BB_BASIC_MOB_STOP_FLEEING. +/// Computes a flee waypoint each loop via find_flee_location then moves to it. +/datum/bt_node/subtree/run_away_from_target + behavior_tree_json = "code/datums/ai/basic_mobs/basic_subtrees/run_away_from_target.bt.json" + +/// Flee variant that fires ranged attacks at the current target while moving. +/datum/bt_node/subtree/run_away_from_target/run_and_shoot + behavior_tree_json = "code/datums/ai/basic_mobs/basic_subtrees/run_away_from_target_run_and_shoot.bt.json" diff --git a/code/datums/ai/basic_mobs/basic_subtrees/run_away_from_target_run_and_shoot.bt.json b/code/datums/ai/basic_mobs/basic_subtrees/run_away_from_target_run_and_shoot.bt.json new file mode 100644 index 00000000000..124239b5060 --- /dev/null +++ b/code/datums/ai/basic_mobs/basic_subtrees/run_away_from_target_run_and_shoot.bt.json @@ -0,0 +1,69 @@ +{ + "dm_type": "/datum/bt_node/subtree/run_away_from_target/run_and_shoot", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_BASIC_MOB_STOP_FLEEING", + "invert": true, + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/find_flee_location", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION", + "destination_key": "BB_FLEE_LOCATION" + } + } + ] + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_ranged_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_FLEE_LOCATION", + "required_dist": 0, + "finish_on_arrival": true + } + } + ] + } + ] + } +} diff --git a/code/datums/ai/basic_mobs/basic_subtrees/run_emote.dm b/code/datums/ai/basic_mobs/basic_subtrees/run_emote.dm deleted file mode 100644 index c0c0da96584..00000000000 --- a/code/datums/ai/basic_mobs/basic_subtrees/run_emote.dm +++ /dev/null @@ -1,31 +0,0 @@ -/// Intermittently run an emote -/datum/ai_planning_subtree/run_emote - var/emote_key = BB_EMOTE_KEY - var/emote_chance_key = BB_EMOTE_CHANCE - -/datum/ai_planning_subtree/run_emote/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/emote_chance = controller.blackboard[emote_chance_key] || 0 - if (!SPT_PROB(emote_chance, seconds_per_tick)) - return - controller.queue_behavior(/datum/ai_behavior/run_emote, emote_key) - -/// Emote from a blackboard key -/datum/ai_behavior/run_emote - -/datum/ai_behavior/run_emote/perform(seconds_per_tick, datum/ai_controller/controller, emote_key) - var/mob/living/living_pawn = controller.pawn - if (!isliving(living_pawn)) - return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED - - var/list/emote_list = controller.blackboard[emote_key] - var/emote - if (islist(emote_list)) - emote = length(emote_list) ? pick(emote_list) : null - else - emote = emote_list - - if(isnull(emote)) - return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED - - living_pawn.emote(emote) - return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED diff --git a/code/datums/ai/basic_mobs/basic_subtrees/shapechange_ambush.dm b/code/datums/ai/basic_mobs/basic_subtrees/shapechange_ambush.dm deleted file mode 100644 index ff01eb804ff..00000000000 --- a/code/datums/ai/basic_mobs/basic_subtrees/shapechange_ambush.dm +++ /dev/null @@ -1,41 +0,0 @@ -/// Shapeshift when we have no target, until someone has been nearby for long enough -/datum/ai_planning_subtree/shapechange_ambush - operational_datums = list(/datum/component/ai_target_timer) - /// Key where we keep our ability - var/ability_key = BB_SHAPESHIFT_ACTION - /// Key where we keep our target - var/target_key = BB_BASIC_MOB_CURRENT_TARGET - /// How long to lull our target into a false sense of security - var/minimum_target_time = 8 SECONDS - -/datum/ai_planning_subtree/shapechange_ambush/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/mob/living/living_pawn = controller.pawn - var/is_shifted = ismob(living_pawn.loc) - var/has_target = controller.blackboard_key_exists(target_key) - var/datum/action/cooldown/using_action = controller.blackboard[ability_key] - - if (!is_shifted) - if (has_target) - return // We're busy - - if (using_action?.IsAvailable()) - controller.queue_behavior(/datum/ai_behavior/use_mob_ability/shapeshift, BB_SHAPESHIFT_ACTION) // Shift - return SUBTREE_RETURN_FINISH_PLANNING - - if (!has_target || !using_action?.IsAvailable()) - return SUBTREE_RETURN_FINISH_PLANNING // Lie in wait - var/time_on_target = controller.blackboard[BB_BASIC_MOB_HAS_TARGET_TIME] || 0 - if (time_on_target < minimum_target_time) - return // Wait a bit longer - controller.queue_behavior(/datum/ai_behavior/use_mob_ability/shapeshift, BB_SHAPESHIFT_ACTION) // Surprise! - -/// Selects a random shapeshift ability before shifting -/datum/ai_behavior/use_mob_ability/shapeshift - -/datum/ai_behavior/use_mob_ability/shapeshift/setup(datum/ai_controller/controller, ability_key) - var/datum/action/cooldown/spell/shapeshift/using_action = controller.blackboard[ability_key] - if (!using_action?.IsAvailable()) - return FALSE - if (isnull(using_action.shapeshift_type)) // If we don't have a shape then pick one, AI can't use context wheels - using_action.shapeshift_type = pick(using_action.possible_shapes) - return ..() diff --git a/code/datums/ai/basic_mobs/basic_subtrees/simple_attack_target.dm b/code/datums/ai/basic_mobs/basic_subtrees/simple_attack_target.dm deleted file mode 100644 index f764568d4ba..00000000000 --- a/code/datums/ai/basic_mobs/basic_subtrees/simple_attack_target.dm +++ /dev/null @@ -1,33 +0,0 @@ -/datum/ai_planning_subtree/basic_melee_attack_subtree - /// What do we do in order to attack someone? - var/datum/ai_behavior/basic_melee_attack/melee_attack_behavior = /datum/ai_behavior/basic_melee_attack - /// Is this the last thing we do? (if we set a movement target, this will usually be yes) - var/end_planning = TRUE - -/datum/ai_planning_subtree/basic_melee_attack_subtree/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - . = ..() - if(!controller.blackboard_key_exists(BB_BASIC_MOB_CURRENT_TARGET)) - return - controller.queue_behavior(melee_attack_behavior, BB_BASIC_MOB_CURRENT_TARGET, BB_TARGETING_STRATEGY, BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION) - if (end_planning) - return SUBTREE_RETURN_FINISH_PLANNING //we are going into battle...no distractions. - -/datum/ai_planning_subtree/basic_ranged_attack_subtree - operational_datums = list(/datum/component/ranged_attacks) - var/datum/ai_behavior/basic_ranged_attack/ranged_attack_behavior = /datum/ai_behavior/basic_ranged_attack - -/datum/ai_planning_subtree/basic_ranged_attack_subtree/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - . = ..() - if(!controller.blackboard_key_exists(BB_BASIC_MOB_CURRENT_TARGET)) - return - controller.queue_behavior(ranged_attack_behavior, BB_BASIC_MOB_CURRENT_TARGET, BB_TARGETING_STRATEGY, BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION) - return SUBTREE_RETURN_FINISH_PLANNING //we are going into battle...no distractions. - -/datum/ai_planning_subtree/basic_melee_attack_subtree/no_fisherman - -/datum/ai_planning_subtree/basic_melee_attack_subtree/no_fisherman/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/atom/movable/target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] - if(QDELETED(target)) - return ..() - if(!HAS_TRAIT(target, TRAIT_SCARY_FISHERMAN)) - return ..() diff --git a/code/datums/ai/basic_mobs/basic_subtrees/simple_find_nearest_target_to_flee.dm b/code/datums/ai/basic_mobs/basic_subtrees/simple_find_nearest_target_to_flee.dm deleted file mode 100644 index 83e514f3270..00000000000 --- a/code/datums/ai/basic_mobs/basic_subtrees/simple_find_nearest_target_to_flee.dm +++ /dev/null @@ -1,25 +0,0 @@ -/// Find the nearest thing which we assume is hostile and set it as the flee target -/datum/ai_planning_subtree/simple_find_nearest_target_to_flee - -/datum/ai_planning_subtree/simple_find_nearest_target_to_flee/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - . = ..() - if (controller.blackboard[BB_BASIC_MOB_STOP_FLEEING]) - return - controller.queue_behavior(/datum/ai_behavior/find_potential_targets/nearest, BB_BASIC_MOB_CURRENT_TARGET, BB_TARGETING_STRATEGY, BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION) - -/// Find the nearest thing on our list of 'things which have done damage to me' and set it as the flee target -/datum/ai_planning_subtree/find_nearest_thing_which_attacked_me_to_flee - ///the targeting strategy we use - var/targeting_key = BB_TARGETING_STRATEGY - ///what key should we set the target as - var/target_key = BB_BASIC_MOB_CURRENT_TARGET - -/datum/ai_planning_subtree/find_nearest_thing_which_attacked_me_to_flee/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - . = ..() - if (controller.blackboard[BB_BASIC_MOB_STOP_FLEEING]) - return - controller.queue_behavior(/datum/ai_behavior/target_from_retaliate_list/nearest, BB_BASIC_MOB_RETALIATE_LIST, target_key, targeting_key, BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION) - -/datum/ai_planning_subtree/find_nearest_thing_which_attacked_me_to_flee/from_flee_key - target_key = BB_BASIC_MOB_FLEE_TARGET - targeting_key = BB_FLEE_TARGETING_STRATEGY diff --git a/code/datums/ai/basic_mobs/basic_subtrees/simple_find_target.dm b/code/datums/ai/basic_mobs/basic_subtrees/simple_find_target.dm deleted file mode 100644 index 57fb22b4a95..00000000000 --- a/code/datums/ai/basic_mobs/basic_subtrees/simple_find_target.dm +++ /dev/null @@ -1,26 +0,0 @@ -/datum/ai_planning_subtree/simple_find_target - /// Variable to store target in - var/target_key = BB_BASIC_MOB_CURRENT_TARGET - /// Targeting strategy key to use - var/strategy_key = BB_TARGETING_STRATEGY - /// Behavior to use to find targets - var/target_behavior = /datum/ai_behavior/find_potential_targets - -/datum/ai_planning_subtree/simple_find_target/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - . = ..() - controller.queue_behavior(target_behavior, target_key, strategy_key, BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION) - -// Prevents finding a target if a human is nearby -/datum/ai_planning_subtree/simple_find_target/not_while_observed - -/datum/ai_planning_subtree/simple_find_target/not_while_observed/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - for(var/mob/living/carbon/human/watcher in hearers(7, controller.pawn)) - if(watcher.stat != DEAD) - return - return ..() - -/datum/ai_planning_subtree/simple_find_target/to_flee - target_key = BB_BASIC_MOB_FLEE_TARGET - -/datum/ai_planning_subtree/simple_find_target/hunt - strategy_key = BB_HUNT_TARGETING_STRATEGY diff --git a/code/datums/ai/basic_mobs/basic_subtrees/simple_find_wounded_target.dm b/code/datums/ai/basic_mobs/basic_subtrees/simple_find_wounded_target.dm deleted file mode 100644 index 7a230014d9a..00000000000 --- a/code/datums/ai/basic_mobs/basic_subtrees/simple_find_wounded_target.dm +++ /dev/null @@ -1,6 +0,0 @@ -/// Selects the most wounded potential target that we can see -/datum/ai_planning_subtree/simple_find_wounded_target - -/datum/ai_planning_subtree/simple_find_wounded_target/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - . = ..() - controller.queue_behavior(/datum/ai_behavior/find_potential_targets/most_wounded, BB_BASIC_MOB_CURRENT_TARGET, BB_TARGETING_STRATEGY, BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION) diff --git a/code/datums/ai/basic_mobs/basic_subtrees/skittish_brawler_combat.bt.json b/code/datums/ai/basic_mobs/basic_subtrees/skittish_brawler_combat.bt.json new file mode 100644 index 00000000000..bb2b997b772 --- /dev/null +++ b/code/datums/ai/basic_mobs/basic_subtrees/skittish_brawler_combat.bt.json @@ -0,0 +1,88 @@ +{ + "dm_type": "/datum/bt_node/subtree/skittish_brawler_combat", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CURRENT_TARGET", + "observer_abort": "BT_ABORT_SELF" + }, + "child": { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/is_at_distance", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "maximum_distance": "DEFAULT_BASIC_FLEE_DISTANCE" + }, + "child": { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/run_away_from_target" + } + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list/nearest", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] +} diff --git a/code/datums/ai/basic_mobs/basic_subtrees/skittish_brawler_combat.dm b/code/datums/ai/basic_mobs/basic_subtrees/skittish_brawler_combat.dm new file mode 100644 index 00000000000..3ea474d2ded --- /dev/null +++ b/code/datums/ai/basic_mobs/basic_subtrees/skittish_brawler_combat.dm @@ -0,0 +1,4 @@ +/// Cowardly brawler combat: keep our distance from attackers, fleeing if they get close but +/// turning to bite if they hang back just out of reach. +/datum/bt_node/subtree/skittish_brawler_combat + behavior_tree_json = "code/datums/ai/basic_mobs/basic_subtrees/skittish_brawler_combat.bt.json" diff --git a/code/datums/ai/basic_mobs/basic_subtrees/sleep_with_no_target.dm b/code/datums/ai/basic_mobs/basic_subtrees/sleep_with_no_target.dm deleted file mode 100644 index 649a45d4cc7..00000000000 --- a/code/datums/ai/basic_mobs/basic_subtrees/sleep_with_no_target.dm +++ /dev/null @@ -1,35 +0,0 @@ -/// Disables AI after a certain amount of time spent with no target, you will have to enable the AI again somewhere else -/datum/ai_planning_subtree/sleep_with_no_target - /// Behaviour to execute when sleeping - var/sleep_behaviour = /datum/ai_behavior/sleep_after_targetless_time - /// Target key to interrogate - var/target_key = BB_BASIC_MOB_CURRENT_TARGET - -/datum/ai_planning_subtree/sleep_with_no_target/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - . = ..() - controller.queue_behavior(sleep_behaviour, BB_BASIC_MOB_CURRENT_TARGET) - -/// Disables AI after a certain amount of time spent with no target, you will have to enable the AI again somewhere else -/datum/ai_behavior/sleep_after_targetless_time - /// Turn off AI if we spend this many seconds without a target - var/time_to_wait = 10 SECONDS - -/datum/ai_behavior/sleep_after_targetless_time/perform(seconds_per_tick, datum/ai_controller/controller, target_key) - return (controller.blackboard_key_exists(target_key)) ? ( AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED) : ( AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED) - -/datum/ai_behavior/sleep_after_targetless_time/finish_action(datum/ai_controller/controller, succeeded, target_key) - . = ..() - if (!succeeded) - controller.clear_blackboard_key(BB_TARGETLESS_TIME) - return - - if (isnull(controller.blackboard[BB_TARGETLESS_TIME])) - controller.set_blackboard_key(BB_TARGETLESS_TIME, world.time + time_to_wait) - - if (controller.blackboard[BB_TARGETLESS_TIME] < world.time) - enter_sleep(controller) - controller.clear_blackboard_key(BB_TARGETLESS_TIME) - -/// Disables AI, override to do additional things or something else -/datum/ai_behavior/sleep_after_targetless_time/proc/enter_sleep(datum/ai_controller/controller) - controller.set_ai_status(AI_STATUS_OFF) diff --git a/code/datums/ai/basic_mobs/basic_subtrees/speech_subtree.dm b/code/datums/ai/basic_mobs/basic_subtrees/speech_subtree.dm deleted file mode 100644 index 2a4a0d336e2..00000000000 --- a/code/datums/ai/basic_mobs/basic_subtrees/speech_subtree.dm +++ /dev/null @@ -1,242 +0,0 @@ -/datum/ai_planning_subtree/random_speech - //The chance of an emote occurring each second - var/speech_chance = 0 - ///Hearable emotes - var/list/emote_hear - ///Unlike speak_emote, the list of things in this variable only show by themselves with no spoken text. IE: Ian barks, Ian yaps - var/list/emote_see - ///Possible lines of speech the AI can have - var/list/speak - ///The sound effects associated with this speech, if any - var/list/sound - -/datum/ai_planning_subtree/random_speech/New() - . = ..() - if(LAZYLEN(speak)) - speak = string_list(speak) - if(LAZYLEN(sound)) - sound = string_list(sound) - if(LAZYLEN(emote_hear)) - emote_hear = string_list(emote_hear) - if(LAZYLEN(emote_see)) - emote_see = string_list(emote_see) - -/datum/ai_planning_subtree/random_speech/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(!SPT_PROB(speech_chance, seconds_per_tick)) - return - speak(controller) - -/// Actually perform an action -/datum/ai_planning_subtree/random_speech/proc/speak(datum/ai_controller/controller) - var/audible_emotes_length = emote_hear?.len - var/non_audible_emotes_length = emote_see?.len - var/speak_lines_length = speak?.len - - var/total_choices_length = audible_emotes_length + non_audible_emotes_length + speak_lines_length - - if (total_choices_length == 0) - return - - var/random_number_in_range = rand(1, total_choices_length) - var/sound_to_play = length(sound) > 0 ? pick(sound) : null - - if(random_number_in_range <= audible_emotes_length) - controller.queue_behavior(/datum/ai_behavior/perform_emote, pick(emote_hear), sound_to_play) - else if(random_number_in_range <= (audible_emotes_length + non_audible_emotes_length)) - controller.queue_behavior(/datum/ai_behavior/perform_emote, pick(emote_see)) - else - controller.queue_behavior(/datum/ai_behavior/perform_speech, pick(speak), sound_to_play) - -/datum/ai_planning_subtree/random_speech/insect - speech_chance = 5 - sound = list('sound/mobs/non-humanoids/insect/chitter.ogg') - emote_hear = list("chitters.") - -/datum/ai_planning_subtree/random_speech/mothroach - speech_chance = 15 - emote_hear = list("flutters.") - -/datum/ai_planning_subtree/random_speech/mouse - speech_chance = 1 - speak = list("Squeak!", "SQUEAK!", "Squeak?") - sound = list('sound/mobs/non-humanoids/mouse/mousesqueek.ogg') - emote_hear = list("squeaks.") - emote_see = list("runs in a circle.", "shakes.") - -/datum/ai_planning_subtree/random_speech/frog - speech_chance = 3 - emote_see = list("jumps in a circle.", "shakes.") - -/datum/ai_planning_subtree/random_speech/lizard // all of these have to be three words long or i'm killing you. you're dead. - speech_chance = 3 - emote_hear = list("stamps around some.", "hisses a bit.") - emote_see = list("blehs the tongue.", "tilts the head.", "does a spin.") - -/datum/ai_planning_subtree/random_speech/sheep - speech_chance = 5 - speak = list("baaa","baaaAAAAAH!","baaah") - sound = list('sound/mobs/non-humanoids/sheep/sheep1.ogg', 'sound/mobs/non-humanoids/sheep/sheep2.ogg', 'sound/mobs/non-humanoids/sheep/sheep3.ogg') - emote_hear = list("bleats.") - emote_see = list("shakes her head.", "stares into the distance.") - -/datum/ai_planning_subtree/random_speech/rabbit - speech_chance = 10 - speak = list("Mrrp.", "CHIRP!", "Mrrp?") // rabbits make some weird noises dude i don't know what to tell you - emote_hear = list("hops.") - emote_see = list("hops around.", "bounces up and down.") - -/// For the easter subvariant of rabbits, these ones actually speak catchphrases. -/datum/ai_planning_subtree/random_speech/rabbit/easter - speak = list( - "Hop into Easter!", - "Come get your eggs!", - "Prizes for everyone!", - ) - -/// These ones have a space mask on, so their catchphrases are muffled. -/datum/ai_planning_subtree/random_speech/rabbit/easter/space - speak = list( - "Hmph mmph mmmph!", - "Mmphe mmphe mmphe!", - "Hmm mmm mmm!", - ) - -/datum/ai_planning_subtree/random_speech/chicken - speech_chance = 15 // really talkative ladies - speak = list("Cluck!", "BWAAAAARK BWAK BWAK BWAK!", "Bwaak bwak.") - sound = list('sound/mobs/non-humanoids/chicken/clucks.ogg', 'sound/mobs/non-humanoids/chicken/bagawk.ogg') - emote_hear = list("clucks.", "croons.") - emote_see = list("pecks at the ground.","flaps her wings viciously.") - -/datum/ai_planning_subtree/random_speech/chick - speech_chance = 4 - speak = list("Cherp.", "Cherp?", "Chirrup.", "Cheep!") - sound = list('sound/mobs/non-humanoids/chicken/chick_peep.ogg') - emote_hear = list("cheeps.") - emote_see = list("pecks at the ground.","flaps her tiny wings.") - -/datum/ai_planning_subtree/random_speech/cow - speech_chance = 1 - speak = list("moo?","moo","MOOOOOO") - sound = list('sound/mobs/non-humanoids/cow/cow.ogg') - emote_hear = list("brays.") - emote_see = list("shakes her head.") - -///unlike normal cows, wisdom cows speak of wisdom and won't shut the fuck up -/datum/ai_planning_subtree/random_speech/cow/wisdom - speech_chance = 15 - -/datum/ai_planning_subtree/random_speech/cow/wisdom/New() - . = ..() - speak = GLOB.wisdoms //Done here so it's setup properly - sound = list() - -/datum/ai_planning_subtree/random_speech/deer - speech_chance = 1 - speak = list("Weeeeeeee?", "Weeee", "WEOOOOOOOOOO") - emote_hear = list("brays.") - emote_see = list("shakes her head.") - -/datum/ai_planning_subtree/random_speech/dog - speech_chance = 1 - -/datum/ai_planning_subtree/random_speech/dog/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(!isdog(controller.pawn)) - return - - // Stay in sync with dog fashion. - var/mob/living/basic/pet/dog/dog_pawn = controller.pawn - dog_pawn.update_dog_speech(src) - - return ..() - -/datum/ai_planning_subtree/random_speech/faithless - speech_chance = 1 - emote_see = list("wails.") - -/datum/ai_planning_subtree/random_speech/garden_gnome - speech_chance = 5 - speak = list("Gnot a gnelf!", "Gnot a gnoblin!", "Howdy chum!") - emote_hear = list("snores.", "burps.") - emote_see = list("blinks.") - -/datum/ai_planning_subtree/random_speech/tree - speech_chance = 3 - emote_see = list("photosynthesizes angrily.") - -/datum/ai_planning_subtree/random_speech/pig - speech_chance = 3 - speak = list("oink?","oink","snurf") - sound = list('sound/mobs/non-humanoids/pig/pig1.ogg', 'sound/mobs/non-humanoids/pig/pig2.ogg') - emote_hear = list("snorts.") - emote_see = list("sniffs around.") - -/datum/ai_planning_subtree/random_speech/pony - speech_chance = 3 - sound = list('sound/mobs/non-humanoids/pony/whinny01.ogg', 'sound/mobs/non-humanoids/pony/whinny02.ogg', 'sound/mobs/non-humanoids/pony/whinny03.ogg') - emote_hear = list("whinnies!") - emote_see = list("horses around.") - -/datum/ai_planning_subtree/random_speech/pony/tamed - speech_chance = 3 - sound = list('sound/mobs/non-humanoids/pony/snort.ogg') - emote_hear = list("snorts.") - emote_see = list("snorts.") - -/datum/ai_planning_subtree/random_speech/killer_tomato - speech_chance = 3 - emote_hear = list("gnashes.", "growls lowly.", "snarls.") - emote_see = list("salivates.") - -/datum/ai_planning_subtree/random_speech/ant - speech_chance = 1 - speak = list("BZZZZT!", "CHTCHTCHT!", "Bzzz", "ChtChtCht") - sound = list('sound/mobs/non-humanoids/insect/chitter.ogg') - emote_hear = list("buzzes.", "clacks.") - emote_see = list("shakes their head.", "twitches their antennae.") - -/datum/ai_planning_subtree/random_speech/fox - speech_chance = 1 - speak = list("Ack-Ack", "Ack-Ack-Ack-Ackawoooo", "Geckers", "Awoo", "Tchoff") - emote_hear = list("howls.", "barks.", "screams.") - emote_see = list("shakes their head.", "shivers.") - -/datum/ai_planning_subtree/random_speech/crab - speech_chance = 1 - sound = list('sound/mobs/non-humanoids/crab/claw_click.ogg') - emote_hear = list("clicks.") - emote_see = list("clacks.") - -/datum/ai_planning_subtree/random_speech/penguin - speech_chance = 5 - speak = list("Gah Gah!", "NOOT NOOT!", "NOOT!", "Noot", "noot", "Prah!", "Grah!") - emote_hear = list("squawks", "gakkers") - -/datum/ai_planning_subtree/random_speech/bear - speech_chance = 5 - emote_hear = list("rawrs.","grumbles.","grawls.", "stomps!") - emote_see = list("stares ferociously.") - -/datum/ai_planning_subtree/random_speech/cats - speech_chance = 10 - sound = list(SFX_CAT_MEOW) - emote_hear = list("meows.") - emote_see = list("meows.") - -/datum/ai_planning_subtree/random_speech/blackboard //literal tower of babel, subtree form - speech_chance = 1 - -/datum/ai_planning_subtree/random_speech/blackboard/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/list/speech_lines = controller.blackboard[BB_BASIC_MOB_SPEAK_LINES] - if(isnull(speech_lines)) - return ..() - - // Note to future developers: this behaviour a singleton so this probably doesn't work as you would expect - // The whole speech tree really needs to be refactored because this isn't how we use AI data these days - speak = speech_lines[BB_EMOTE_SAY] || list() - emote_see = speech_lines[BB_EMOTE_SEE] || list() - emote_hear = speech_lines[BB_EMOTE_HEAR] || list() - sound = speech_lines[BB_EMOTE_SOUND] || list() - speech_chance = speech_lines[BB_SPEAK_CHANCE] ? speech_lines[BB_SPEAK_CHANCE] : initial(speech_chance) - - return ..() diff --git a/code/datums/ai/basic_mobs/basic_subtrees/stare_at_thing.dm b/code/datums/ai/basic_mobs/basic_subtrees/stare_at_thing.dm deleted file mode 100644 index 5263c8f82c6..00000000000 --- a/code/datums/ai/basic_mobs/basic_subtrees/stare_at_thing.dm +++ /dev/null @@ -1,12 +0,0 @@ -/// Locate a thing (practically any atom) to stop and stare at. -/datum/ai_planning_subtree/stare_at_thing - -/datum/ai_planning_subtree/stare_at_thing/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/atom/target = controller.blackboard[BB_STATIONARY_CAUSE] - - if(isnull(target)) // No target? Time to locate one using the list we set in this mob's blackboard. - var/list/potential_scares = controller.blackboard[BB_STATIONARY_TARGETS] - controller.queue_behavior(/datum/ai_behavior/find_and_set/in_list, BB_STATIONARY_CAUSE, potential_scares) - return - - controller.queue_behavior(/datum/ai_behavior/stop_and_stare, BB_STATIONARY_CAUSE) diff --git a/code/datums/ai/basic_mobs/basic_subtrees/target_retaliate.dm b/code/datums/ai/basic_mobs/basic_subtrees/target_retaliate.dm deleted file mode 100644 index c8699405461..00000000000 --- a/code/datums/ai/basic_mobs/basic_subtrees/target_retaliate.dm +++ /dev/null @@ -1,94 +0,0 @@ -/// Sets the BB target to a mob which you can see and who has recently attacked you -/datum/ai_planning_subtree/target_retaliate - operational_datums = list(/datum/element/ai_retaliate, /datum/component/ai_retaliate_advanced) - /// Blackboard key which tells us how to select valid targets - var/targeting_strategy_key = BB_TARGETING_STRATEGY - /// Blackboard key in which to store selected target - var/target_key = BB_BASIC_MOB_CURRENT_TARGET - /// Blackboard key in which to store selected target's hiding place - var/hiding_place_key = BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION - /// do we check for faction? - var/check_faction = FALSE - /// Behavior to use to select our target - var/target_behavior = /datum/ai_behavior/target_from_retaliate_list - -/datum/ai_planning_subtree/target_retaliate/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - controller.queue_behavior(target_behavior, BB_BASIC_MOB_RETALIATE_LIST, target_key, targeting_strategy_key, hiding_place_key, check_faction) - -/datum/ai_planning_subtree/target_retaliate/check_faction - check_faction = TRUE - -/// Places a mob which you can see and who has recently attacked you into some 'run away from this' AI keys -/// Can use a different targeting strategy than you use to select attack targets -/// Not required if fleeing is the only target behaviour or uses the same target datum -/datum/ai_planning_subtree/target_retaliate/to_flee - targeting_strategy_key = BB_FLEE_TARGETING_STRATEGY - target_key = BB_BASIC_MOB_FLEE_TARGET - hiding_place_key = BB_BASIC_MOB_FLEE_TARGET_HIDING_LOCATION - -/** - * Picks a target from a provided list of atoms who have been pissing you off - * You will probably need /datum/element/ai_retaliate to take advantage of this unless you're populating the blackboard yourself - */ -/datum/ai_behavior/target_from_retaliate_list - action_cooldown = 2 SECONDS - /// How far can we see stuff? - var/vision_range = 9 - -/datum/ai_behavior/target_from_retaliate_list/perform(seconds_per_tick, datum/ai_controller/controller, shitlist_key, target_key, targeting_strategy_key, hiding_location_key, check_faction) - var/mob/living/living_mob = controller.pawn - var/datum/targeting_strategy/targeting_strategy = GET_TARGETING_STRATEGY(controller.blackboard[targeting_strategy_key]) - if(!targeting_strategy) - . = AI_BEHAVIOR_DELAY - CRASH("No target datum was supplied in the blackboard for [controller.pawn]") - - var/list/shitlist = controller.blackboard[shitlist_key] - var/atom/existing_target = controller.blackboard[target_key] - - var/datum/target_priority_strategy/priority_strategy = GET_TARGET_PRIORITY_STRATEGY(controller.blackboard[BB_TARGET_PRIORITY_STRATEGY]) - var/existing_priority = 0 - // If we have an existing target and its priority is higher than our new target's, don't switch focus - if (priority_strategy && existing_target) - existing_priority = priority_strategy.get_target_priority(controller, existing_target) - - if (!check_faction) - controller.set_blackboard_key(BB_TEMPORARILY_IGNORE_FACTION, TRUE) - - if (!QDELETED(existing_target) && targeting_strategy.can_attack(living_mob, existing_target, vision_range)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - - var/list/enemies_list = list() - for(var/mob/living/potential_target as anything in shitlist) - if(!targeting_strategy.can_attack(living_mob, potential_target, vision_range)) - continue - // Strict comparasion because priority strategies might not care about retaliation, so this makes existing targets not override potential retaliates - if (priority_strategy && priority_strategy.get_target_priority(controller, potential_target) < existing_priority) - continue - enemies_list += potential_target - - if(!length(enemies_list)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - var/atom/new_target = pick_final_target(controller, enemies_list) - controller.set_blackboard_key(target_key, new_target) - - var/atom/potential_hiding_location = targeting_strategy.find_hidden_mobs(living_mob, new_target) - - if(potential_hiding_location) //If they're hiding inside of something, we need to know so we can go for that instead initially. - controller.set_blackboard_key(hiding_location_key, potential_hiding_location) - - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -/// Returns the desired final target from the filtered list of enemies -/datum/ai_behavior/target_from_retaliate_list/proc/pick_final_target(datum/ai_controller/controller, list/enemies_list) - var/datum/target_priority_strategy/priority_strategy = GET_TARGET_PRIORITY_STRATEGY(controller.blackboard[BB_TARGET_PRIORITY_STRATEGY]) - if (!priority_strategy) - return pick(enemies_list) - return priority_strategy.select_target(controller, enemies_list) - -/datum/ai_behavior/target_from_retaliate_list/finish_action(datum/ai_controller/controller, succeeded, shitlist_key, target_key, targeting_strategy_key, hiding_location_key, check_faction) - . = ..() - if (succeeded || check_faction) - return - var/usually_ignores_faction = controller.blackboard[BB_ALWAYS_IGNORE_FACTION] || FALSE - controller.set_blackboard_key(BB_TEMPORARILY_IGNORE_FACTION, usually_ignores_faction) diff --git a/code/datums/ai/basic_mobs/basic_subtrees/targeted_mob_ability.dm b/code/datums/ai/basic_mobs/basic_subtrees/targeted_mob_ability.dm deleted file mode 100644 index 010dd04a425..00000000000 --- a/code/datums/ai/basic_mobs/basic_subtrees/targeted_mob_ability.dm +++ /dev/null @@ -1,34 +0,0 @@ -/// Attempts to use a mob ability on a target -/datum/ai_planning_subtree/targeted_mob_ability - /// Blackboard key for the ability - var/ability_key = BB_TARGETED_ACTION - /// Blackboard key for where the target ref is stored - var/target_key = BB_BASIC_MOB_CURRENT_TARGET - /// Behaviour to perform using ability - var/use_ability_behaviour = /datum/ai_behavior/targeted_mob_ability - /// If true we terminate planning after trying to use the ability. - var/finish_planning = TRUE - -/datum/ai_planning_subtree/targeted_mob_ability/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if (!ability_key) - CRASH("You forgot to tell this mob where to find its ability") - - if (!controller.blackboard_key_exists(target_key)) - return - - var/datum/action/cooldown/using_action = controller.blackboard[ability_key] - if (!using_action?.IsAvailable()) - return - if (!additional_ability_checks(controller, using_action)) - return - - controller.queue_behavior(use_ability_behaviour, ability_key, target_key) - if (finish_planning) - return SUBTREE_RETURN_FINISH_PLANNING - -/// Any additional checks before we queue the behaviour -/datum/ai_planning_subtree/targeted_mob_ability/proc/additional_ability_checks(datum/ai_controller/controller, datum/action/cooldown/using_action) - return TRUE - -/datum/ai_planning_subtree/targeted_mob_ability/continue_planning - finish_planning = FALSE diff --git a/code/datums/ai/basic_mobs/basic_subtrees/teleport_away_from_target.dm b/code/datums/ai/basic_mobs/basic_subtrees/teleport_away_from_target.dm deleted file mode 100644 index 25f0e4a4249..00000000000 --- a/code/datums/ai/basic_mobs/basic_subtrees/teleport_away_from_target.dm +++ /dev/null @@ -1,55 +0,0 @@ -///behavior to activate ability to escape from target -/datum/ai_planning_subtree/teleport_away_from_target - ///minimum distance away from the target before we execute behavior - var/minimum_distance = 2 - ///the ability we will execute - var/ability_key - -/datum/ai_planning_subtree/teleport_away_from_target/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(!controller.blackboard_key_exists(BB_BASIC_MOB_CURRENT_TARGET)) - return - var/atom/target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] - var/distance_from_target = get_dist(target, controller.pawn) - if(distance_from_target >= minimum_distance) - controller.clear_blackboard_key(BB_ESCAPE_DESTINATION) - return - var/datum/action/cooldown/ability = controller.blackboard[ability_key] - if(!ability?.IsAvailable()) - return - var/turf/location_turf = controller.blackboard[BB_ESCAPE_DESTINATION] - - if(isnull(location_turf)) - controller.queue_behavior(/datum/ai_behavior/find_furthest_turf_from_target, BB_BASIC_MOB_CURRENT_TARGET, BB_ESCAPE_DESTINATION, minimum_distance) - return SUBTREE_RETURN_FINISH_PLANNING - - if(get_dist(location_turf, target) < minimum_distance || !can_see(controller.pawn, location_turf)) //target moved close too close or we moved too far since finding the target turf - controller.clear_blackboard_key(BB_ESCAPE_DESTINATION) - return - - controller.queue_behavior(/datum/ai_behavior/targeted_mob_ability/and_clear_target, ability_key, BB_ESCAPE_DESTINATION) - -///find furtherst turf target so we may teleport to it -/datum/ai_behavior/find_furthest_turf_from_target - -/datum/ai_behavior/find_furthest_turf_from_target/perform(seconds_per_tick, datum/ai_controller/controller, target_key, set_key, range) - var/mob/living/living_target = controller.blackboard[target_key] - if(QDELETED(living_target)) - return AI_BEHAVIOR_INSTANT - - var/distance = 0 - var/turf/chosen_turf - for(var/turf/open/potential_destination in oview(range, living_target)) - if(potential_destination.is_blocked_turf()) - continue - var/new_distance_to_target = get_dist(potential_destination, living_target) - if(new_distance_to_target > distance) - chosen_turf = potential_destination - distance = new_distance_to_target - if(distance == range) - break //we have already found the max distance - - if(isnull(chosen_turf)) - return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED - - controller.set_blackboard_key(set_key, chosen_turf) - return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED diff --git a/code/datums/ai/basic_mobs/basic_subtrees/tip_reaction.bt.json b/code/datums/ai/basic_mobs/basic_subtrees/tip_reaction.bt.json new file mode 100644 index 00000000000..862d917bde0 --- /dev/null +++ b/code/datums/ai/basic_mobs/basic_subtrees/tip_reaction.bt.json @@ -0,0 +1,13 @@ +{ + "dm_type": "/datum/bt_node/subtree/tip_reaction", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_true", + "vars": { + "key": "BB_BASIC_MOB_TIP_REACTING", + "observer_abort": "BT_ABORT_LOWER_PRIORITY" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/tipped_reaction" + } +} diff --git a/code/datums/ai/basic_mobs/basic_subtrees/tipped_subtree.dm b/code/datums/ai/basic_mobs/basic_subtrees/tipped_subtree.dm deleted file mode 100644 index b502860a6be..00000000000 --- a/code/datums/ai/basic_mobs/basic_subtrees/tipped_subtree.dm +++ /dev/null @@ -1,10 +0,0 @@ -///used by cows -/datum/ai_planning_subtree/tip_reaction - -/datum/ai_planning_subtree/tip_reaction/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - . = ..() - var/tip_reacting = controller.blackboard[BB_BASIC_MOB_TIP_REACTING] - if(!tip_reacting) - return - controller.queue_behavior(/datum/ai_behavior/tipped_reaction, BB_BASIC_MOB_TIPPER, BB_BASIC_MOB_TIP_REACTING) - return SUBTREE_RETURN_FINISH_PLANNING //no point in trying, boy. you're TIPPED. diff --git a/code/datums/ai/basic_mobs/basic_subtrees/travel_to_point.dm b/code/datums/ai/basic_mobs/basic_subtrees/travel_to_point.dm deleted file mode 100644 index 0b5e5d4776f..00000000000 --- a/code/datums/ai/basic_mobs/basic_subtrees/travel_to_point.dm +++ /dev/null @@ -1,21 +0,0 @@ -/// Simply walk to a location -/datum/ai_planning_subtree/travel_to_point - /// Blackboard key where we travel a place we walk to - var/location_key = BB_TRAVEL_DESTINATION - /// What do we do in order to travel - var/travel_behaviour = /datum/ai_behavior/travel_towards - -/datum/ai_planning_subtree/travel_to_point/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - . = ..() - var/atom/target = controller.blackboard[location_key] - if (QDELETED(target)) - return - controller.queue_behavior(travel_behaviour, location_key) - return SUBTREE_RETURN_FINISH_PLANNING - - -/datum/ai_planning_subtree/travel_to_point/and_clear_target - travel_behaviour = /datum/ai_behavior/travel_towards/stop_on_arrival - -/datum/ai_planning_subtree/travel_to_point/and_clear_target/reinforce - location_key = BB_BASIC_MOB_REINFORCEMENT_TARGET diff --git a/code/datums/ai/basic_mobs/basic_subtrees/use_mob_ability.dm b/code/datums/ai/basic_mobs/basic_subtrees/use_mob_ability.dm deleted file mode 100644 index 4c794a73d8a..00000000000 --- a/code/datums/ai/basic_mobs/basic_subtrees/use_mob_ability.dm +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Simple behaviours which simply try to use an ability whenever it is available. - * For something which wants a target try `targeted_mob_ability`. - */ -/datum/ai_planning_subtree/use_mob_ability - /// Blackboard key for the ability - var/ability_key = BB_GENERIC_ACTION - /// Behaviour to perform using ability - var/use_ability_behaviour = /datum/ai_behavior/use_mob_ability - /// If true we terminate planning after trying to use the ability. - var/finish_planning = FALSE - -/datum/ai_planning_subtree/use_mob_ability/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if (!ability_key) - CRASH("You forgot to tell this mob where to find its ability") - - var/datum/action/using_action = controller.blackboard[ability_key] - if (!using_action?.IsAvailable()) - return - - controller.queue_behavior(use_ability_behaviour, ability_key) - if (finish_planning) - return SUBTREE_RETURN_FINISH_PLANNING - -/datum/ai_behavior/use_mob_ability - -/datum/ai_behavior/use_mob_ability/perform(seconds_per_tick, datum/ai_controller/controller, ability_key) - var/datum/action/using_action = controller.blackboard[ability_key] - if (QDELETED(using_action)) - return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED - if(using_action.Trigger()) - return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED - return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED diff --git a/code/datums/ai/basic_mobs/generic_controllers.dm b/code/datums/ai/basic_mobs/generic_controllers.dm index b6581a5cb8b..0a0661f5a8d 100644 --- a/code/datums/ai/basic_mobs/generic_controllers.dm +++ b/code/datums/ai/basic_mobs/generic_controllers.dm @@ -5,138 +5,121 @@ ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk + behavior_tree_json = ABSTRACT_AI_CLASS + + +/datum/bt_node/subtree/simple_hostile_combat + behavior_tree_json = "code/datums/ai/basic_mobs/simple_hostile_combat.bt.json" + -/// The most basic AI tree which just finds a guy and then runs at them to click them /datum/ai_controller/basic_controller/simple/simple_hostile - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) + behavior_tree_json = "code/datums/ai/basic_mobs/simple_hostile.bt.json" + + +/datum/bt_node/subtree/simple_ranged_combat + behavior_tree_json = "code/datums/ai/basic_mobs/simple_ranged_combat.bt.json" + +/datum/bt_node/subtree/simple_ranged_retaliate_combat + behavior_tree_json = "code/datums/ai/basic_mobs/simple_ranged_retaliate_combat.bt.json" + + +/datum/bt_node/subtree/simple_skirmisher_combat + behavior_tree_json = "code/datums/ai/basic_mobs/simple_skirmisher_combat.bt.json" + +/datum/bt_node/subtree/simple_ability_combat + behavior_tree_json = "code/datums/ai/basic_mobs/simple_ability_combat.bt.json" + +/datum/bt_node/subtree/simple_ability_retaliate_combat + behavior_tree_json = "code/datums/ai/basic_mobs/simple_ability_retaliate_combat.bt.json" + +/datum/bt_node/subtree/simple_ability_melee_combat + behavior_tree_json = "code/datums/ai/basic_mobs/simple_ability_melee_combat.bt.json" + +/datum/bt_node/subtree/simple_ability_ranged_combat + behavior_tree_json = "code/datums/ai/basic_mobs/simple_ability_ranged_combat.bt.json" + + +/datum/bt_node/subtree/simple_retaliate_combat + behavior_tree_json = "code/datums/ai/basic_mobs/simple_retaliate_combat.bt.json" + +/datum/bt_node/subtree/simple_capricious_combat + behavior_tree_json = "code/datums/ai/basic_mobs/simple_capricious_combat.bt.json" + +/datum/bt_node/subtree/simple_fearful_combat + behavior_tree_json = "code/datums/ai/basic_mobs/simple_fearful_combat.bt.json" + +/datum/bt_node/subtree/simple_skittish_combat + behavior_tree_json = "code/datums/ai/basic_mobs/simple_skittish_combat.bt.json" + +/datum/bt_node/subtree/simple_hostile_obstacles_combat + behavior_tree_json = "code/datums/ai/basic_mobs/simple_hostile_obstacles_combat.bt.json" + + /// Find a target, walk at target, attack intervening obstacles /datum/ai_controller/basic_controller/simple/simple_hostile_obstacles - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) + behavior_tree_json = "code/datums/ai/basic_mobs/simple_hostile_obstacles.bt.json" -/// Find a target, walk at target, attack intervening obstacles +/// Find a target, maintain distance, shoot them /datum/ai_controller/basic_controller/simple/simple_ranged - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/maintain_distance, - /datum/ai_planning_subtree/ranged_skirmish, - ) + behavior_tree_json = "code/datums/ai/basic_mobs/simple_ranged.bt.json" /datum/ai_controller/basic_controller/simple/simple_ranged_retaliate - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/target_retaliate, - /datum/ai_planning_subtree/maintain_distance, - /datum/ai_planning_subtree/ranged_skirmish, - ) + behavior_tree_json = "code/datums/ai/basic_mobs/simple_ranged_retaliate.bt.json" /// Find a target, walk towards it AND shoot it /datum/ai_controller/basic_controller/simple/simple_skirmisher - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/ranged_skirmish, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) + behavior_tree_json = "code/datums/ai/basic_mobs/simple_skirmisher.bt.json" /// Use an ability on target on cooldown /datum/ai_controller/basic_controller/simple/simple_ability - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/maintain_distance, - /datum/ai_planning_subtree/targeted_mob_ability, - ) + behavior_tree_json = "code/datums/ai/basic_mobs/simple_ability.bt.json" /datum/ai_controller/basic_controller/simple/simple_ability_retaliate - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/target_retaliate, - /datum/ai_planning_subtree/maintain_distance, - /datum/ai_planning_subtree/targeted_mob_ability, - ) + behavior_tree_json = "code/datums/ai/basic_mobs/simple_ability_retaliate.bt.json" /// Use an ability on target on cooldown, then try to punch them /datum/ai_controller/basic_controller/simple/simple_ability_melee - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/targeted_mob_ability, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) + behavior_tree_json = "code/datums/ai/basic_mobs/simple_ability_melee.bt.json" /// Use an ability on target on cooldown, then try to shoot them /datum/ai_controller/basic_controller/simple/simple_ability_ranged - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/maintain_distance, - /datum/ai_planning_subtree/targeted_mob_ability, - /datum/ai_planning_subtree/ranged_skirmish, - ) + behavior_tree_json = "code/datums/ai/basic_mobs/simple_ability_ranged.bt.json" /// Fight back if attacked /datum/ai_controller/basic_controller/simple/simple_retaliate + behavior_tree_json = "code/datums/ai/basic_mobs/simple_retaliate.bt.json" ai_traits = DEFAULT_AI_FLAGS | STOP_MOVING_WHEN_PULLED - planning_subtrees = list( - /datum/ai_planning_subtree/target_retaliate, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) /// Get pissed at random people for no reason /datum/ai_controller/basic_controller/simple/simple_capricious + behavior_tree_json = "code/datums/ai/basic_mobs/simple_capricious.bt.json" ai_traits = DEFAULT_AI_FLAGS | STOP_MOVING_WHEN_PULLED - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/capricious_retaliate, - /datum/ai_planning_subtree/target_retaliate, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) /// Runs away from anyone it sees /datum/ai_controller/basic_controller/simple/simple_fearful + behavior_tree_json = "code/datums/ai/basic_mobs/simple_fearful.bt.json" ai_traits = PASSIVE_AI_FLAGS - planning_subtrees = list( - /datum/ai_planning_subtree/simple_find_nearest_target_to_flee, - /datum/ai_planning_subtree/flee_target, - ) /// Runs away when attacked /datum/ai_controller/basic_controller/simple/simple_skittish + behavior_tree_json = "code/datums/ai/basic_mobs/simple_skittish.bt.json" ai_traits = PASSIVE_AI_FLAGS - planning_subtrees = list( - /datum/ai_planning_subtree/find_nearest_thing_which_attacked_me_to_flee, - /datum/ai_planning_subtree/flee_target, - ) /// Does what it is told and protects da boss +/// TODO: port pet command system to BT so pet_planning functions correctly /datum/ai_controller/basic_controller/simple/simple_goon + behavior_tree_json = "code/datums/ai/basic_mobs/simple_goon.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/not_friends, BB_PET_TARGETING_STRATEGY = /datum/targeting_strategy/basic/not_friends, ) - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/pet_planning, - ) -/// Literally does nothing except random speedh +/// Literally does nothing except random speech /datum/ai_controller/basic_controller/talk - idle_behavior = null - planning_subtrees = list( - /datum/ai_planning_subtree/random_speech/blackboard, - ) + behavior_tree_json = "code/datums/ai/basic_mobs/talk.bt.json" + + +/datum/bt_node/subtree/simple_hostile_combat_with_retaliate + behavior_tree_json = "code/datums/ai/basic_mobs/simple_hostile_combat_with_retaliate.bt.json" diff --git a/code/datums/ai/basic_mobs/pet_commands/fetch.dm b/code/datums/ai/basic_mobs/pet_commands/fetch.dm deleted file mode 100644 index 5ff208560d2..00000000000 --- a/code/datums/ai/basic_mobs/pet_commands/fetch.dm +++ /dev/null @@ -1,143 +0,0 @@ -/** - * Traverse to a target with the intention of picking it up. - * If we can't do that, add it to a list of ignored items. - */ -/datum/ai_behavior/fetch_seek - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT|AI_BEHAVIOR_REQUIRE_REACH - -/datum/ai_behavior/fetch_seek/setup(datum/ai_controller/controller, target_key, delivery_key) - . = ..() - var/obj/item/fetch_thing = controller.blackboard[target_key] - // It stopped existing - if (QDELETED(fetch_thing)) - return FALSE - set_movement_target(controller, fetch_thing) - -/datum/ai_behavior/fetch_seek/perform(seconds_per_tick, datum/ai_controller/controller, target_key, delivery_key) - var/obj/item/fetch_thing = controller.blackboard[target_key] - - // It stopped existing - if (QDELETED(fetch_thing)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - // We can't pick this up - if (fetch_thing.anchored) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -/datum/ai_behavior/fetch_seek/finish_action(datum/ai_controller/controller, success, target_key, delivery_key) - . = ..() - if (success) - return - // Blacklist item if we failed - var/obj/item/target = controller.blackboard[target_key] - if (target) - controller.set_blackboard_key_assoc_lazylist(BB_FETCH_IGNORE_LIST, target, TRUE) - controller.clear_blackboard_key(target_key) - controller.clear_blackboard_key(delivery_key) - -/** - * The second half of fetching, deliver the item to a target. - */ -/datum/ai_behavior/deliver_fetched_item - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT|AI_BEHAVIOR_REQUIRE_REACH - -/datum/ai_behavior/deliver_fetched_item/setup(datum/ai_controller/controller, delivery_key, storage_key) - . = ..() - var/mob/living/return_target = controller.blackboard[delivery_key] - if(QDELETED(return_target)) // Guess it's mine now - return FALSE - set_movement_target(controller, return_target) - -/datum/ai_behavior/deliver_fetched_item/perform(seconds_per_tick, datum/ai_controller/controller, delivery_key, storage_key) - var/mob/living/return_target = controller.blackboard[delivery_key] - if(QDELETED(return_target)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - if(!deliver_item(controller, return_target, storage_key)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -/datum/ai_behavior/deliver_fetched_item/finish_action(datum/ai_controller/controller, success, delivery_key) - . = ..() - controller.clear_blackboard_key(delivery_key) - controller.clear_blackboard_key(BB_ACTIVE_PET_COMMAND) - -/// Actually deliver the fetched item to the target, if we still have it -/// Returns TRUE if we succeeded, FALSE if we failed -/datum/ai_behavior/deliver_fetched_item/proc/deliver_item(datum/ai_controller/controller, return_target, storage_key) - var/mob/pawn = controller.pawn - var/obj/item/carried_item = controller.blackboard[storage_key] - if(QDELETED(carried_item) || carried_item.loc != pawn) - pawn.visible_message(span_notice("[pawn] looks around as if [pawn.p_they()] [pawn.p_have()] lost something.")) - return FALSE - - pawn.visible_message(span_notice("[pawn] delivers [carried_item] to [return_target].")) - carried_item.forceMove(get_turf(return_target)) - controller.clear_blackboard_key(storage_key) - return TRUE - -/** - * The alternate second half of fetching, attack the item if we can eat it. - * Or make pleading eyes at someone who has picked it up. - * - * Unfortunately this doesn't work because food can't currently be eaten by mobs. - */ -/datum/ai_behavior/eat_fetched_snack - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT - action_cooldown = 0.8 SECONDS - -/datum/ai_behavior/eat_fetched_snack/setup(datum/ai_controller/controller, target_key, delivery_key) - . = ..() - var/obj/item/snack = controller.blackboard[target_key] - if(!istype(snack) || !IS_EDIBLE(snack) || !(isturf(snack.loc) || ishuman(snack.loc))) - return FALSE // This isn't food at all! - set_movement_target(controller, snack) - -/datum/ai_behavior/eat_fetched_snack/perform(seconds_per_tick, datum/ai_controller/controller, target_key, delivery_key) - var/obj/item/snack = controller.blackboard[target_key] - var/is_living_loc = isliving(snack.loc) - if(QDELETED(snack) || (!isturf(snack.loc) && !is_living_loc)) - // Where did it go? - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - var/mob/living/basic/basic_pawn = controller.pawn - if(is_living_loc) - if(SPT_PROB(10, seconds_per_tick)) - basic_pawn.manual_emote("Stares at [snack.loc]'s [snack.name] intently.") - return AI_BEHAVIOR_DELAY - - if(!basic_pawn.Adjacent(snack)) - return AI_BEHAVIOR_DELAY - - controller.ai_interact(target = snack) - - if(QDELETED(snack)) // we ate it! - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - return AI_BEHAVIOR_DELAY - -/datum/ai_behavior/eat_fetched_snack/finish_action(datum/ai_controller/controller, succeeded, target_key, delivery_key) - . = ..() - controller.clear_blackboard_key(target_key) - controller.clear_blackboard_key(delivery_key) - controller.clear_blackboard_key(BB_ACTIVE_PET_COMMAND) - -/** - * Clear our failed fetch list every so often - */ -/datum/ai_behavior/forget_failed_fetches - /// How long to wait between resetting the list - var/cooldown_duration = AI_FETCH_IGNORE_DURATION - /// Time until we should forget things we failed to pick up - COOLDOWN_DECLARE(reset_ignore_cooldown) - -/datum/ai_behavior/forget_failed_fetches/setup(datum/ai_controller/controller, ...) - . = ..() - if (!COOLDOWN_FINISHED(src, reset_ignore_cooldown)) - return FALSE - if (!length(controller.blackboard[BB_FETCH_IGNORE_LIST])) - return - -/datum/ai_behavior/forget_failed_fetches/perform(seconds_per_tick, datum/ai_controller/controller) - COOLDOWN_START(src, reset_ignore_cooldown, cooldown_duration) - controller.clear_blackboard_key(BB_FETCH_IGNORE_LIST) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED diff --git a/code/datums/ai/basic_mobs/pet_commands/pet_command_attack.bt.json b/code/datums/ai/basic_mobs/pet_commands/pet_command_attack.bt.json new file mode 100644 index 00000000000..ca4446c07f7 --- /dev/null +++ b/code/datums/ai/basic_mobs/pet_commands/pet_command_attack.bt.json @@ -0,0 +1,48 @@ +{ + "dm_type": "/datum/bt_node/subtree/pet_command/attack", + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CURRENT_PET_TARGET", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_PET_TARGET", + "targeting_strategy": "BB_PET_TARGETING_STRATEGY", + "hiding_location_key": "BB_PET_ATTACK_HIDING_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_PET_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + } + ] +} diff --git a/code/datums/ai/basic_mobs/pet_commands/pet_command_attack_dog.bt.json b/code/datums/ai/basic_mobs/pet_commands/pet_command_attack_dog.bt.json new file mode 100644 index 00000000000..c8aa95fd452 --- /dev/null +++ b/code/datums/ai/basic_mobs/pet_commands/pet_command_attack_dog.bt.json @@ -0,0 +1,48 @@ +{ + "dm_type": "/datum/bt_node/subtree/pet_command/attack/dog", + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CURRENT_PET_TARGET", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack/dog", + "vars": { + "target_key": "BB_CURRENT_PET_TARGET", + "targeting_strategy": "BB_PET_TARGETING_STRATEGY", + "hiding_location_key": "BB_PET_ATTACK_HIDING_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_PET_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + } + ] +} diff --git a/code/datums/ai/basic_mobs/pet_commands/pet_command_attack_minebot.bt.json b/code/datums/ai/basic_mobs/pet_commands/pet_command_attack_minebot.bt.json new file mode 100644 index 00000000000..c1c6ab6b77a --- /dev/null +++ b/code/datums/ai/basic_mobs/pet_commands/pet_command_attack_minebot.bt.json @@ -0,0 +1,48 @@ +{ + "dm_type": "/datum/bt_node/subtree/pet_command/attack/minebot", + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CURRENT_PET_TARGET", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_ranged_attack/minebot", + "vars": { + "target_key": "BB_CURRENT_PET_TARGET", + "targeting_strategy": "BB_PET_TARGETING_STRATEGY", + "hiding_location_key": "BB_PET_ATTACK_HIDING_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_PET_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + } + ] +} diff --git a/code/datums/ai/basic_mobs/pet_commands/pet_command_attack_ranged_glockroach.bt.json b/code/datums/ai/basic_mobs/pet_commands/pet_command_attack_ranged_glockroach.bt.json new file mode 100644 index 00000000000..dd8ff0b694a --- /dev/null +++ b/code/datums/ai/basic_mobs/pet_commands/pet_command_attack_ranged_glockroach.bt.json @@ -0,0 +1,46 @@ +{ + "dm_type": "/datum/bt_node/subtree/pet_command/attack/ranged/glockroach", + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CURRENT_PET_TARGET", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_ranged_attack/glockroach", + "vars": { + "target_key": "BB_CURRENT_PET_TARGET", + "targeting_strategy": "BB_PET_TARGETING_STRATEGY", + "hiding_location_key": "BB_PET_ATTACK_HIDING_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/maintain_distance", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + } + ] + } + } + ] +} diff --git a/code/datums/ai/basic_mobs/pet_commands/pet_command_beehive.bt.json b/code/datums/ai/basic_mobs/pet_commands/pet_command_beehive.bt.json new file mode 100644 index 00000000000..d5344983e85 --- /dev/null +++ b/code/datums/ai/basic_mobs/pet_commands/pet_command_beehive.bt.json @@ -0,0 +1,23 @@ +{ + "dm_type": "/datum/bt_node/subtree/pet_command/beehive", + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_HOME", + "required_dist": 1, + "finish_on_arrival": false + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/enter_exit_hive" + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/clear_pet_command" + } + ] +} diff --git a/code/datums/ai/basic_mobs/pet_commands/pet_command_breed.bt.json b/code/datums/ai/basic_mobs/pet_commands/pet_command_breed.bt.json new file mode 100644 index 00000000000..18d2ef34057 --- /dev/null +++ b/code/datums/ai/basic_mobs/pet_commands/pet_command_breed.bt.json @@ -0,0 +1,64 @@ +{ + "dm_type": "/datum/bt_node/subtree/pet_command/breed", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CURRENT_PET_TARGET", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/key_in_typelist", + "vars": { + "key": "BB_CURRENT_PET_TARGET", + "typelist_key": "BB_BABIES_PARTNER_TYPES" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_PET_TARGET", + "required_dist": 1, + "finish_on_arrival": true + } + }, + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/keys_different_gender", + "vars": { + "invert": true, + "key_a": "BB_MY_PAWN", + "key_b": "BB_CURRENT_PET_TARGET" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/perform_emote", + "vars": { + "emote": "Seems confused" + } + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/ai_interact", + "vars": { + "target_key": "BB_CURRENT_PET_TARGET", + "combat_mode": false + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/clear_pet_command" + } + ] + } + } +} diff --git a/code/datums/ai/basic_mobs/pet_commands/pet_command_bt.dm b/code/datums/ai/basic_mobs/pet_commands/pet_command_bt.dm new file mode 100644 index 00000000000..2654e04ada8 --- /dev/null +++ b/code/datums/ai/basic_mobs/pet_commands/pet_command_bt.dm @@ -0,0 +1,128 @@ +// Pet-command-specific BT behaviors and override subtrees. +// Generic leaf behaviors (wait, play_dead, pick_up_item_virtual, pass_item_virtual, ai_interact) live in basic_ai_behaviors/. + +/// Validates a protect_owner target; clears command + target if invalid. +/datum/bt_node/ai_behavior/protect_owner_check + +/datum/bt_node/ai_behavior/protect_owner_check/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/victim = controller.blackboard[BB_CURRENT_PET_TARGET] + if(QDELETED(victim)) + controller.clear_blackboard_key(BB_ACTIVE_PET_COMMAND) + controller.clear_blackboard_key(BB_CURRENT_PET_TARGET) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + var/datum/targeting_strategy/targeter = GET_TARGETING_STRATEGY(controller.blackboard[BB_PET_TARGETING_STRATEGY]) + if(!targeter?.is_valid_target(controller.pawn, victim)) + controller.clear_blackboard_key(BB_ACTIVE_PET_COMMAND) + controller.clear_blackboard_key(BB_CURRENT_PET_TARGET) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + var/minimum_stat = controller.blackboard[BB_TARGET_MINIMUM_STAT] + if((!isnull(minimum_stat) && victim.stat > minimum_stat) || victim == controller.pawn) + controller.clear_blackboard_key(BB_ACTIVE_PET_COMMAND) + controller.clear_blackboard_key(BB_CURRENT_PET_TARGET) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED + +/// Validates a fetch item at target_key; adds to ignore list and clears keys on failure. +/datum/bt_node/ai_behavior/fetch_seek + var/target_key + +/datum/bt_node/ai_behavior/fetch_seek/setup(datum/ai_controller/controller) + var/obj/item/target = controller.blackboard[target_key] + return !QDELETED(target) + +/datum/bt_node/ai_behavior/fetch_seek/perform(seconds_per_tick, datum/ai_controller/controller) + var/obj/item/fetch_thing = controller.blackboard[target_key] + if(QDELETED(fetch_thing)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + if(fetch_thing.anchored) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + +/datum/bt_node/ai_behavior/fetch_seek/finish_action(datum/ai_controller/controller, succeeded) + . = ..() + if(succeeded) + return + var/obj/item/target = controller.blackboard[target_key] + if(target) + controller.set_blackboard_key_assoc_lazylist(BB_FETCH_IGNORE_LIST, target, TRUE) + controller.clear_blackboard_key(target_key) + controller.clear_blackboard_key(BB_FETCH_DELIVER_TO) + +/// Clears the fetch ignore list at most once per AI_FETCH_IGNORE_DURATION. Always succeeds. +/datum/bt_node/ai_behavior/forget_failed_fetches + COOLDOWN_DECLARE(clear_cooldown) + +/datum/bt_node/ai_behavior/forget_failed_fetches/perform(seconds_per_tick, datum/ai_controller/controller) + if(COOLDOWN_FINISHED(src, clear_cooldown) && LAZYLEN(controller.blackboard[BB_FETCH_IGNORE_LIST])) + COOLDOWN_START(src, clear_cooldown, AI_FETCH_IGNORE_DURATION) + controller.clear_blackboard_key(BB_FETCH_IGNORE_LIST) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED + +/// Clears BB_ACTIVE_PET_COMMAND and removes the SUBPLAN_ID_PET_COMMAND override. +/datum/bt_node/ai_behavior/clear_pet_command + +/datum/bt_node/ai_behavior/clear_pet_command/perform(seconds_per_tick, datum/ai_controller/controller) + controller.clear_blackboard_key(BB_ACTIVE_PET_COMMAND) + controller.set_behavior_tree_override(SUBPLAN_ID_PET_COMMAND, null) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED + + +/// Waits forever; blocks normal AI while stay/idle is active. +/datum/bt_node/subtree/pet_command/stay + behavior_tree_json = "code/datums/ai/basic_mobs/pet_commands/pet_command_stay.bt.json" + +/// Loops move_to_target toward BB_CURRENT_PET_TARGET until the key is cleared. +/datum/bt_node/subtree/pet_command/follow + behavior_tree_json = "code/datums/ai/basic_mobs/pet_commands/pet_command_follow.bt.json" + +/// Plays dead (10%/tick to get up). Clears command on revival. +/datum/bt_node/subtree/pet_command/play_dead + behavior_tree_json = "code/datums/ai/basic_mobs/pet_commands/pet_command_play_dead.bt.json" + +/// Attacks BB_CURRENT_PET_TARGET in a looping melee combat parallel. +/datum/bt_node/subtree/pet_command/attack + behavior_tree_json = "code/datums/ai/basic_mobs/pet_commands/pet_command_attack.bt.json" + +/// Protect owner: loops a validity check then melee attack. Clears command if target invalid. +/datum/bt_node/subtree/pet_command/protect_owner + behavior_tree_json = "code/datums/ai/basic_mobs/pet_commands/pet_command_protect_owner.bt.json" + +/// Travels to BB_CURRENT_PET_TARGET, clears command on arrival. +/datum/bt_node/subtree/pet_command/move_to + behavior_tree_json = "code/datums/ai/basic_mobs/pet_commands/pet_command_move_to.bt.json" + +/// Moves to BB_CURRENT_PET_TARGET and fishes there on a loop. +/datum/bt_node/subtree/pet_command/fish + behavior_tree_json = "code/datums/ai/basic_mobs/pet_commands/pet_command_fish.bt.json" + +/// Moves to BB_CURRENT_PET_TARGET and breeds once. Clears command on completion. +/datum/bt_node/subtree/pet_command/breed + behavior_tree_json = "code/datums/ai/basic_mobs/pet_commands/pet_command_breed.bt.json" + +/// Moves to BB_CURRENT_PET_TARGET and fires BB_TARGETED_ACTION on it once. +/datum/bt_node/subtree/pet_command/targeted_ability + behavior_tree_json = "code/datums/ai/basic_mobs/pet_commands/pet_command_targeted_ability.bt.json" + +/// Fires the ability stored in BB_PET_ACTIVE_ABILITY once (untargeted). +/datum/bt_node/subtree/pet_command/untargeted_ability + behavior_tree_json = "code/datums/ai/basic_mobs/pet_commands/pet_command_untargeted_ability.bt.json" + +/// Fetch: seek > pick up > deliver. Falls back to clear_pet_command if nothing to do. +/datum/bt_node/subtree/pet_command/fetch + behavior_tree_json = "code/datums/ai/basic_mobs/pet_commands/pet_command_fetch.bt.json" + +/// Attacks BB_CURRENT_PET_TARGET using the dog's melee behavior (paws if BB_DOG_HARASS_HARM is false, bites otherwise). +/datum/bt_node/subtree/pet_command/attack/dog + behavior_tree_json = "code/datums/ai/basic_mobs/pet_commands/pet_command_attack_dog.bt.json" + +/// Attacks BB_CURRENT_PET_TARGET with glockroach ranged attack (1s cooldown). +/datum/bt_node/subtree/pet_command/attack/ranged/glockroach + behavior_tree_json = "code/datums/ai/basic_mobs/pet_commands/pet_command_attack_ranged_glockroach.bt.json" + +/// Attacks BB_CURRENT_PET_TARGET with minebot ranged attack (avoids friendly fire). +/datum/bt_node/subtree/pet_command/attack/minebot + behavior_tree_json = "code/datums/ai/basic_mobs/pet_commands/pet_command_attack_minebot.bt.json" + +/// Protect owner: loops validity check then glockroach ranged attack. Clears command if target invalid. +/datum/bt_node/subtree/pet_command/protect_owner/ranged/glockroach + behavior_tree_json = "code/datums/ai/basic_mobs/pet_commands/pet_command_protect_owner_ranged_glockroach.bt.json" diff --git a/code/datums/ai/basic_mobs/pet_commands/pet_command_fetch.bt.json b/code/datums/ai/basic_mobs/pet_commands/pet_command_fetch.bt.json new file mode 100644 index 00000000000..c6ef1ab9177 --- /dev/null +++ b/code/datums/ai/basic_mobs/pet_commands/pet_command_fetch.bt.json @@ -0,0 +1,107 @@ +{ + "dm_type": "/datum/bt_node/subtree/pet_command/fetch", + "type": "selector", + "children": [ + { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/forget_failed_fetches" + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CURRENT_PET_TARGET", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_PET_TARGET", + "required_dist": 1, + "finish_on_arrival": true + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/fetch_seek", + "vars": { + "target_key": "BB_CURRENT_PET_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/pick_up_item_virtual", + "vars": { + "target_key": "BB_CURRENT_PET_TARGET", + "storage_key": "BB_SIMPLE_CARRY_ITEM" + } + } + ] + } + } + ] + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_SIMPLE_CARRY_ITEM", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_FETCH_DELIVER_TO", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_FETCH_DELIVER_TO", + "required_dist": 1, + "finish_on_arrival": true + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/pass_item_virtual", + "vars": { + "delivery_key": "BB_FETCH_DELIVER_TO", + "storage_key": "BB_SIMPLE_CARRY_ITEM" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/clear_pet_command" + } + ] + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "invert": true, + "key": "BB_CURRENT_PET_TARGET" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/wait" + } + } + ] +} diff --git a/code/datums/ai/basic_mobs/pet_commands/pet_command_fish.bt.json b/code/datums/ai/basic_mobs/pet_commands/pet_command_fish.bt.json new file mode 100644 index 00000000000..62e0d24abe7 --- /dev/null +++ b/code/datums/ai/basic_mobs/pet_commands/pet_command_fish.bt.json @@ -0,0 +1,38 @@ +{ + "dm_type": "/datum/bt_node/subtree/pet_command/fish", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CURRENT_PET_TARGET", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_FAIL_ON_FAILURE", + "children": [ + { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_PET_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/ai_interact", + "vars": { + "target_key": "BB_CURRENT_PET_TARGET", + "combat_mode": false + } + } + ] + } + ] + } +} diff --git a/code/datums/ai/basic_mobs/pet_commands/pet_command_follow.bt.json b/code/datums/ai/basic_mobs/pet_commands/pet_command_follow.bt.json new file mode 100644 index 00000000000..9a70605a7ae --- /dev/null +++ b/code/datums/ai/basic_mobs/pet_commands/pet_command_follow.bt.json @@ -0,0 +1,25 @@ +{ + "dm_type": "/datum/bt_node/subtree/pet_command/follow", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CURRENT_PET_TARGET", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_FAIL_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_PET_TARGET", + "required_dist": 1, + "finish_on_arrival": true + } + } + ] + } +} diff --git a/code/datums/ai/basic_mobs/pet_commands/pet_command_mine_walls.bt.json b/code/datums/ai/basic_mobs/pet_commands/pet_command_mine_walls.bt.json new file mode 100644 index 00000000000..f7a76f638f6 --- /dev/null +++ b/code/datums/ai/basic_mobs/pet_commands/pet_command_mine_walls.bt.json @@ -0,0 +1,46 @@ +{ + "dm_type": "/datum/bt_node/subtree/pet_command/mine_walls", + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CURRENT_PET_TARGET", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_PET_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/mine_wall", + "vars": { + "target_key": "BB_CURRENT_PET_TARGET" + } + } + ] + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/find_mineral_wall", + "vars": { + "target_key": "BB_CURRENT_PET_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/clear_pet_command" + } + ] +} diff --git a/code/datums/ai/basic_mobs/pet_commands/pet_command_move_to.bt.json b/code/datums/ai/basic_mobs/pet_commands/pet_command_move_to.bt.json new file mode 100644 index 00000000000..98fd4fd007e --- /dev/null +++ b/code/datums/ai/basic_mobs/pet_commands/pet_command_move_to.bt.json @@ -0,0 +1,27 @@ +{ + "dm_type": "/datum/bt_node/subtree/pet_command/move_to", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CURRENT_PET_TARGET", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_PET_TARGET", + "required_dist": 0, + "finish_on_arrival": true + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/clear_pet_command" + } + ] + } +} diff --git a/code/datums/ai/basic_mobs/pet_commands/pet_command_planning.dm b/code/datums/ai/basic_mobs/pet_commands/pet_command_planning.dm deleted file mode 100644 index bf4885b817b..00000000000 --- a/code/datums/ai/basic_mobs/pet_commands/pet_command_planning.dm +++ /dev/null @@ -1,14 +0,0 @@ -/** - * # Pet Planning - * Perform behaviour based on what pet commands you have received. This is delegated to the pet command datum. - * When a command is set, we blackboard a key to our currently active command. - * The blackboard also has a weak reference to every command datum available to us. - * We use the key to figure out which datum to run, then ask it to figure out how to execute its action. - */ -/datum/ai_planning_subtree/pet_planning - -/datum/ai_planning_subtree/pet_planning/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/datum/pet_command/command = controller.blackboard[BB_ACTIVE_PET_COMMAND] - if (!command) - return // Do something else - return command.execute_action(controller) diff --git a/code/datums/ai/basic_mobs/pet_commands/pet_command_play_dead.bt.json b/code/datums/ai/basic_mobs/pet_commands/pet_command_play_dead.bt.json new file mode 100644 index 00000000000..c7a182918b3 --- /dev/null +++ b/code/datums/ai/basic_mobs/pet_commands/pet_command_play_dead.bt.json @@ -0,0 +1,14 @@ +{ + "dm_type": "/datum/bt_node/subtree/pet_command/play_dead", + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/play_dead" + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/clear_pet_command" + } + ] +} diff --git a/code/datums/ai/basic_mobs/pet_commands/pet_command_protect_owner.bt.json b/code/datums/ai/basic_mobs/pet_commands/pet_command_protect_owner.bt.json new file mode 100644 index 00000000000..3c9f545eb66 --- /dev/null +++ b/code/datums/ai/basic_mobs/pet_commands/pet_command_protect_owner.bt.json @@ -0,0 +1,57 @@ +{ + "dm_type": "/datum/bt_node/subtree/pet_command/protect_owner", + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CURRENT_PET_TARGET", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/protect_owner_check" + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_PET_TARGET", + "targeting_strategy": "BB_PET_TARGETING_STRATEGY", + "hiding_location_key": "BB_PET_ATTACK_HIDING_LOCATION" + } + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_PET_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + } + ] +} diff --git a/code/datums/ai/basic_mobs/pet_commands/pet_command_protect_owner_ranged_glockroach.bt.json b/code/datums/ai/basic_mobs/pet_commands/pet_command_protect_owner_ranged_glockroach.bt.json new file mode 100644 index 00000000000..b8cdd00f02f --- /dev/null +++ b/code/datums/ai/basic_mobs/pet_commands/pet_command_protect_owner_ranged_glockroach.bt.json @@ -0,0 +1,55 @@ +{ + "dm_type": "/datum/bt_node/subtree/pet_command/protect_owner/ranged/glockroach", + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CURRENT_PET_TARGET", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/protect_owner_check" + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_ranged_attack/glockroach", + "vars": { + "target_key": "BB_CURRENT_PET_TARGET", + "targeting_strategy": "BB_PET_TARGETING_STRATEGY", + "hiding_location_key": "BB_PET_ATTACK_HIDING_LOCATION" + } + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/maintain_distance", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + } + ] + } + } + ] +} diff --git a/code/datums/ai/basic_mobs/pet_commands/pet_command_scatter.bt.json b/code/datums/ai/basic_mobs/pet_commands/pet_command_scatter.bt.json new file mode 100644 index 00000000000..d3d3517b2b7 --- /dev/null +++ b/code/datums/ai/basic_mobs/pet_commands/pet_command_scatter.bt.json @@ -0,0 +1,17 @@ +{ + "dm_type": "/datum/bt_node/subtree/pet_command/scatter", + "type": "sequence", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/run_away_from_target", + "bindings": { + "byk9gqj4": "BB_CURRENT_PET_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/clear_pet_command" + } + ] +} diff --git a/code/datums/ai/basic_mobs/pet_commands/pet_command_stay.bt.json b/code/datums/ai/basic_mobs/pet_commands/pet_command_stay.bt.json new file mode 100644 index 00000000000..d374016e90c --- /dev/null +++ b/code/datums/ai/basic_mobs/pet_commands/pet_command_stay.bt.json @@ -0,0 +1,8 @@ +{ + "dm_type": "/datum/bt_node/subtree/pet_command/stay", + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/wait", + "vars": { + "duration": 0 + } +} diff --git a/code/datums/ai/basic_mobs/pet_commands/pet_command_swirl.bt.json b/code/datums/ai/basic_mobs/pet_commands/pet_command_swirl.bt.json new file mode 100644 index 00000000000..baf9fc2e7d4 --- /dev/null +++ b/code/datums/ai/basic_mobs/pet_commands/pet_command_swirl.bt.json @@ -0,0 +1,81 @@ +{ + "dm_type": "/datum/bt_node/subtree/pet_command/swirl", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_SWARM_TARGET", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CURRENT_PET_TARGET", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_PET_TARGET", + "targeting_strategy": "BB_PET_TARGETING_STRATEGY", + "hiding_location_key": "BB_PET_ATTACK_HIDING_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_PET_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/swirl_around_target" + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_SWIRL_TURF", + "required_dist": 0, + "finish_on_arrival": true + } + } + ] + } + ] + } + ] + } +} diff --git a/code/datums/ai/basic_mobs/pet_commands/pet_command_targeted_ability.bt.json b/code/datums/ai/basic_mobs/pet_commands/pet_command_targeted_ability.bt.json new file mode 100644 index 00000000000..aa3b6b5b936 --- /dev/null +++ b/code/datums/ai/basic_mobs/pet_commands/pet_command_targeted_ability.bt.json @@ -0,0 +1,51 @@ +{ + "dm_type": "/datum/bt_node/subtree/pet_command/targeted_ability", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CURRENT_PET_TARGET", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability", + "vars": { + "ability_key": "BB_TARGETED_ACTION", + "target_key": "BB_CURRENT_PET_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/clear_pet_command" + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_PET_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } +} diff --git a/code/datums/ai/basic_mobs/pet_commands/pet_command_untargeted_ability.bt.json b/code/datums/ai/basic_mobs/pet_commands/pet_command_untargeted_ability.bt.json new file mode 100644 index 00000000000..a993a27f8e5 --- /dev/null +++ b/code/datums/ai/basic_mobs/pet_commands/pet_command_untargeted_ability.bt.json @@ -0,0 +1,17 @@ +{ + "dm_type": "/datum/bt_node/subtree/pet_command/untargeted_ability", + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/use_mob_ability", + "vars": { + "ability_key": "BB_PET_ACTIVE_ABILITY" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/clear_pet_command" + } + ] +} diff --git a/code/datums/ai/basic_mobs/pet_commands/pet_follow_friend.dm b/code/datums/ai/basic_mobs/pet_commands/pet_follow_friend.dm deleted file mode 100644 index 38a6939c901..00000000000 --- a/code/datums/ai/basic_mobs/pet_commands/pet_follow_friend.dm +++ /dev/null @@ -1,16 +0,0 @@ -/// Just keep following the target until the command is interrupted -/datum/ai_behavior/pet_follow_friend - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_MOVE_AND_PERFORM - -/datum/ai_behavior/pet_follow_friend/setup(datum/ai_controller/controller, target_key) - . = ..() - var/atom/target = controller.blackboard[target_key] - if (QDELETED(target)) - return FALSE - set_movement_target(controller, target) - -/datum/ai_behavior/pet_follow_friend/perform(seconds_per_tick, datum/ai_controller/controller, target_key) - var/atom/target = controller.blackboard[target_key] - if (QDELETED(target)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - return AI_BEHAVIOR_DELAY diff --git a/code/datums/ai/basic_mobs/pet_commands/pet_use_targeted_ability.dm b/code/datums/ai/basic_mobs/pet_commands/pet_use_targeted_ability.dm index c90ffa78590..e69de29bb2d 100644 --- a/code/datums/ai/basic_mobs/pet_commands/pet_use_targeted_ability.dm +++ b/code/datums/ai/basic_mobs/pet_commands/pet_use_targeted_ability.dm @@ -1,39 +0,0 @@ -/// Pet owners can't see their pet's ability cooldowns so we keep attempting to use an ability until we succeed -/datum/ai_behavior/pet_use_ability - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_MOVE_AND_PERFORM - -/datum/ai_behavior/pet_use_ability/setup(datum/ai_controller/controller, ability_key, target_key) - . = ..() - var/mob/living/target = controller.blackboard[target_key] - if (QDELETED(target)) - return FALSE - set_movement_target(controller, target) - -/datum/ai_behavior/pet_use_ability/perform(seconds_per_tick, datum/ai_controller/controller, ability_key, target_key) - var/datum/action/cooldown/mob_cooldown/ability = controller.blackboard[ability_key] - var/mob/living/target = controller.blackboard[target_key] - if (QDELETED(ability) || QDELETED(target)) - return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED - var/mob/pawn = controller.pawn - if(QDELETED(pawn) || ability.InterceptClickOn(pawn, null, target)) - return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED - return AI_BEHAVIOR_INSTANT - -/datum/ai_behavior/pet_use_ability/finish_action(datum/ai_controller/controller, succeeded, ability_key, target_key) - . = ..() - controller.clear_blackboard_key(target_key) - -/datum/ai_behavior/pet_use_ability/then_attack - -/datum/ai_behavior/pet_use_ability/then_attack/finish_action(datum/ai_controller/controller, succeeded, ability_key, target_key) - . = ..() - if(succeeded) - controller.queue_behavior(/datum/ai_behavior/basic_melee_attack, target_key, BB_PET_TARGETING_STRATEGY) - -/datum/ai_behavior/pet_use_ability/then_attack/short_ranged - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT - required_distance = 4 - -/datum/ai_behavior/pet_use_ability/then_attack/long_ranged - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT - required_distance = 8 diff --git a/code/datums/ai/basic_mobs/pet_commands/play_dead.dm b/code/datums/ai/basic_mobs/pet_commands/play_dead.dm deleted file mode 100644 index 5a7a0943026..00000000000 --- a/code/datums/ai/basic_mobs/pet_commands/play_dead.dm +++ /dev/null @@ -1,26 +0,0 @@ -/// Pretend to be dead -/datum/ai_behavior/play_dead - -/datum/ai_behavior/play_dead/setup(datum/ai_controller/controller) - . = ..() - var/mob/living/basic/basic_pawn = controller.pawn - if(!istype(basic_pawn) || basic_pawn.stat) // Can't act dead if you're dead - return - basic_pawn.emote("deathgasp", intentional=FALSE) - ADD_TRAIT(basic_pawn, TRAIT_FAKEDEATH, BASIC_MOB_DEATH_TRAIT) - basic_pawn.look_dead() - -/datum/ai_behavior/play_dead/perform(seconds_per_tick, datum/ai_controller/controller) - if(SPT_PROB(10, seconds_per_tick)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - return AI_BEHAVIOR_DELAY - -/datum/ai_behavior/play_dead/finish_action(datum/ai_controller/controller, succeeded) - . = ..() - var/mob/living/basic/basic_pawn = controller.pawn - if(QDELETED(basic_pawn) || basic_pawn.stat) // imagine actually dying while playing dead. hell, imagine being the kid waiting for your pup to get back up :( - return - basic_pawn.visible_message(span_notice("[basic_pawn] miraculously springs back to life!")) - REMOVE_TRAIT(basic_pawn, TRAIT_FAKEDEATH, BASIC_MOB_DEATH_TRAIT) - basic_pawn.look_alive() - controller.clear_blackboard_key(BB_ACTIVE_PET_COMMAND) diff --git a/code/datums/ai/basic_mobs/simple_ability.bt.json b/code/datums/ai/basic_mobs/simple_ability.bt.json new file mode 100644 index 00000000000..3a741326a40 --- /dev/null +++ b/code/datums/ai/basic_mobs/simple_ability.bt.json @@ -0,0 +1,14 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/simple/simple_ability", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_ability_combat" + } + ] +} diff --git a/code/datums/ai/basic_mobs/simple_ability_combat.bt.json b/code/datums/ai/basic_mobs/simple_ability_combat.bt.json new file mode 100644 index 00000000000..7415f3130c3 --- /dev/null +++ b/code/datums/ai/basic_mobs/simple_ability_combat.bt.json @@ -0,0 +1,101 @@ +{ + "dm_type": "/datum/bt_node/subtree/simple_ability_combat", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "invert": false, + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/cooldown", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "cooldown_key": "BB_WIZARD_SPELL_COOLDOWN", + "cooldown_duration": "WIZARD_SPELL_COOLDOWN" + }, + "child": { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability", + "vars": { + "ability_key": "BB_WIZARD_TARGETED_SPELL", + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability", + "vars": { + "ability_key": "BB_WIZARD_SECONDARY_SPELL", + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability", + "vars": { + "ability_key": "BB_WIZARD_BLINK_SPELL", + "target_key": "BB_CURRENT_TARGET" + } + } + ] + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 3, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] +} diff --git a/code/datums/ai/basic_mobs/simple_ability_melee.bt.json b/code/datums/ai/basic_mobs/simple_ability_melee.bt.json new file mode 100644 index 00000000000..7bb2f68b287 --- /dev/null +++ b/code/datums/ai/basic_mobs/simple_ability_melee.bt.json @@ -0,0 +1,14 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/simple/simple_ability_melee", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_ability_melee_combat" + } + ] +} diff --git a/code/datums/ai/basic_mobs/simple_ability_melee_combat.bt.json b/code/datums/ai/basic_mobs/simple_ability_melee_combat.bt.json new file mode 100644 index 00000000000..4da9075ce8a --- /dev/null +++ b/code/datums/ai/basic_mobs/simple_ability_melee_combat.bt.json @@ -0,0 +1,91 @@ +{ + "dm_type": "/datum/bt_node/subtree/simple_ability_melee_combat", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/attack_obstructions", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability", + "vars": { + "ability_key": "BB_TARGETED_ACTION", + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] +} diff --git a/code/datums/ai/basic_mobs/simple_ability_ranged.bt.json b/code/datums/ai/basic_mobs/simple_ability_ranged.bt.json new file mode 100644 index 00000000000..971a7059245 --- /dev/null +++ b/code/datums/ai/basic_mobs/simple_ability_ranged.bt.json @@ -0,0 +1,14 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/simple/simple_ability_ranged", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_ability_ranged_combat" + } + ] +} diff --git a/code/datums/ai/basic_mobs/simple_ability_ranged_combat.bt.json b/code/datums/ai/basic_mobs/simple_ability_ranged_combat.bt.json new file mode 100644 index 00000000000..78d229fec93 --- /dev/null +++ b/code/datums/ai/basic_mobs/simple_ability_ranged_combat.bt.json @@ -0,0 +1,82 @@ +{ + "dm_type": "/datum/bt_node/subtree/simple_ability_ranged_combat", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability", + "vars": { + "ability_key": "BB_TARGETED_ACTION", + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_ranged_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/maintain_distance", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] +} diff --git a/code/datums/ai/basic_mobs/simple_ability_retaliate.bt.json b/code/datums/ai/basic_mobs/simple_ability_retaliate.bt.json new file mode 100644 index 00000000000..546fb2ed02a --- /dev/null +++ b/code/datums/ai/basic_mobs/simple_ability_retaliate.bt.json @@ -0,0 +1,14 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/simple/simple_ability_retaliate", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_ability_retaliate_combat" + } + ] +} diff --git a/code/datums/ai/basic_mobs/simple_ability_retaliate_combat.bt.json b/code/datums/ai/basic_mobs/simple_ability_retaliate_combat.bt.json new file mode 100644 index 00000000000..9f154848853 --- /dev/null +++ b/code/datums/ai/basic_mobs/simple_ability_retaliate_combat.bt.json @@ -0,0 +1,70 @@ +{ + "dm_type": "/datum/bt_node/subtree/simple_ability_retaliate_combat", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_BASIC_MOB_RETALIATE_LIST" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability", + "vars": { + "ability_key": "BB_TARGETED_ACTION", + "target_key": "BB_CURRENT_TARGET" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 3, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] +} diff --git a/code/datums/ai/basic_mobs/simple_capricious.bt.json b/code/datums/ai/basic_mobs/simple_capricious.bt.json new file mode 100644 index 00000000000..70ba4d20a29 --- /dev/null +++ b/code/datums/ai/basic_mobs/simple_capricious.bt.json @@ -0,0 +1,14 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/simple/simple_capricious", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_capricious_combat" + } + ] +} diff --git a/code/datums/ai/basic_mobs/simple_capricious_combat.bt.json b/code/datums/ai/basic_mobs/simple_capricious_combat.bt.json new file mode 100644 index 00000000000..265658831d8 --- /dev/null +++ b/code/datums/ai/basic_mobs/simple_capricious_combat.bt.json @@ -0,0 +1,66 @@ +{ + "dm_type": "/datum/bt_node/subtree/simple_capricious_combat", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_BASIC_MOB_RETALIATE_LIST" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/capricious_pick_target" + } + ] +} diff --git a/code/datums/ai/basic_mobs/simple_fearful.bt.json b/code/datums/ai/basic_mobs/simple_fearful.bt.json new file mode 100644 index 00000000000..d4f2d92caeb --- /dev/null +++ b/code/datums/ai/basic_mobs/simple_fearful.bt.json @@ -0,0 +1,10 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/simple/simple_fearful", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_fearful_combat" + } + ] +} diff --git a/code/datums/ai/basic_mobs/simple_fearful_combat.bt.json b/code/datums/ai/basic_mobs/simple_fearful_combat.bt.json new file mode 100644 index 00000000000..e4bca3a8614 --- /dev/null +++ b/code/datums/ai/basic_mobs/simple_fearful_combat.bt.json @@ -0,0 +1,41 @@ +{ + "dm_type": "/datum/bt_node/subtree/simple_fearful_combat", + "type": "selector", + "children": [ + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/run_away_from_target" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets/nearest", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] +} diff --git a/code/datums/ai/basic_mobs/simple_goon.bt.json b/code/datums/ai/basic_mobs/simple_goon.bt.json new file mode 100644 index 00000000000..2add30ed087 --- /dev/null +++ b/code/datums/ai/basic_mobs/simple_goon.bt.json @@ -0,0 +1,15 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/simple/simple_goon", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree", + "override_id": "SUBPLAN_ID_PET_COMMAND" + } + ] +} diff --git a/code/datums/ai/basic_mobs/simple_hostile.bt.json b/code/datums/ai/basic_mobs/simple_hostile.bt.json new file mode 100644 index 00000000000..9e4077fd8b9 --- /dev/null +++ b/code/datums/ai/basic_mobs/simple_hostile.bt.json @@ -0,0 +1,14 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/simple/simple_hostile", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_hostile_combat" + } + ] +} diff --git a/code/datums/ai/basic_mobs/simple_hostile_combat.bt.json b/code/datums/ai/basic_mobs/simple_hostile_combat.bt.json new file mode 100644 index 00000000000..53738730793 --- /dev/null +++ b/code/datums/ai/basic_mobs/simple_hostile_combat.bt.json @@ -0,0 +1,98 @@ +{ + "dm_type": "/datum/bt_node/subtree/simple_hostile_combat", + "bindings": { + "bbrbyj7y": { + "label": "combat_speech_behavior", + "default": "" + }, + "bp3p5vvb": { + "label": "walk_chance", + "default": "25" + } + }, + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "1 SECONDS", + "finish_on_primary": true, + "children": [ + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + }, + { + "type": "leaf", + "behavior": "$bbrbyj7y" + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk", + "bindings": { + "bf07i8ep": "$bp3p5vvb" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] +} diff --git a/code/datums/ai/basic_mobs/simple_hostile_combat_with_retaliate.bt.json b/code/datums/ai/basic_mobs/simple_hostile_combat_with_retaliate.bt.json new file mode 100644 index 00000000000..b5a026e01a6 --- /dev/null +++ b/code/datums/ai/basic_mobs/simple_hostile_combat_with_retaliate.bt.json @@ -0,0 +1,107 @@ +{ + "dm_type": "/datum/bt_node/subtree/simple_hostile_combat_with_retaliate", + "bindings": { + "bhvqd9fh": { + "label": "speech loop subtree", + "default": "/datum/bt_node/subtree" + } + }, + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_SUCCEED_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/attack_obstructions", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + }, + { + "type": "subtree", + "subtype": "$bhvqd9fh" + } + ] +} diff --git a/code/datums/ai/basic_mobs/simple_hostile_obstacles.bt.json b/code/datums/ai/basic_mobs/simple_hostile_obstacles.bt.json new file mode 100644 index 00000000000..3ce7b8cac9b --- /dev/null +++ b/code/datums/ai/basic_mobs/simple_hostile_obstacles.bt.json @@ -0,0 +1,49 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/simple/simple_hostile_obstacles", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/run_away_from_target" + }, + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "loop_delay": "1 SECONDS", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack" + } + ] + } + ] + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/pick_retaliate_target" + } + ] + } + ] +} diff --git a/code/datums/ai/basic_mobs/simple_hostile_obstacles_combat.bt.json b/code/datums/ai/basic_mobs/simple_hostile_obstacles_combat.bt.json new file mode 100644 index 00000000000..846e921552a --- /dev/null +++ b/code/datums/ai/basic_mobs/simple_hostile_obstacles_combat.bt.json @@ -0,0 +1,109 @@ +{ + "dm_type": "/datum/bt_node/subtree/simple_hostile_obstacles_combat", + "bindings": { + "b95z0f0c": { + "label": "while_attacking_subtree", + "default": "" + }, + "by235tlw": { + "label": "cant_attack_subtree", + "default": "" + }, + "bqul7l8t": { + "label": "speech_subtree", + "default": "/datum/bt_node/subtree" + } + }, + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_SUCCEED_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/attack_obstructions", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + }, + { + "type": "subtree", + "subtype": "$b95z0f0c" + } + ] + } + }, + { + "type": "subtree", + "subtype": "$by235tlw" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "subtree", + "subtype": "$bqul7l8t" + } + ] +} diff --git a/code/datums/ai/basic_mobs/simple_ranged.bt.json b/code/datums/ai/basic_mobs/simple_ranged.bt.json new file mode 100644 index 00000000000..cc8e48db271 --- /dev/null +++ b/code/datums/ai/basic_mobs/simple_ranged.bt.json @@ -0,0 +1,14 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/simple/simple_ranged", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_ranged_combat" + } + ] +} diff --git a/code/datums/ai/basic_mobs/simple_ranged_combat.bt.json b/code/datums/ai/basic_mobs/simple_ranged_combat.bt.json new file mode 100644 index 00000000000..6da0fd0be51 --- /dev/null +++ b/code/datums/ai/basic_mobs/simple_ranged_combat.bt.json @@ -0,0 +1,77 @@ +{ + "dm_type": "/datum/bt_node/subtree/simple_ranged_combat", + "bindings": { + "bsjjwub2": { + "label": "idle_behavior", + "default": "/datum/bt_node/subtree/random_walk" + } + }, + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_ranged_attack", + "vars": { + "time_between_perform": "0.6 SECONDS", + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/maintain_distance", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "approach_movement_type": "/datum/ai_movement/basic_avoidance" + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "$bsjjwub2" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] +} diff --git a/code/datums/ai/basic_mobs/simple_ranged_retaliate.bt.json b/code/datums/ai/basic_mobs/simple_ranged_retaliate.bt.json new file mode 100644 index 00000000000..92e64b045f1 --- /dev/null +++ b/code/datums/ai/basic_mobs/simple_ranged_retaliate.bt.json @@ -0,0 +1,14 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/simple/simple_ranged_retaliate", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_ranged_retaliate_combat" + } + ] +} diff --git a/code/datums/ai/basic_mobs/simple_ranged_retaliate_combat.bt.json b/code/datums/ai/basic_mobs/simple_ranged_retaliate_combat.bt.json new file mode 100644 index 00000000000..06747f4e9e8 --- /dev/null +++ b/code/datums/ai/basic_mobs/simple_ranged_retaliate_combat.bt.json @@ -0,0 +1,69 @@ +{ + "dm_type": "/datum/bt_node/subtree/simple_ranged_retaliate_combat", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CURRENT_TARGET", + "observer_abort": "BT_ABORT_SELF" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_ranged_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/maintain_distance", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] +} diff --git a/code/datums/ai/basic_mobs/simple_retaliate.bt.json b/code/datums/ai/basic_mobs/simple_retaliate.bt.json new file mode 100644 index 00000000000..1155f237868 --- /dev/null +++ b/code/datums/ai/basic_mobs/simple_retaliate.bt.json @@ -0,0 +1,14 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/simple/simple_retaliate", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_retaliate_combat" + } + ] +} diff --git a/code/datums/ai/basic_mobs/simple_retaliate_combat.bt.json b/code/datums/ai/basic_mobs/simple_retaliate_combat.bt.json new file mode 100644 index 00000000000..105e0ef8b7a --- /dev/null +++ b/code/datums/ai/basic_mobs/simple_retaliate_combat.bt.json @@ -0,0 +1,87 @@ +{ + "dm_type": "/datum/bt_node/subtree/simple_retaliate_combat", + "bindings": { + "b2jvnm5d": { + "label": "check_faction", + "default": "FALSE" + } + }, + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION", + "check_faction": "$b2jvnm5d" + } + } + ] +} diff --git a/code/datums/ai/basic_mobs/simple_skirmisher.bt.json b/code/datums/ai/basic_mobs/simple_skirmisher.bt.json new file mode 100644 index 00000000000..3a671abba85 --- /dev/null +++ b/code/datums/ai/basic_mobs/simple_skirmisher.bt.json @@ -0,0 +1,14 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/simple/simple_skirmisher", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_skirmisher_combat" + } + ] +} diff --git a/code/datums/ai/basic_mobs/simple_skirmisher_combat.bt.json b/code/datums/ai/basic_mobs/simple_skirmisher_combat.bt.json new file mode 100644 index 00000000000..6c5d60b593f --- /dev/null +++ b/code/datums/ai/basic_mobs/simple_skirmisher_combat.bt.json @@ -0,0 +1,92 @@ +{ + "dm_type": "/datum/bt_node/subtree/simple_skirmisher_combat", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/attack_obstructions", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_ranged_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] +} diff --git a/code/datums/ai/basic_mobs/simple_skittish.bt.json b/code/datums/ai/basic_mobs/simple_skittish.bt.json new file mode 100644 index 00000000000..36828f42ae5 --- /dev/null +++ b/code/datums/ai/basic_mobs/simple_skittish.bt.json @@ -0,0 +1,10 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/simple/simple_skittish", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_skittish_combat" + } + ] +} diff --git a/code/datums/ai/basic_mobs/simple_skittish_combat.bt.json b/code/datums/ai/basic_mobs/simple_skittish_combat.bt.json new file mode 100644 index 00000000000..6db93fc782d --- /dev/null +++ b/code/datums/ai/basic_mobs/simple_skittish_combat.bt.json @@ -0,0 +1,41 @@ +{ + "dm_type": "/datum/bt_node/subtree/simple_skittish_combat", + "type": "selector", + "children": [ + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CURRENT_TARGET", + "observer_abort": "BT_ABORT_SELF" + }, + "child": { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/run_away_from_target" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list/nearest", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] +} diff --git a/code/datums/ai/basic_mobs/talk.bt.json b/code/datums/ai/basic_mobs/talk.bt.json new file mode 100644 index 00000000000..20584cd412b --- /dev/null +++ b/code/datums/ai/basic_mobs/talk.bt.json @@ -0,0 +1,5 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/talk", + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop" +} diff --git a/code/datums/ai/basic_mobs/target_sources/_target_source.dm b/code/datums/ai/basic_mobs/target_sources/_target_source.dm new file mode 100644 index 00000000000..0a501c5ca13 --- /dev/null +++ b/code/datums/ai/basic_mobs/target_sources/_target_source.dm @@ -0,0 +1,106 @@ +/// Singleton datum that defines how an acquire_target behavior gathers candidates. +/// Subtype collect_candidates() to change what atoms are considered. +/// Subtype with a typecache var to pre-filter by type before targeting_strategy validation. +/datum/target_source + +/// Returns a list of candidate atoms for the behavior to filter and select from. +/datum/target_source/proc/collect_candidates(mob/living/pawn, datum/ai_controller/controller, range) + return list() + +/// Gathers nearby atoms via oview(). No type pre-filtering. +/datum/target_source/oview + +/datum/target_source/oview/collect_candidates(mob/living/pawn, datum/ai_controller/controller, range) + var/list/candidates = oview(range, pawn) + return candidates + +/// Gathers nearby atoms via oview(), pre-filtered by a typecache +/datum/target_source/oview_typed + /// Optional typecache for pre-filtering candidates; null means no type pre-filter. + var/list/typecache + +/datum/target_source/oview_typed/collect_candidates(mob/living/pawn, datum/ai_controller/controller, range) + if(isnull(typecache)) + CRASH("[pawn] using [controller] ran [src] with no typecache.") + var/list/candidates = typecache_filter_list(oview(range, pawn), typecache) + return candidates + +/// Gathers nearby atoms via oview(), pre-filtered by a typecache stored in a blackboard key. +/// Use this when the typecache varies per mob species (e.g. BB_BASIC_FOODS). +/datum/target_source/oview_typed/from_bb_key + /// Blackboard key whose value is the typecache list to filter by. + var/typecache_key + +/datum/target_source/oview_typed/from_bb_key/collect_candidates(mob/living/pawn, datum/ai_controller/controller, range) + var/list/bb_typecache = controller.blackboard[typecache_key] + var/list/candidates + if(isnull(bb_typecache)) + candidates = oview(range, pawn) + else + candidates = typecache_filter_list(oview(range, pawn), bb_typecache) + return candidates + +/// Gathers nearby atoms via hearers() plus any hostile machines on the same z-level. (I should probably split out the hostile machine part but oh well) +/datum/target_source/hearers + +/datum/target_source/hearers/collect_candidates(mob/living/pawn, datum/ai_controller/controller, range) + var/list/candidates = hearers(range, get_turf(pawn)) - pawn + var/turf/mob_turf = get_turf(pawn) + if(mob_turf?.z) + for(var/atom/hostile_machine as anything in GLOB.hostile_machines_by_z[mob_turf.z]) + if(can_see(pawn, hostile_machine, range)) + candidates += hostile_machine + + return candidates + +/// Gathers turfs in range via RANGE_TURFS(). +/datum/target_source/range_turfs + +/datum/target_source/range_turfs/collect_candidates(mob/living/pawn, datum/ai_controller/controller, range) + return RANGE_TURFS(range, pawn) + +/// Gathers items currently held in the pawn's hands. +/datum/target_source/held_items + +/datum/target_source/held_items/collect_candidates(mob/living/pawn, datum/ai_controller/controller, range) + return pawn.held_items || list() + +/// Reads a typecache from BB_BASIC_FOODS and filters oview candidates by it. +/// For mobs whose food list varies by species (set in Initialize via set_blackboard_key). +/datum/target_source/oview_typed/from_bb_key/basic_foods + typecache_key = BB_BASIC_FOODS + +/// Reads a typecache from BB_HUNTABLE_PREY and filters oview candidates by it. +/datum/target_source/oview_typed/from_bb_key/huntable_prey + typecache_key = BB_HUNTABLE_PREY + +/// Gathers items the pawn is carrying that match the prey typecache in BB_HUNTABLE_PREY. +/datum/target_source/carried_huntable_prey + +/datum/target_source/carried_huntable_prey/collect_candidates(mob/living/pawn, datum/ai_controller/controller, range) + var/list/prey = controller.blackboard[BB_HUNTABLE_PREY] + if(isnull(prey)) + return list() + return typecache_filter_list(pawn.contents, prey) + +/// Reads candidates directly from a blackboard list. No spatial filtering; range is ignored. +/datum/target_source/from_bb_list + var/list_key + +/datum/target_source/from_bb_list/collect_candidates(mob/living/pawn, datum/ai_controller/controller, range) + return controller.blackboard[list_key] || list() + +/// Reads from BB_BASIC_MOB_RETALIATE_LIST. +/datum/target_source/from_bb_list/retaliate_list + list_key = BB_BASIC_MOB_RETALIATE_LIST + + +/datum/target_source/oview_items + +/datum/target_source/oview_items/collect_candidates(mob/living/pawn, datum/ai_controller/controller, range) + . = ..() + var/list/candidates = list() + for(var/obj/item/item_candidate in oview(range, pawn)) + candidates += item_candidate + + return candidates diff --git a/code/datums/ai/basic_mobs/target_sources/held_items_then_oview.dm b/code/datums/ai/basic_mobs/target_sources/held_items_then_oview.dm new file mode 100644 index 00000000000..721a7c09545 --- /dev/null +++ b/code/datums/ai/basic_mobs/target_sources/held_items_then_oview.dm @@ -0,0 +1,11 @@ +/// Returns the pawn's held items first, then nearby atoms via oview(). Used for food searches that prefer held items. +/datum/target_source/held_items_then_oview + +/datum/target_source/held_items_then_oview/collect_candidates(mob/living/pawn, datum/ai_controller/controller, range) + var/list/candidates = list() + for(var/obj/item/candidate_item as anything in pawn.held_items) + if(isnull(candidate_item)) + continue + candidates += candidate_item + candidates += oview(range, pawn) + return candidates diff --git a/code/datums/ai/basic_mobs/target_sources/held_items_typed.dm b/code/datums/ai/basic_mobs/target_sources/held_items_typed.dm new file mode 100644 index 00000000000..3f12cfa3957 --- /dev/null +++ b/code/datums/ai/basic_mobs/target_sources/held_items_typed.dm @@ -0,0 +1,13 @@ +/// Gathers held items matching a fixed typepath. +/datum/target_source/held_items_typed + var/locate_typepath + +/datum/target_source/held_items_typed/collect_candidates(mob/living/pawn, datum/ai_controller/controller, range) + var/list/candidates = list() + for(var/atom/candidate as anything in pawn.held_items) + if(istype(candidate, locate_typepath)) + candidates += candidate + return candidates + +/datum/target_source/held_items_typed/instrument + locate_typepath = /obj/item/instrument diff --git a/code/datums/ai/basic_mobs/target_sources/mobs_in_oview.dm b/code/datums/ai/basic_mobs/target_sources/mobs_in_oview.dm new file mode 100644 index 00000000000..e0d0e34e3f9 --- /dev/null +++ b/code/datums/ai/basic_mobs/target_sources/mobs_in_oview.dm @@ -0,0 +1,20 @@ +/datum/target_source/oview_living + +/datum/target_source/oview_living/collect_candidates(mob/living/pawn, datum/ai_controller/controller, range) + . = ..() + var/list/candidates = list() + for(var/mob/living/living_candidate in oview(range, pawn)) + candidates += living_candidate + + return candidates + + +/datum/target_source/oview_raptor_babies + +/datum/target_source/oview_raptor_babies/collect_candidates(mob/living/pawn, datum/ai_controller/controller, range) + . = ..() + var/list/candidates = list() + for(var/mob/living/basic/raptor/candidate in oview(range, pawn)) + candidates += candidate + + return candidates diff --git a/code/datums/ai/basic_mobs/target_sources/near_village_humans.dm b/code/datums/ai/basic_mobs/target_sources/near_village_humans.dm new file mode 100644 index 00000000000..893fc25a461 --- /dev/null +++ b/code/datums/ai/basic_mobs/target_sources/near_village_humans.dm @@ -0,0 +1,12 @@ +/// Gathers nearby carbon humans within BB_MAXIMUM_DISTANCE_TO_VILLAGE of the BB_HOME_VILLAGE anchor. +/datum/target_source/near_village_humans + +/datum/target_source/near_village_humans/collect_candidates(mob/living/pawn, datum/ai_controller/controller, range) + var/atom/anchor = controller.blackboard[BB_HOME_VILLAGE] + var/max_dist = controller.blackboard[BB_MAXIMUM_DISTANCE_TO_VILLAGE] || range + var/list/candidates = list() + for(var/mob/living/carbon/human/candidate in oview(max_dist, pawn)) + if(!isnull(anchor) && get_dist(candidate, anchor) > max_dist) + continue + candidates += candidate + return candidates diff --git a/code/datums/ai/basic_mobs/target_sources/oview_single_type.dm b/code/datums/ai/basic_mobs/target_sources/oview_single_type.dm new file mode 100644 index 00000000000..0c8835332a4 --- /dev/null +++ b/code/datums/ai/basic_mobs/target_sources/oview_single_type.dm @@ -0,0 +1,58 @@ +/** + * Gathers all targets of a type in the area. This is a define so we can do cheap loops over oview with astype. Yeah it sucks. + * You can do /datum/target_source/oview_single_type/[sub_type] to use it + */ +#define OVIEW_TARGET_SOURCE(subtype, path) \ +/datum/target_source/oview_single_type/##subtype/collect_candidates(mob/living/pawn, datum/ai_controller/controller, range) { \ + var/list/candidates = list(); \ + for(var/##path/candidate in oview(range, pawn)) { \ + candidates += candidate; \ + } \ + return candidates; \ +} + +/// Abstract parent for target sources that return every nearby atom of a single fixed type. +/// Subtypes are generated with the OVIEW_TARGET_SOURCE() macro above. +/datum/target_source/oview_single_type + +OVIEW_TARGET_SOURCE(carbon_mob, mob/living/carbon) +OVIEW_TARGET_SOURCE(human_mob, mob/living/carbon/human) +OVIEW_TARGET_SOURCE(living_mob, mob/living) +OVIEW_TARGET_SOURCE(disposal_unit, obj/machinery/disposal) +OVIEW_TARGET_SOURCE(paper, obj/item/paper) +OVIEW_TARGET_SOURCE(watering_can, obj/item/reagent_containers/cup/watering_can) +OVIEW_TARGET_SOURCE(ore_stand, obj/structure/ore_container/material_stand) +OVIEW_TARGET_SOURCE(flora_tree, obj/structure/flora/tree) +OVIEW_TARGET_SOURCE(vent_pump, obj/machinery/atmospherics/components/unary/vent_pump) +OVIEW_TARGET_SOURCE(tribal_chief, mob/living/basic/mining/mook/worker/tribal_chief) +OVIEW_TARGET_SOURCE(apc, obj/machinery/power/apc) +OVIEW_TARGET_SOURCE(machine, obj/machinery) +OVIEW_TARGET_SOURCE(beehive, obj/structure/beebox) +OVIEW_TARGET_SOURCE(penguin_egg, obj/item/food/egg/penguin_egg) +OVIEW_TARGET_SOURCE(raptor, mob/living/basic/raptor) +OVIEW_TARGET_SOURCE(raptor_trough, obj/structure/ore_container/food_trough/raptor_trough) +OVIEW_TARGET_SOURCE(gutlunch_trough, obj/structure/ore_container/food_trough/gutlunch_trough) +OVIEW_TARGET_SOURCE(mouse, mob/living/basic/mouse) +OVIEW_TARGET_SOURCE(oven, obj/machinery/oven/range) +OVIEW_TARGET_SOURCE(cable, obj/structure/cable) +OVIEW_TARGET_SOURCE(donut, obj/item/food/donut) +OVIEW_TARGET_SOURCE(hydroponics, obj/machinery/hydroponics) +OVIEW_TARGET_SOURCE(cheese, obj/item/food/cheese) +OVIEW_TARGET_SOURCE(piano_synth, obj/item/instrument/piano_synth) +OVIEW_TARGET_SOURCE(orbie, mob/living/basic/orbie) +OVIEW_TARGET_SOURCE(ore_vent, obj/structure/ore_vent) +OVIEW_TARGET_SOURCE(mushroom_food, obj/item/food/grown/mushroom) +OVIEW_TARGET_SOURCE(ore, obj/item/stack/ore) +OVIEW_TARGET_SOURCE(minebot_target, obj/effect/temp_visual/minebot_target) +OVIEW_TARGET_SOURCE(node_drone, mob/living/basic/node_drone) +OVIEW_TARGET_SOURCE(icy_rock, obj/structure/flora/rock/icy) +OVIEW_TARGET_SOURCE(ice_whelp, mob/living/basic/mining/ice_whelp) +OVIEW_TARGET_SOURCE(hivebot, mob/living/basic/hivebot) +OVIEW_TARGET_SOURCE(carrot, obj/item/food/grown/carrotlike/carrot) +OVIEW_TARGET_SOURCE(ants, obj/effect/decal/cleanable/ants) +OVIEW_TARGET_SOURCE(cat_house, obj/structure/cat_house) +OVIEW_TARGET_SOURCE(honeycomb, obj/item/food/honeycomb) +OVIEW_TARGET_SOURCE(kitten, mob/living/basic/pet/cat/kitten) +OVIEW_TARGET_SOURCE(deer_animals, mob/living/basic/deer) + +#undef OVIEW_TARGET_SOURCE diff --git a/code/datums/ai/basic_mobs/target_sources/oview_typed_from_bb_key.dm b/code/datums/ai/basic_mobs/target_sources/oview_typed_from_bb_key.dm new file mode 100644 index 00000000000..49c526fd74c --- /dev/null +++ b/code/datums/ai/basic_mobs/target_sources/oview_typed_from_bb_key.dm @@ -0,0 +1,19 @@ +/// Subtypes of oview_typed/from_bb_key for interaction searches. + +/datum/target_source/oview_typed/from_bb_key/stationary_targets + typecache_key = BB_STATIONARY_TARGETS + +/datum/target_source/oview_typed/from_bb_key/mook_heal_targets + typecache_key = BB_MOOK_HEAL_TARGETS + +/datum/target_source/oview_typed/from_bb_key/bonfire_targets + typecache_key = BB_BONFIRE_TARGETS + +/datum/target_source/oview_typed/from_bb_key/hunt_target_list + typecache_key = BB_HUNT_TARGET_LIST + +/datum/target_source/oview_typed/from_bb_key/turtle_headbutt_types + typecache_key = BB_TURTLE_HEADBUTT_TYPES + +/datum/target_source/oview_typed/from_bb_key/turtle_flora_types + typecache_key = BB_TURTLE_FLORA_TYPES diff --git a/code/datums/ai/basic_mobs/target_sources/range_turfs_typecache_visible.dm b/code/datums/ai/basic_mobs/target_sources/range_turfs_typecache_visible.dm new file mode 100644 index 00000000000..d8b2617c2c1 --- /dev/null +++ b/code/datums/ai/basic_mobs/target_sources/range_turfs_typecache_visible.dm @@ -0,0 +1,34 @@ +/// Gathers visible turfs in range matching a typecache. Shuffled for variety. +/datum/target_source/range_turfs/typecache_visible + var/list/typecache + +/datum/target_source/range_turfs/typecache_visible/collect_candidates(mob/living/pawn, datum/ai_controller/controller, range) + var/list/found = RANGE_TURFS(range, pawn) + var/list/valid = list() + for(var/turf/candidate as anything in found) + if(!is_type_in_typecache(candidate, typecache)) + continue + if(can_see(pawn, candidate, range)) + valid += candidate + return valid + +/datum/target_source/range_turfs/typecache_visible/deer_grass + typecache = list(/turf/open/floor/grass, /turf/open/misc/grass) + +/datum/target_source/range_turfs/typecache_visible/deer_grass/New() + . = ..() + typecache = typecacheof(typecache) + +/datum/target_source/range_turfs/typecache_visible/deer_water + typecache = list(/turf/open/water) + +/datum/target_source/range_turfs/typecache_visible/deer_water/New() + . = ..() + typecache = typecacheof(typecache) + +/datum/target_source/range_turfs/typecache_visible/ice + typecache = list(/turf/open/misc/ice) + +/datum/target_source/range_turfs/typecache_visible/ice/New() + . = ..() + typecache = typecacheof(typecache) diff --git a/code/datums/ai/basic_mobs/target_sources/slime_source.dm b/code/datums/ai/basic_mobs/target_sources/slime_source.dm new file mode 100644 index 00000000000..f4597cedde3 --- /dev/null +++ b/code/datums/ai/basic_mobs/target_sources/slime_source.dm @@ -0,0 +1,11 @@ +/datum/target_source/oview_living_no_slimes + +/datum/target_source/oview_living_no_slimes/collect_candidates(mob/living/pawn, datum/ai_controller/controller, range) + . = ..() + var/list/candidates = list() + for(var/mob/living/living_candidate in oview(range, pawn)) + if(isslime(living_candidate)) + continue + candidates += living_candidate + + return candidates diff --git a/code/datums/ai/basic_mobs/target_sources/turfs_in_oview.dm b/code/datums/ai/basic_mobs/target_sources/turfs_in_oview.dm new file mode 100644 index 00000000000..5dbf4f8ba91 --- /dev/null +++ b/code/datums/ai/basic_mobs/target_sources/turfs_in_oview.dm @@ -0,0 +1,23 @@ + +/datum/target_source/oview_water_turfs + +/datum/target_source/oview_water_turfs/collect_candidates(mob/living/pawn, datum/ai_controller/controller, range) + . = ..() + var/list/candidates = list() + for(var/turf/open/water/water_turf in oview(range, pawn)) + candidates += water_turf + + return candidates + + +/datum/target_source/oview_land_turfs + +/datum/target_source/oview_water_turfs/collect_candidates(mob/living/pawn, datum/ai_controller/controller, range) + . = ..() + var/list/candidates = list() + for(var/turf/open/potential_floor in oview(range, pawn)) + if(iswaterturf(potential_floor)) + continue + candidates += potential_floor + + return candidates diff --git a/code/datums/ai/basic_mobs/targeting_strategies/_targeting_strategy.dm b/code/datums/ai/basic_mobs/targeting_strategies/_targeting_strategy.dm index 5720602c6fc..9e6e61cab28 100644 --- a/code/datums/ai/basic_mobs/targeting_strategies/_targeting_strategy.dm +++ b/code/datums/ai/basic_mobs/targeting_strategies/_targeting_strategy.dm @@ -2,25 +2,41 @@ ///Global, just like ai_behaviors /datum/targeting_strategy -///Returns true or false depending on if the target can be attacked by the mob -/datum/targeting_strategy/proc/can_attack(mob/living/living_mob, atom/target, vision_range) - return +///Returns true or false depending on if the target can be attacked by the mob. +///Base proc checks if target is within vision_range distance. +/datum/targeting_strategy/proc/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + if(QDELETED(target)) + return FALSE -///Returns something the target might be hiding inside of + if(!vision_range) + return TRUE + + return get_dist(living_mob, target) <= vision_range + +/// Returns an atom the target might be hiding inside of, or null if none. /datum/targeting_strategy/proc/find_hidden_mobs(mob/living/living_mob, atom/target) - var/atom/target_hiding_location - if(istype(target.loc, /obj/structure/closet) || istype(target.loc, /obj/machinery/disposal) || istype(target.loc, /obj/machinery/sleeper)) - target_hiding_location = target.loc - return target_hiding_location + return null + +/// Returns TRUE if we should keep tracking an existing target when no new candidates are visible. +/// Called with the loss range (typically larger than vision_range). +/// Default delegates to is_valid_target so all normal checks still apply. +/datum/targeting_strategy/proc/can_keep_target(mob/living/living_mob, atom/target, range, datum/ai_controller/controller = null) + return is_valid_target(living_mob, target, range, controller) /// Simply always returns true if you have a target, so only use this if you're pre-checking the targets somewhere else /datum/targeting_strategy/anything -/datum/targeting_strategy/anything/can_attack(mob/living/living_mob, atom/target, vision_range) - return !!target +/datum/targeting_strategy/anything/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + return TRUE ///A very simple targeting strategy that checks that the target is a valid fishing spot. /datum/targeting_strategy/fishing -/datum/targeting_strategy/fishing/can_attack(mob/living/living_mob, atom/target, vision_range) +/datum/targeting_strategy/fishing/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE return HAS_TRAIT(target, TRAIT_FISHING_SPOT) diff --git a/code/datums/ai/basic_mobs/targeting_strategies/accessible_cable.dm b/code/datums/ai/basic_mobs/targeting_strategies/accessible_cable.dm new file mode 100644 index 00000000000..7a62b9d4c02 --- /dev/null +++ b/code/datums/ai/basic_mobs/targeting_strategies/accessible_cable.dm @@ -0,0 +1,14 @@ +/// Accepts visible cables sitting on accessible open floor tiles. +/datum/targeting_strategy/accessible_cable + +/datum/targeting_strategy/accessible_cable/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + var/obj/structure/cable/candidate = target + if(!istype(candidate) || !can_see(living_mob, candidate, vision_range)) + return FALSE + var/turf/open/floor/below_the_cable = get_turf(candidate) + if(!istype(below_the_cable)) + return FALSE + return below_the_cable.underfloor_accessibility >= UNDERFLOOR_INTERACTABLE diff --git a/code/datums/ai/basic_mobs/targeting_strategies/ally_mob.dm b/code/datums/ai/basic_mobs/targeting_strategies/ally_mob.dm new file mode 100644 index 00000000000..e881ccafd5d --- /dev/null +++ b/code/datums/ai/basic_mobs/targeting_strategies/ally_mob.dm @@ -0,0 +1,10 @@ +/// Accepts visible mobs that are allies of the pawn (and not the pawn itself). +/datum/targeting_strategy/ally_mob + +/datum/targeting_strategy/ally_mob/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + if(target == living_mob || !living_mob.has_ally(target)) + return FALSE + return can_see(living_mob, target, vision_range) diff --git a/code/datums/ai/basic_mobs/targeting_strategies/baby_raptor.dm b/code/datums/ai/basic_mobs/targeting_strategies/baby_raptor.dm new file mode 100644 index 00000000000..bcdcdce2fdf --- /dev/null +++ b/code/datums/ai/basic_mobs/targeting_strategies/baby_raptor.dm @@ -0,0 +1,11 @@ +/// Accepts visible, living baby raptors. ONLY USE ON BABY RAPTORS WE ASSUME IT IS ONE +/datum/targeting_strategy/healthy_raptor_baby + +/datum/targeting_strategy/healthy_raptor_baby/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + var/mob/living/basic/raptor/candidate = target + if(candidate.stat == DEAD || candidate.growth_stage != RAPTOR_BABY) + return FALSE + return can_see(living_mob, candidate, vision_range) diff --git a/code/datums/ai/basic_mobs/targeting_strategies/basic_targeting_strategy.dm b/code/datums/ai/basic_mobs/targeting_strategies/basic_targeting_strategy.dm index be1d1484225..a75a9c79b4c 100644 --- a/code/datums/ai/basic_mobs/targeting_strategies/basic_targeting_strategy.dm +++ b/code/datums/ai/basic_mobs/targeting_strategies/basic_targeting_strategy.dm @@ -1,20 +1,37 @@ /datum/targeting_strategy/basic + ///Whether we ignore faction checks + var/ignore_target_status = FALSE /// When we do our basic faction check, do we look for exact faction matches? var/check_factions_exactly = FALSE /// Whether we care for seeing the target or not var/ignore_sight = FALSE + /// Whether we skip faction checks entirely, never rejecting a target over factions + var/ignore_faction = FALSE + /// If TRUE the faction check is inverted, making us only target mobs that DO share a faction with us + var/invert_faction_check = FALSE + /// Set to TRUE on subtypes that override faction_check() with custom logic, routing the check through the proc instead of the inlined macro + var/custom_faction_check = FALSE /// Blackboard key containing the minimum stat of a living mob to target var/minimum_stat_key = BB_TARGET_MINIMUM_STAT /// If this blackboard key is TRUE, makes us only target wounded mobs var/target_wounded_key -/datum/targeting_strategy/basic/can_attack(mob/living/living_mob, atom/the_target, vision_range) +/datum/targeting_strategy/basic/is_valid_target(mob/living/living_mob, atom/the_target, vision_range, datum/ai_controller/controller = null) + // checks are ordered cheapest first so invalid targets are rejected before the expensive sight check + if(isturf(the_target) || isnull(the_target)) // bail out on invalids + return FALSE + var/datum/ai_controller/basic_controller/our_controller = living_mob.ai_controller if(isnull(our_controller)) return FALSE - if(isturf(the_target) || isnull(the_target)) // bail out on invalids + if(living_mob.see_invisible < the_target.invisibility) //Target's invisible to us, forget it + return FALSE + + if(!isturf(living_mob.loc)) + return FALSE + if(isturf(the_target.loc) && living_mob.z != the_target.z) // z check will always fail if target is in a mech or pawn is shapeshifted or jaunting return FALSE if(isobj(the_target.loc)) @@ -31,35 +48,27 @@ if (vision_range && get_dist(living_mob, the_target) > vision_range) return FALSE - if(!ignore_sight && !can_see(living_mob, the_target, vision_range)) //Target has moved behind cover and we have lost line of sight to it - return FALSE - - if(living_mob.see_invisible < the_target.invisibility) //Target's invisible to us, forget it - return FALSE - - if(!isturf(living_mob.loc)) - return FALSE - if(isturf(the_target.loc) && living_mob.z != the_target.z) // z check will always fail if target is in a mech or pawn is shapeshifted or jaunting - return FALSE - if(isliving(the_target)) //Targeting vs living mobs - var/mob/living/living_target = the_target - if(faction_check(our_controller, living_mob, living_target)) - return FALSE - if(living_target.stat > our_controller.blackboard[minimum_stat_key]) - return FALSE - if(target_wounded_key && our_controller.blackboard[target_wounded_key] && living_target.health == living_target.maxHealth) - return FALSE + if(!ignore_target_status) + var/mob/living/living_target = the_target + if(custom_faction_check ? faction_check(our_controller, living_mob, living_target) : TARGETING_FACTION_CHECK(src, our_controller, living_mob, living_target)) + return FALSE + if(living_target.stat > our_controller.blackboard[minimum_stat_key]) + return FALSE + if(target_wounded_key && our_controller.blackboard[target_wounded_key] && living_target.health == living_target.maxHealth) + return FALSE - return TRUE - - if(ismecha(the_target)) //Targeting vs mechas + else if(ismecha(the_target)) //Targeting vs mechas var/obj/vehicle/sealed/mecha/M = the_target + var/valid_occupant = FALSE for(var/occupant in M.occupants) - if(can_attack(living_mob, occupant)) //Can we attack any of the occupants? - return TRUE + if(is_valid_target(living_mob, occupant)) //Can we attack any of the occupants? + valid_occupant = TRUE + break + if(!valid_occupant) + return FALSE - if(istype(the_target, /obj/machinery/porta_turret)) //Cringe turret! kill it! + else if(istype(the_target, /obj/machinery/porta_turret)) //Cringe turret! kill it! var/obj/machinery/porta_turret/P = the_target if(P.in_faction(living_mob)) //Don't attack if the turret is in the same faction return FALSE @@ -67,21 +76,33 @@ return FALSE if(P.machine_stat & BROKEN) //Or turrets that are already broken return FALSE - return TRUE - return FALSE - -/// Returns true if the mob and target share factions -/datum/targeting_strategy/basic/proc/faction_check(datum/ai_controller/controller, mob/living/living_mob, mob/living/the_target) - if (controller.blackboard[BB_ALWAYS_IGNORE_FACTION] || controller.blackboard[BB_TEMPORARILY_IGNORE_FACTION]) + else //Not a type of thing we can target return FALSE - return living_mob.faction_check_atom(the_target, exact_match = check_factions_exactly) + + if(!ignore_sight && !can_see(living_mob, the_target, vision_range)) //Sight check goes last, it's by far the most expensive + return FALSE + return TRUE + +/datum/targeting_strategy/basic/find_hidden_mobs(mob/living/living_mob, atom/target) + . = ..() + if(istype(target.loc, /obj/structure/closet) || istype(target.loc, /obj/machinery/disposal) || istype(target.loc, /obj/machinery/sleeper)) + return target.loc + return null + +/datum/targeting_strategy/basic/can_keep_target(mob/living/living_mob, atom/target, range) + return can_see(living_mob, target, range) + +/// Returns true if the mob and target share factions. +/// Slow path for subtypes with custom_faction_check set; everything else uses TARGETING_FACTION_CHECK directly +/datum/targeting_strategy/basic/proc/faction_check(datum/ai_controller/controller, mob/living/living_mob, mob/living/the_target) + return TARGETING_FACTION_CHECK(src, controller, living_mob, the_target) /// Subtype more forgiving for items. /// Careful, this can go wrong and keep a mob hyper-focused on an item it can't lose aggro on /datum/targeting_strategy/basic/allow_items -/datum/targeting_strategy/basic/allow_items/can_attack(mob/living/living_mob, atom/the_target, vision_range) +/datum/targeting_strategy/basic/allow_items/is_valid_target(mob/living/living_mob, atom/the_target, vision_range, datum/ai_controller/controller = null) . = ..() if(isitem(the_target)) // trust fall exercise @@ -89,7 +110,7 @@ /datum/targeting_strategy/basic/require_traits -/datum/targeting_strategy/basic/require_traits/can_attack(mob/living/living_mob, atom/the_target, vision_range) +/datum/targeting_strategy/basic/require_traits/is_valid_target(mob/living/living_mob, atom/the_target, vision_range, datum/ai_controller/controller = null) . = ..() if (!.) return FALSE @@ -109,7 +130,7 @@ /// If true, we will return mobs which are the same size as us. var/inclusive = TRUE -/datum/targeting_strategy/basic/of_size/can_attack(mob/living/owner, atom/target, vision_range) +/datum/targeting_strategy/basic/of_size/is_valid_target(mob/living/owner, atom/target, vision_range, datum/ai_controller/controller = null) if(!isliving(target)) return FALSE . = ..() @@ -136,13 +157,11 @@ /// Makes the mob only attack their own faction. Useful mostly if their attacks do something helpful (e.g. healing touch). /datum/targeting_strategy/basic/same_faction - -/datum/targeting_strategy/basic/same_faction/faction_check(mob/living/living_mob, mob/living/the_target) - return !..() // inverts logic to ONLY target mobs that share a faction + invert_faction_check = TRUE /datum/targeting_strategy/basic/allow_turfs -/datum/targeting_strategy/basic/allow_turfs/can_attack(mob/living/living_mob, atom/the_target, vision_range) +/datum/targeting_strategy/basic/allow_turfs/is_valid_target(mob/living/living_mob, atom/the_target, vision_range, datum/ai_controller/controller = null) if(isturf(the_target)) return TRUE return ..() @@ -150,7 +169,7 @@ /// Subtype which searches for mobs that havent been gutted by megafauna /datum/targeting_strategy/basic/no_gutted_mobs -/datum/targeting_strategy/basic/no_gutted_mobs/can_attack(mob/living/owner, mob/living/target, vision_range) +/datum/targeting_strategy/basic/no_gutted_mobs/is_valid_target(mob/living/owner, mob/living/target, vision_range, datum/ai_controller/controller = null) if(!istype(target) || target.has_status_effect(/datum/status_effect/gutted)) return FALSE return ..() @@ -160,7 +179,7 @@ /datum/targeting_strategy/basic/exact_match/ignore_friends -/datum/targeting_strategy/basic/exact_match/ignore_friends/can_attack(mob/living/living_mob, atom/the_target, vision_range) +/datum/targeting_strategy/basic/exact_match/ignore_friends/is_valid_target(mob/living/living_mob, atom/the_target, vision_range, datum/ai_controller/controller = null) . = ..() if (!.) return FALSE diff --git a/code/datums/ai/basic_mobs/targeting_strategies/beamable_hydro.dm b/code/datums/ai/basic_mobs/targeting_strategies/beamable_hydro.dm new file mode 100644 index 00000000000..f86c5e65c29 --- /dev/null +++ b/code/datums/ai/basic_mobs/targeting_strategies/beamable_hydro.dm @@ -0,0 +1,11 @@ +/// Accepts hydroponics trays whose plant health is below the seed's endurance threshold. +/datum/targeting_strategy/beamable_hydro + +/datum/targeting_strategy/beamable_hydro/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + var/obj/machinery/hydroponics/hydro = target + if(!istype(hydro) || isnull(hydro.myseed)) + return FALSE + return hydro.plant_health < hydro.myseed.endurance diff --git a/code/datums/ai/basic_mobs/targeting_strategies/befriendable_cultist.dm b/code/datums/ai/basic_mobs/targeting_strategies/befriendable_cultist.dm new file mode 100644 index 00000000000..8b0f1dafb63 --- /dev/null +++ b/code/datums/ai/basic_mobs/targeting_strategies/befriendable_cultist.dm @@ -0,0 +1,13 @@ +/// Accepts carbon cultists that the pawn has not already befriended. +/datum/targeting_strategy/befriendable_cultist + +/datum/targeting_strategy/befriendable_cultist/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + if(!iscarbon(target)) + return FALSE + var/mob/living/carbon/carbon_target = target + if(!IS_CULTIST(carbon_target)) + return FALSE + return !living_mob.has_ally(carbon_target) diff --git a/code/datums/ai/basic_mobs/targeting_strategies/cat_food.dm b/code/datums/ai/basic_mobs/targeting_strategies/cat_food.dm new file mode 100644 index 00000000000..f5e1e98feec --- /dev/null +++ b/code/datums/ai/basic_mobs/targeting_strategies/cat_food.dm @@ -0,0 +1,12 @@ +/// Accepts visible food that has no kitten (other than the pawn) nearby. +/// Pair with a food typecache source (e.g. oview_typed/from_bb_key/basic_foods). +/datum/targeting_strategy/cat_food + +/datum/targeting_strategy/cat_food/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + var/mob/living/nearby_kitten = locate(/mob/living/basic/pet/cat/kitten) in oview(2, target) + if(nearby_kitten && nearby_kitten != living_mob) + return FALSE + return can_see(living_mob, target, vision_range) diff --git a/code/datums/ai/basic_mobs/targeting_strategies/chargeable_apc.dm b/code/datums/ai/basic_mobs/targeting_strategies/chargeable_apc.dm new file mode 100644 index 00000000000..601e790c95d --- /dev/null +++ b/code/datums/ai/basic_mobs/targeting_strategies/chargeable_apc.dm @@ -0,0 +1,14 @@ +/// Accepts APCs that have a cell which is not at full charge, and are visible to the pawn. +/datum/targeting_strategy/chargeable_apc + +/datum/targeting_strategy/chargeable_apc/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + var/obj/machinery/power/apc/candidate = target + if(!istype(candidate) || !candidate.cell) + return FALSE + var/obj/item/stock_parts/power_store/cell/apc_cell = candidate.cell + if(apc_cell.charge == apc_cell.maxcharge) + return FALSE + return can_see(living_mob, candidate, vision_range) diff --git a/code/datums/ai/basic_mobs/targeting_strategies/conscious_human.dm b/code/datums/ai/basic_mobs/targeting_strategies/conscious_human.dm new file mode 100644 index 00000000000..e9154e85cd9 --- /dev/null +++ b/code/datums/ai/basic_mobs/targeting_strategies/conscious_human.dm @@ -0,0 +1,13 @@ +/// Targets conscious human carbons with a mind. Used for interaction targets (traders, etc.). +/datum/targeting_strategy/conscious_human + +/datum/targeting_strategy/conscious_human/is_valid_target(mob/living/living_mob, atom/the_target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + if(!istype(the_target, /mob/living/carbon/human)) + return FALSE + var/mob/living/carbon/human/human_target = the_target + if(IS_DEAD_OR_INCAP(human_target) || !human_target.mind) + return FALSE + return TRUE diff --git a/code/datums/ai/basic_mobs/targeting_strategies/conscious_mob.dm b/code/datums/ai/basic_mobs/targeting_strategies/conscious_mob.dm new file mode 100644 index 00000000000..17bac1f3dbc --- /dev/null +++ b/code/datums/ai/basic_mobs/targeting_strategies/conscious_mob.dm @@ -0,0 +1,11 @@ +/// Accepts living mobs that are not dead or incapacitated, and visible to the pawn. +/datum/targeting_strategy/conscious_mob + +/datum/targeting_strategy/conscious_mob/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + var/mob/living/candidate = target + if(!isliving(candidate) || IS_DEAD_OR_INCAP(candidate)) + return FALSE + return can_see(living_mob, candidate, vision_range) diff --git a/code/datums/ai/basic_mobs/targeting_strategies/conscious_snail.dm b/code/datums/ai/basic_mobs/targeting_strategies/conscious_snail.dm new file mode 100644 index 00000000000..37924ca37fc --- /dev/null +++ b/code/datums/ai/basic_mobs/targeting_strategies/conscious_snail.dm @@ -0,0 +1,13 @@ +/// Accepts visible, conscious carbon mobs of the snail species. +/datum/targeting_strategy/conscious_snail + +/datum/targeting_strategy/conscious_snail/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + var/mob/living/carbon/candidate = target + if(!istype(candidate) || candidate.stat != CONSCIOUS) + return FALSE + if(!is_species(candidate, /datum/species/snail)) + return FALSE + return can_see(living_mob, candidate, vision_range) diff --git a/code/datums/ai/basic_mobs/targeting_strategies/damaged_eyes.dm b/code/datums/ai/basic_mobs/targeting_strategies/damaged_eyes.dm new file mode 100644 index 00000000000..3b10ad9579f --- /dev/null +++ b/code/datums/ai/basic_mobs/targeting_strategies/damaged_eyes.dm @@ -0,0 +1,17 @@ +/// Accepts carbon mobs whose eyes are damaged at or beyond the controller's BB_EYE_DAMAGE_THRESHOLD. +/datum/targeting_strategy/damaged_eyes + +/datum/targeting_strategy/damaged_eyes/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + if(!iscarbon(target)) + return FALSE + var/threshold = controller?.blackboard[BB_EYE_DAMAGE_THRESHOLD] + if(!threshold) + return FALSE + var/mob/living/carbon/carbon_target = target + var/obj/item/organ/eyes/eyes = carbon_target.get_organ_slot(ORGAN_SLOT_EYES) + if(isnull(eyes) || eyes.damage < threshold) + return FALSE + return TRUE diff --git a/code/datums/ai/basic_mobs/targeting_strategies/damaged_machine.dm b/code/datums/ai/basic_mobs/targeting_strategies/damaged_machine.dm new file mode 100644 index 00000000000..459733c7e88 --- /dev/null +++ b/code/datums/ai/basic_mobs/targeting_strategies/damaged_machine.dm @@ -0,0 +1,11 @@ +/// Accepts machinery below max integrity that is visible to the pawn. +/datum/targeting_strategy/damaged_machine + +/datum/targeting_strategy/damaged_machine/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + var/obj/machinery/candidate = target + if(!istype(candidate) || candidate.get_integrity() >= candidate.max_integrity) + return FALSE + return can_see(living_mob, candidate, vision_range) diff --git a/code/datums/ai/basic_mobs/targeting_strategies/dead_mob.dm b/code/datums/ai/basic_mobs/targeting_strategies/dead_mob.dm new file mode 100644 index 00000000000..0cce3346d4b --- /dev/null +++ b/code/datums/ai/basic_mobs/targeting_strategies/dead_mob.dm @@ -0,0 +1,20 @@ +/// Accepts dead living mobs that are visible to the pawn. +/datum/targeting_strategy/dead_mob + +/datum/targeting_strategy/dead_mob/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + var/mob/living/candidate = target + if(!isliving(candidate) || candidate.stat != DEAD) + return FALSE + return TRUE + +/// As dead_mob, but rejects corpses already being dragged by something. +/datum/targeting_strategy/dead_mob/not_pulled + +/datum/targeting_strategy/dead_mob/not_pulled/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + var/mob/living/candidate = target + if(isliving(candidate) && candidate.pulledby) + return FALSE + return ..() diff --git a/code/datums/ai/basic_mobs/targeting_strategies/decorated_donut.dm b/code/datums/ai/basic_mobs/targeting_strategies/decorated_donut.dm new file mode 100644 index 00000000000..b6bb107bb7f --- /dev/null +++ b/code/datums/ai/basic_mobs/targeting_strategies/decorated_donut.dm @@ -0,0 +1,11 @@ +/// Accepts decorated donuts that are visible to the pawn. +/datum/targeting_strategy/decorated_donut + +/datum/targeting_strategy/decorated_donut/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + var/obj/item/food/donut/candidate = target + if(!istype(candidate) || !candidate.is_decorated) + return FALSE + return can_see(living_mob, candidate, vision_range) diff --git a/code/datums/ai/basic_mobs/targeting_strategies/dont_target_friends.dm b/code/datums/ai/basic_mobs/targeting_strategies/dont_target_friends.dm index 268b4ca8842..e13d02fdcce 100644 --- a/code/datums/ai/basic_mobs/targeting_strategies/dont_target_friends.dm +++ b/code/datums/ai/basic_mobs/targeting_strategies/dont_target_friends.dm @@ -1,5 +1,6 @@ /// Don't target an atom in our friends list (or turfs), anything else is fair game /datum/targeting_strategy/basic/not_friends + ignore_faction = TRUE //friends dont care about factions /// Stop regarding someone as a valid target once they pass this stat level, setting it to DEAD means you will happily attack corpses var/attack_until_past_stat = HARD_CRIT /// If we can try to closed turfs or not @@ -8,7 +9,7 @@ var/attack_obj = FALSE ///Returns true or false depending on if the target can be attacked by the mob -/datum/targeting_strategy/basic/not_friends/can_attack(mob/living/living_mob, atom/target, vision_range) +/datum/targeting_strategy/basic/not_friends/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) if(attack_closed_turf && isclosedturf(target)) return TRUE @@ -21,10 +22,6 @@ return ..() -///friends dont care about factions -/datum/targeting_strategy/basic/not_friends/faction_check(mob/living/living_mob, mob/living/the_target) - return FALSE - /datum/targeting_strategy/basic/not_friends/attack_closed_turfs attack_closed_turf = TRUE @@ -40,7 +37,7 @@ /// Subtype that allows us to target items while deftly avoiding attacking our allies. Be careful when it comes to targeting items as an AI could get trapped targeting something it can't destroy. /datum/targeting_strategy/basic/not_friends/allow_items -/datum/targeting_strategy/basic/not_friends/allow_items/can_attack(mob/living/living_mob, atom/the_target, vision_range) +/datum/targeting_strategy/basic/not_friends/allow_items/is_valid_target(mob/living/living_mob, atom/the_target, vision_range, datum/ai_controller/controller = null) . = ..() if(isitem(the_target)) // trust fall exercise diff --git a/code/datums/ai/basic_mobs/targeting_strategies/drillable_ice.dm b/code/datums/ai/basic_mobs/targeting_strategies/drillable_ice.dm new file mode 100644 index 00000000000..1f092ce20d6 --- /dev/null +++ b/code/datums/ai/basic_mobs/targeting_strategies/drillable_ice.dm @@ -0,0 +1,12 @@ +/// Accepts ice turfs that can have a hole made in them and are visible to the pawn. +/// Pair with a range_turfs source. +/datum/targeting_strategy/drillable_ice + +/datum/targeting_strategy/drillable_ice/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + var/turf/open/misc/ice/candidate = target + if(!istype(candidate) || !candidate.can_make_hole) + return FALSE + return can_see(living_mob, candidate, vision_range) diff --git a/code/datums/ai/basic_mobs/targeting_strategies/empty_paper.dm b/code/datums/ai/basic_mobs/targeting_strategies/empty_paper.dm new file mode 100644 index 00000000000..8c0145c6f81 --- /dev/null +++ b/code/datums/ai/basic_mobs/targeting_strategies/empty_paper.dm @@ -0,0 +1,11 @@ +/// Accepts pieces of paper that have nothing written on them yet. +/datum/targeting_strategy/empty_paper + +/datum/targeting_strategy/empty_paper/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + var/obj/item/paper/candidate = target + if(!istype(candidate) || !candidate.is_empty()) + return FALSE + return TRUE diff --git a/code/datums/ai/basic_mobs/targeting_strategies/finished_stove.dm b/code/datums/ai/basic_mobs/targeting_strategies/finished_stove.dm new file mode 100644 index 00000000000..c8d49aa68a6 --- /dev/null +++ b/code/datums/ai/basic_mobs/targeting_strategies/finished_stove.dm @@ -0,0 +1,14 @@ +/// Accepts closed ovens whose tray holds finished (no longer bakeable) goods. +/datum/targeting_strategy/finished_stove + +/datum/targeting_strategy/finished_stove/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + var/obj/machinery/oven/range/candidate = target + if(!istype(candidate) || candidate.open || !length(candidate.used_tray?.contents)) + return FALSE + for(var/atom/baking as anything in candidate.used_tray) + if(HAS_TRAIT(baking, TRAIT_BAKEABLE)) + return FALSE + return TRUE diff --git a/code/datums/ai/basic_mobs/targeting_strategies/food_or_drink.dm b/code/datums/ai/basic_mobs/targeting_strategies/food_or_drink.dm new file mode 100644 index 00000000000..6233824fdc0 --- /dev/null +++ b/code/datums/ai/basic_mobs/targeting_strategies/food_or_drink.dm @@ -0,0 +1,30 @@ +/// Accepts items that are edible or consumable from a bowl, or drinks when allowed by the controller blackboard. +/datum/targeting_strategy/pickup_item/food_or_drink + +/datum/targeting_strategy/pickup_item/food_or_drink/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + var/find_drinks = controller?.blackboard[BB_IGNORE_DRINKS] + return _is_food(target) || (find_drinks && _is_drink(target)) + +/datum/targeting_strategy/pickup_item/food_or_drink/proc/_is_food(obj/item/thing) + if(IS_EDIBLE(thing)) + return TRUE + if(istype(thing, /obj/item/reagent_containers/cup/bowl)) + return thing.reagents.total_volume > 0 + return FALSE + +/datum/targeting_strategy/pickup_item/food_or_drink/proc/_is_drink(obj/item/thing) + if(istype(thing, /obj/item/reagent_containers/cup/glass)) + return thing.reagents.total_volume > 0 + return FALSE + +/// Like food_or_drink but always accepts drinks regardless of BB_IGNORE_DRINKS. +/datum/targeting_strategy/pickup_item/food_or_drink/include_drinks + +/datum/targeting_strategy/pickup_item/food_or_drink/include_drinks/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + return _is_food(target) || _is_drink(target) diff --git a/code/datums/ai/basic_mobs/targeting_strategies/goliath_diggable_turf.dm b/code/datums/ai/basic_mobs/targeting_strategies/goliath_diggable_turf.dm new file mode 100644 index 00000000000..cc7d01b4a8e --- /dev/null +++ b/code/datums/ai/basic_mobs/targeting_strategies/goliath_diggable_turf.dm @@ -0,0 +1,11 @@ +/// Accepts undig asteroid turfs. Pair with a range_turfs source. +/datum/targeting_strategy/goliath_diggable_turf + +/datum/targeting_strategy/goliath_diggable_turf/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + var/turf/open/misc/asteroid/candidate = target + if(!istype(candidate) || candidate.dug) + return FALSE + return TRUE diff --git a/code/datums/ai/basic_mobs/targeting_strategies/goose_edible.dm b/code/datums/ai/basic_mobs/targeting_strategies/goose_edible.dm new file mode 100644 index 00000000000..b2c52be2d36 --- /dev/null +++ b/code/datums/ai/basic_mobs/targeting_strategies/goose_edible.dm @@ -0,0 +1,11 @@ +/// Accepts items that are edible or made of plastic. Used by goose eating. +/datum/targeting_strategy/goose_edible + +/datum/targeting_strategy/goose_edible/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + var/obj/item/thing = target + if(!isitem(thing)) + return FALSE + return IS_EDIBLE(thing) || thing.has_material_type(/datum/material/plastic) diff --git a/code/datums/ai/basic_mobs/targeting_strategies/huntable.dm b/code/datums/ai/basic_mobs/targeting_strategies/huntable.dm new file mode 100644 index 00000000000..e7ae75556d0 --- /dev/null +++ b/code/datums/ai/basic_mobs/targeting_strategies/huntable.dm @@ -0,0 +1,12 @@ +/// Accepts any visible target that isn't a dead mob. Mirrors the legacy hunt finder's validity check. +/datum/targeting_strategy/huntable + +/datum/targeting_strategy/huntable/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + if(isliving(target)) + var/mob/living/living_target = target + if(living_target.stat == DEAD) + return FALSE + return can_see(living_mob, target, vision_range) diff --git a/code/datums/ai/basic_mobs/targeting_strategies/huntable_mouse.dm b/code/datums/ai/basic_mobs/targeting_strategies/huntable_mouse.dm new file mode 100644 index 00000000000..ee688815b79 --- /dev/null +++ b/code/datums/ai/basic_mobs/targeting_strategies/huntable_mouse.dm @@ -0,0 +1,11 @@ +/// Accepts living, mindless mice that are visible to the pawn. +/datum/targeting_strategy/huntable_mouse + +/datum/targeting_strategy/huntable_mouse/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + var/mob/living/candidate = target + if(!isliving(candidate) || candidate.stat == DEAD || candidate.mind) + return FALSE + return can_see(living_mob, candidate, vision_range) diff --git a/code/datums/ai/basic_mobs/targeting_strategies/injured_mob.dm b/code/datums/ai/basic_mobs/targeting_strategies/injured_mob.dm new file mode 100644 index 00000000000..6afb807c2d1 --- /dev/null +++ b/code/datums/ai/basic_mobs/targeting_strategies/injured_mob.dm @@ -0,0 +1,19 @@ +/// Accepts mobs below their maximum health (no line-of-sight requirement). +/datum/targeting_strategy/injured_mob + +/datum/targeting_strategy/injured_mob/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + var/mob/living/candidate = target + if(!isliving(candidate)) + return FALSE + return candidate.health < candidate.maxHealth + +/// As injured_mob, but never accepts the searching pawn itself. +/datum/targeting_strategy/injured_mob/not_self + +/datum/targeting_strategy/injured_mob/not_self/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + if(target == living_mob) + return FALSE + return ..() diff --git a/code/datums/ai/basic_mobs/targeting_strategies/injured_raptor.dm b/code/datums/ai/basic_mobs/targeting_strategies/injured_raptor.dm new file mode 100644 index 00000000000..36c7a2b8cae --- /dev/null +++ b/code/datums/ai/basic_mobs/targeting_strategies/injured_raptor.dm @@ -0,0 +1,6 @@ +/datum/targeting_strategy/injured_mob/not_self/injured_raptor + +/datum/targeting_strategy/injured_mob/not_self/injured_raptor/is_valid_target(mob/living/living_mob, mob/living/basic/raptor/target, vision_range, datum/ai_controller/controller = null) + if(!istype(target)) + return FALSE + return ..() diff --git a/code/datums/ai/basic_mobs/targeting_strategies/legged_conscious_human.dm b/code/datums/ai/basic_mobs/targeting_strategies/legged_conscious_human.dm new file mode 100644 index 00000000000..97294b66770 --- /dev/null +++ b/code/datums/ai/basic_mobs/targeting_strategies/legged_conscious_human.dm @@ -0,0 +1,13 @@ +/// Accepts conscious humans who have at least one leg and are visible to the pawn. +/datum/targeting_strategy/legged_conscious_human + +/datum/targeting_strategy/legged_conscious_human/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + var/mob/living/carbon/human/candidate = target + if(!istype(candidate) || candidate.stat != CONSCIOUS) + return FALSE + if(isnull(candidate.get_bodypart(BODY_ZONE_R_LEG)) && isnull(candidate.get_bodypart(BODY_ZONE_L_LEG))) + return FALSE + return can_see(living_mob, candidate, vision_range) diff --git a/code/datums/ai/basic_mobs/targeting_strategies/living_not_dead.dm b/code/datums/ai/basic_mobs/targeting_strategies/living_not_dead.dm new file mode 100644 index 00000000000..e712d70f388 --- /dev/null +++ b/code/datums/ai/basic_mobs/targeting_strategies/living_not_dead.dm @@ -0,0 +1,12 @@ +/// Accepts living mobs that are not dead and visible to the pawn. Skips faction checks, +/// so it's suitable for finding allies (e.g. a hivebot looking for another hivebot). +/datum/targeting_strategy/living_not_dead + +/datum/targeting_strategy/living_not_dead/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + var/mob/living/candidate = target + if(!isliving(candidate) || candidate.stat == DEAD) + return FALSE + return can_see(living_mob, candidate, vision_range) diff --git a/code/datums/ai/basic_mobs/targeting_strategies/non_stump_tree.dm b/code/datums/ai/basic_mobs/targeting_strategies/non_stump_tree.dm new file mode 100644 index 00000000000..c969e8d2412 --- /dev/null +++ b/code/datums/ai/basic_mobs/targeting_strategies/non_stump_tree.dm @@ -0,0 +1,10 @@ +/// Accepts trees that are not stumps and are visible to the pawn. +/datum/targetingUI_strategy/non_stump_tree + +/datum/targeting_strategy/non_stump_tree/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + if(istype(target, /obj/structure/flora/tree/stump)) + return FALSE + return can_see(living_mob, target, vision_range) diff --git a/code/datums/ai/basic_mobs/targeting_strategies/pickup_item.dm b/code/datums/ai/basic_mobs/targeting_strategies/pickup_item.dm new file mode 100644 index 00000000000..dc7efb60626 --- /dev/null +++ b/code/datums/ai/basic_mobs/targeting_strategies/pickup_item.dm @@ -0,0 +1,10 @@ +///Ensures the item is actually on a turf, else we can't go there to pick it up! +/datum/targeting_strategy/pickup_item + +/datum/targeting_strategy/pickup_item/is_valid_target(mob/living/living_mob, obj/item/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + if(!isturf(target.loc) && !living_mob.is_holding(target)) + return FALSE + return TRUE diff --git a/code/datums/ai/basic_mobs/targeting_strategies/playable_deer.dm b/code/datums/ai/basic_mobs/targeting_strategies/playable_deer.dm new file mode 100644 index 00000000000..e140b8ee2fd --- /dev/null +++ b/code/datums/ai/basic_mobs/targeting_strategies/playable_deer.dm @@ -0,0 +1,11 @@ +/// Accepts living (non-dead) deer that are visible to the pawn. +/datum/targeting_strategy/playable_deer + +/datum/targeting_strategy/playable_deer/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + var/mob/living/candidate = target + if(!isliving(candidate) || candidate.stat == DEAD) + return FALSE + return can_see(living_mob, candidate, vision_range) diff --git a/code/datums/ai/basic_mobs/targeting_strategies/playable_synthesizer.dm b/code/datums/ai/basic_mobs/targeting_strategies/playable_synthesizer.dm new file mode 100644 index 00000000000..3c83efdd4f6 --- /dev/null +++ b/code/datums/ai/basic_mobs/targeting_strategies/playable_synthesizer.dm @@ -0,0 +1,12 @@ +/// Accepts a visible piano synthesizer sitting on the floor (not a wearable headphones variant). +/datum/targeting_strategy/playable_synthesizer + +/datum/targeting_strategy/playable_synthesizer/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + if(target.type != /obj/item/instrument/piano_synth) + return FALSE + if(!isturf(target.loc)) + return FALSE + return can_see(living_mob, target, vision_range) diff --git a/code/datums/ai/basic_mobs/targeting_strategies/pollinatable_hydro.dm b/code/datums/ai/basic_mobs/targeting_strategies/pollinatable_hydro.dm new file mode 100644 index 00000000000..44b78548503 --- /dev/null +++ b/code/datums/ai/basic_mobs/targeting_strategies/pollinatable_hydro.dm @@ -0,0 +1,11 @@ +/// Accepts visible hydroponics trays a bee can currently pollinate. +/datum/targeting_strategy/pollinatable_hydro + +/datum/targeting_strategy/pollinatable_hydro/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + var/obj/machinery/hydroponics/candidate = target + if(!istype(candidate) || !candidate.can_bee_pollinate()) + return FALSE + return can_see(living_mob, candidate, vision_range) diff --git a/code/datums/ai/basic_mobs/targeting_strategies/raptor_trough.dm b/code/datums/ai/basic_mobs/targeting_strategies/raptor_trough.dm new file mode 100644 index 00000000000..5f51cf84008 --- /dev/null +++ b/code/datums/ai/basic_mobs/targeting_strategies/raptor_trough.dm @@ -0,0 +1,10 @@ +/// Looks for filled troughs raptors can eat from +/datum/targeting_strategy/raptor_trough + +/datum/targeting_strategy/raptor_trough/is_valid_target(mob/living/living_mob, obj/structure/ore_container/food_trough/raptor_trough/trough, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + if(!can_see(living_mob, trough, vision_range)) + return FALSE + return !!(locate(/obj/item/stack/ore) in trough.contents) diff --git a/code/datums/ai/basic_mobs/targeting_strategies/slime_food.dm b/code/datums/ai/basic_mobs/targeting_strategies/slime_food.dm new file mode 100644 index 00000000000..8f63df2315c --- /dev/null +++ b/code/datums/ai/basic_mobs/targeting_strategies/slime_food.dm @@ -0,0 +1,44 @@ +/// Accepts edible targets for slimes based on hunger level, faction, and species. +/// Requires the controller (reads slime hunger/rabid/current-target blackboard keys). +/datum/targeting_strategy/slime_food + +/datum/targeting_strategy/slime_food/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + if(!controller) + return FALSE + // Anything that attacked us is a valid target even if we can't eat it. + if(target in controller.blackboard[BB_BASIC_MOB_RETALIATE_LIST]) + var/datum/targeting_strategy/retaliate_strategy = GET_TARGETING_STRATEGY(/datum/targeting_strategy/basic/not_friends) + return retaliate_strategy.is_valid_target(living_mob, target, vision_range) + + var/mob/living/basic/slime/hunter = living_mob + var/mob/living/candidate = target + if(!isliving(candidate)) + return FALSE + + // We're already latched onto them and feeding; don't invalidate our own meal. + if(hunter.buckled == candidate) + return TRUE + + var/static/list/slime_faction + if(isnull(slime_faction)) + slime_faction = string_list(list(FACTION_SLIME)) + + if(FAST_FACTION_CHECK(slime_faction, candidate.get_faction(), hunter.allies, candidate.allies, FALSE)) + return FALSE + + if(!hunter.can_feed_on(candidate, check_adjacent = FALSE)) + return FALSE + + if(candidate == controller.blackboard[BB_CURRENT_TARGET]) + return can_see(hunter, candidate, vision_range) + + if(controller.blackboard[BB_SLIME_HUNGER_LEVEL] == SLIME_HUNGER_STARVING && controller.blackboard[BB_SLIME_RABID]) + return can_see(hunter, candidate, vision_range) + + if(islarva(candidate) || ismonkey(candidate) || ishuman(candidate) || isalienadult(candidate)) + return can_see(hunter, candidate, vision_range) + + return FALSE diff --git a/code/datums/ai/basic_mobs/targeting_strategies/sniffable_hydro.dm b/code/datums/ai/basic_mobs/targeting_strategies/sniffable_hydro.dm new file mode 100644 index 00000000000..61ab84289f1 --- /dev/null +++ b/code/datums/ai/basic_mobs/targeting_strategies/sniffable_hydro.dm @@ -0,0 +1,15 @@ +/// Accepts hydroponics trays with a growing seed that are not too weedy or pest-ridden, and visible. +/datum/targeting_strategy/sniffable_hydro + +/datum/targeting_strategy/sniffable_hydro/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + var/obj/machinery/hydroponics/candidate = target + if(!istype(candidate)) + return FALSE + if(isnull(candidate.myseed)) + return FALSE + if(candidate.weedlevel > 5 || candidate.pestlevel > 5) + return FALSE + return can_see(living_mob, candidate, vision_range) diff --git a/code/datums/ai/basic_mobs/targeting_strategies/stealable_item.dm b/code/datums/ai/basic_mobs/targeting_strategies/stealable_item.dm new file mode 100644 index 00000000000..15c0128be6c --- /dev/null +++ b/code/datums/ai/basic_mobs/targeting_strategies/stealable_item.dm @@ -0,0 +1,11 @@ +/// Accepts visible items that aren't anchored or already being pulled. +/datum/targeting_strategy/pickup_item/stealable_item + +/datum/targeting_strategy/pickup_item/stealable_item/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + var/obj/item/candidate = target + if(!isitem(candidate) || candidate.anchored || candidate.pulledby) + return FALSE + return can_see(living_mob, candidate, vision_range) diff --git a/code/datums/ai/basic_mobs/targeting_strategies/stocked_beehive.dm b/code/datums/ai/basic_mobs/targeting_strategies/stocked_beehive.dm new file mode 100644 index 00000000000..7b21dc74394 --- /dev/null +++ b/code/datums/ai/basic_mobs/targeting_strategies/stocked_beehive.dm @@ -0,0 +1,11 @@ +/// Accepts beehives that contain at least one honeycomb and are visible to the pawn. +/datum/targeting_strategy/stocked_beehive + +/datum/targeting_strategy/stocked_beehive/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + var/obj/structure/beebox/candidate = target + if(!istype(candidate) || !length(candidate.honeycombs)) + return FALSE + return can_see(living_mob, candidate, vision_range) diff --git a/code/datums/ai/basic_mobs/targeting_strategies/treatable_hydro.dm b/code/datums/ai/basic_mobs/targeting_strategies/treatable_hydro.dm new file mode 100644 index 00000000000..79f922bfb3c --- /dev/null +++ b/code/datums/ai/basic_mobs/targeting_strategies/treatable_hydro.dm @@ -0,0 +1,19 @@ +/// Accepts hydroponics trays that need watering (if pawn holds a watering can) or weeding/dead plant removal. +/// Reads thresholds from the controller blackboard. +/datum/targeting_strategy/treatable_hydro + +/datum/targeting_strategy/treatable_hydro/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + var/obj/machinery/hydroponics/hydro = target + if(!istype(hydro) || isnull(hydro.myseed)) + return FALSE + if(!controller) + return FALSE + var/waterlevel_threshold = controller.blackboard[BB_WATERLEVEL_THRESHOLD] + var/weedlevel_threshold = controller.blackboard[BB_WEEDLEVEL_THRESHOLD] + if(hydro.waterlevel < waterlevel_threshold) + if(locate(/obj/item/reagent_containers/cup/watering_can) in living_mob) + return TRUE + return hydro.weedlevel > weedlevel_threshold || hydro.plant_status == HYDROTRAY_PLANT_DEAD diff --git a/code/datums/ai/basic_mobs/targeting_strategies/trough_with_ore.dm b/code/datums/ai/basic_mobs/targeting_strategies/trough_with_ore.dm new file mode 100644 index 00000000000..10a2781bd98 --- /dev/null +++ b/code/datums/ai/basic_mobs/targeting_strategies/trough_with_ore.dm @@ -0,0 +1,10 @@ +/// Accepts containers (e.g. troughs) that hold at least one ore item. +/datum/targeting_strategy/trough_with_ore + +/datum/targeting_strategy/trough_with_ore/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + if(!ismovable(target)) + return FALSE + return !!(locate(/obj/item/stack/ore) in target.contents) diff --git a/code/datums/ai/basic_mobs/targeting_strategies/unbroken_light.dm b/code/datums/ai/basic_mobs/targeting_strategies/unbroken_light.dm new file mode 100644 index 00000000000..9289e0de4fa --- /dev/null +++ b/code/datums/ai/basic_mobs/targeting_strategies/unbroken_light.dm @@ -0,0 +1,11 @@ +/// Accepts light fixtures that are not already broken and are visible to the pawn. +/datum/targeting_strategy/unbroken_light + +/datum/targeting_strategy/unbroken_light/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + var/obj/machinery/light/candidate = target + if(!istype(candidate) || candidate.status == LIGHT_BROKEN) + return FALSE + return can_see(living_mob, candidate, vision_range) diff --git a/code/datums/ai/basic_mobs/targeting_strategies/uncarried_egg.dm b/code/datums/ai/basic_mobs/targeting_strategies/uncarried_egg.dm new file mode 100644 index 00000000000..4cf778ac710 --- /dev/null +++ b/code/datums/ai/basic_mobs/targeting_strategies/uncarried_egg.dm @@ -0,0 +1,10 @@ +/// Accepts visible penguin eggs the pawn is not already carrying. +/datum/targeting_strategy/uncarried_egg + +/datum/targeting_strategy/uncarried_egg/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + if(target in living_mob.contents) + return FALSE + return can_see(living_mob, target, vision_range) diff --git a/code/datums/ai/basic_mobs/targeting_strategies/unlit_bonfire.dm b/code/datums/ai/basic_mobs/targeting_strategies/unlit_bonfire.dm new file mode 100644 index 00000000000..f9cf2e6dbb1 --- /dev/null +++ b/code/datums/ai/basic_mobs/targeting_strategies/unlit_bonfire.dm @@ -0,0 +1,11 @@ +/// Accepts bonfires that are not currently burning and are visible to the pawn. +/datum/targeting_strategy/unlit_bonfire + +/datum/targeting_strategy/unlit_bonfire/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + var/obj/structure/bonfire/candidate = target + if(!istype(candidate) || candidate.burning) + return FALSE + return can_see(living_mob, candidate, vision_range) diff --git a/code/datums/ai/basic_mobs/targeting_strategies/valid_cat_home.dm b/code/datums/ai/basic_mobs/targeting_strategies/valid_cat_home.dm new file mode 100644 index 00000000000..7ce51200152 --- /dev/null +++ b/code/datums/ai/basic_mobs/targeting_strategies/valid_cat_home.dm @@ -0,0 +1,11 @@ +/// Accepts cat houses that don't already have a resident cat. +/datum/targeting_strategy/valid_cat_home + +/datum/targeting_strategy/valid_cat_home/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + var/obj/structure/cat_house/home = target + if(!istype(home) || home.resident_cat) + return FALSE + return TRUE diff --git a/code/datums/ai/basic_mobs/targeting_strategies/valid_kitten.dm b/code/datums/ai/basic_mobs/targeting_strategies/valid_kitten.dm new file mode 100644 index 00000000000..55056a0b5b4 --- /dev/null +++ b/code/datums/ai/basic_mobs/targeting_strategies/valid_kitten.dm @@ -0,0 +1,15 @@ +/// Accepts living kittens that don't already have huntable food sitting next to them. +/// Reads the prey typecache from BB_HUNTABLE_PREY on the searching controller. +/datum/targeting_strategy/valid_kitten + +/datum/targeting_strategy/valid_kitten/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + var/mob/living/kitten = target + if(!isliving(kitten) || kitten.stat == DEAD) + return FALSE + var/list/prey = controller?.blackboard[BB_HUNTABLE_PREY] + if(prey && length(typecache_filter_list(oview(2, kitten), prey))) + return FALSE + return TRUE diff --git a/code/datums/ai/basic_mobs/targeting_strategies/walkable_turf.dm b/code/datums/ai/basic_mobs/targeting_strategies/walkable_turf.dm new file mode 100644 index 00000000000..bfb6618236b --- /dev/null +++ b/code/datums/ai/basic_mobs/targeting_strategies/walkable_turf.dm @@ -0,0 +1,9 @@ +/// Base strategy for checking if a turf is walkable +/datum/targeting_strategy/walkable_turf + +/datum/targeting_strategy/walkable_turf/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + var/turf/candidate = target + return !candidate.is_blocked_turf() diff --git a/code/datums/ai/basic_mobs/targeting_strategies/water_dispenser.dm b/code/datums/ai/basic_mobs/targeting_strategies/water_dispenser.dm new file mode 100644 index 00000000000..3de583899d4 --- /dev/null +++ b/code/datums/ai/basic_mobs/targeting_strategies/water_dispenser.dm @@ -0,0 +1,13 @@ +/// Accepts sinks or reagent dispensers that contain water and are visible to the pawn. +/datum/targeting_strategy/water_dispenser + +/datum/targeting_strategy/water_dispenser/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + var/atom/movable/dispenser = target + if(!istype(dispenser, /obj/structure/sink) && !istype(dispenser, /obj/structure/reagent_dispensers)) + return FALSE + if(!dispenser.reagents || !(locate(/datum/reagent/water) in dispenser.reagents.reagent_list)) + return FALSE + return can_see(living_mob, dispenser, vision_range) diff --git a/code/datums/ai/basic_mobs/targeting_strategies/with_object.dm b/code/datums/ai/basic_mobs/targeting_strategies/with_object.dm index 7cc76d3010c..e255eef3364 100644 --- a/code/datums/ai/basic_mobs/targeting_strategies/with_object.dm +++ b/code/datums/ai/basic_mobs/targeting_strategies/with_object.dm @@ -8,11 +8,13 @@ /datum/targeting_strategy/basic/holding_object /// BB key that holds the target typepath to use var/target_item_key = BB_TARGET_HELD_ITEM + ///We dont care that they're dead, if they got tongs theyre bad + ignore_target_status = TRUE ///Returns true or false depending on if the target can be attacked by the mob -/datum/targeting_strategy/basic/holding_object/can_attack(mob/living/living_mob, atom/target, vision_range) - var/datum/ai_controller/controller = living_mob.ai_controller - var/object_type_path = controller.blackboard[target_item_key] +/datum/targeting_strategy/basic/holding_object/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + var/datum/ai_controller/our_controller = living_mob.ai_controller + var/object_type_path = our_controller.blackboard[target_item_key] if (object_type_path == null) return FALSE // no op @@ -22,8 +24,8 @@ // Look at me, type casting like a grown up var/mob/targetmob = target // Check if our parent behaviour agrees we can attack this target (we ignore faction by default) - var/can_attack = ..() - if(can_attack && targetmob.is_holding_item_of_type(object_type_path)) + var/is_valid_target = ..() + if(is_valid_target && targetmob.is_holding_item_of_type(object_type_path)) return TRUE // they have the item // No valid target return FALSE diff --git a/code/datums/ai/basic_mobs/targeting_strategies/working_machine.dm b/code/datums/ai/basic_mobs/targeting_strategies/working_machine.dm new file mode 100644 index 00000000000..9f982d1bdf6 --- /dev/null +++ b/code/datums/ai/basic_mobs/targeting_strategies/working_machine.dm @@ -0,0 +1,11 @@ +/// Accepts machinery that is not broken and is visible to the pawn. +/datum/targeting_strategy/working_machine + +/datum/targeting_strategy/working_machine/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + var/obj/machinery/candidate = target + if(!istype(candidate) || (candidate.machine_stat & BROKEN)) + return FALSE + return can_see(living_mob, candidate, vision_range) diff --git a/code/datums/ai/bots/bot_decorators.dm b/code/datums/ai/bots/bot_decorators.dm new file mode 100644 index 00000000000..e36668a3a02 --- /dev/null +++ b/code/datums/ai/bots/bot_decorators.dm @@ -0,0 +1,45 @@ +/// Gates child on pawn being emagged. Use invert = TRUE for the opposite. Checked each tick. +/datum/bt_node/decorator/bot_is_emagged + +/datum/bt_node/decorator/bot_is_emagged/check_condition(datum/ai_controller/controller) + var/mob/living/basic/bot/bot_pawn = controller.pawn + return !!(bot_pawn.bot_access_flags & BOT_COVER_EMAGGED) + +/// Gates child on pawn having the specified bot_mode_flag. Observes COMSIG_BOT_MODE_FLAGS_SET. +/datum/bt_node/decorator/bot_mode_flag + var/flag + +/datum/bt_node/decorator/bot_mode_flag/register_observe_signals(atom/pawn) + RegisterSignal(pawn, COMSIG_BOT_MODE_FLAGS_SET, PROC_REF(on_signal_changed)) + return TRUE + +/datum/bt_node/decorator/bot_mode_flag/unregister_observe_signals(atom/pawn) + UnregisterSignal(pawn, COMSIG_BOT_MODE_FLAGS_SET) + +/datum/bt_node/decorator/bot_mode_flag/check_condition(datum/ai_controller/controller) + var/mob/living/basic/bot/bot_pawn = controller.pawn + return !!(bot_pawn.bot_mode_flags & flag) + +/// Gates child on the pawn's current mode matching `mode` (e.g. BOT_DELIVER). Use invert = TRUE for the opposite. Checked each tick. +/datum/bt_node/decorator/bot_mode + var/mode + +/datum/bt_node/decorator/bot_mode/check_condition(datum/ai_controller/controller) + var/mob/living/basic/bot/bot_pawn = controller.pawn + return bot_pawn.mode == mode + +/// Gates child on the pawn's `wire` being cut. Use invert = TRUE to gate on the wire being intact. Checked each tick. +/datum/bt_node/decorator/bot_wire_cut + var/wire + +/datum/bt_node/decorator/bot_wire_cut/check_condition(datum/ai_controller/controller) + var/mob/living/basic/bot/bot_pawn = controller.pawn + return !!(bot_pawn.wires?.is_cut(wire)) + +/// Gates child when pawn has the specified medical mode flag. Use invert = TRUE for the opposite. Checked each tick. +/datum/bt_node/decorator/bot_medical_flag + var/flag + +/datum/bt_node/decorator/bot_medical_flag/check_condition(datum/ai_controller/controller) + var/mob/living/basic/bot/medbot/bot_pawn = controller.pawn + return !!(bot_pawn.medical_mode_flags & flag) diff --git a/code/datums/ai/bots/bot_patrol.bt.json b/code/datums/ai/bots/bot_patrol.bt.json new file mode 100644 index 00000000000..9d6f1f84067 --- /dev/null +++ b/code/datums/ai/bots/bot_patrol.bt.json @@ -0,0 +1,88 @@ +{ + "dm_type": "/datum/bt_node/subtree/bot_patrol", + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/key_off_cooldown", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "cooldown_key": "BB_BOT_BEACON_COOLDOWN" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bot_mode_flag", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "flag": "BOT_MODE_AUTOPATROL" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_PREVIOUS_BEACON_TARGET" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/find_next_beacon_target", + "vars": { + "target_key": "BB_BEACON_TARGET" + } + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/find_first_beacon_target", + "vars": { + "target_key": "BB_BEACON_TARGET" + } + } + ] + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_BEACON_TARGET" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_BEACON_TARGET", + "required_dist": 0, + "finish_on_arrival": true + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/arrive_at_beacon", + "vars": { + "target_key": "BB_BEACON_TARGET" + } + } + ] + } + } + ] + } + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/wait", + "vars": { + "duration": "1 SECONDS" + } + } + ] +} diff --git a/code/datums/ai/bots/bot_respond_to_summon.bt.json b/code/datums/ai/bots/bot_respond_to_summon.bt.json new file mode 100644 index 00000000000..7d0661fa7e6 --- /dev/null +++ b/code/datums/ai/bots/bot_respond_to_summon.bt.json @@ -0,0 +1,29 @@ +{ + "dm_type": "/datum/bt_node/subtree/bot_respond_to_summon", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_BOT_SUMMON_TARGET" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_BOT_SUMMON_TARGET", + "required_dist": 0, + "finish_on_arrival": true + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/complete_summon_travel", + "vars": { + "target_key": "BB_BOT_SUMMON_TARGET" + } + } + ] + } +} diff --git a/code/datums/ai/bots/bot_salute_authority.bt.json b/code/datums/ai/bots/bot_salute_authority.bt.json new file mode 100644 index 00000000000..b7a6609f637 --- /dev/null +++ b/code/datums/ai/bots/bot_salute_authority.bt.json @@ -0,0 +1,29 @@ +{ + "dm_type": "/datum/bt_node/subtree/bot_salute_authority", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/cooldown", + "vars": { + "cooldown_key": "BB_SALUTE_COOLDOWN", + "cooldown_duration": "60 SECONDS" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/find_valid_authority", + "vars": { + "target_key": "BB_SALUTE_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/salute_authority", + "vars": { + "target_key": "BB_SALUTE_TARGET", + "salute_keys": "BB_SALUTE_MESSAGES" + } + } + ] + } +} diff --git a/code/datums/ai/bots/bot_subtrees.dm b/code/datums/ai/bots/bot_subtrees.dm new file mode 100644 index 00000000000..efabf9984da --- /dev/null +++ b/code/datums/ai/bots/bot_subtrees.dm @@ -0,0 +1,281 @@ +#define BOT_NO_BEACON_PATH_PENALTY 30 SECONDS + +/** + * Searches for a valid target in oview and sets a blackboard key when found. + * Subtypes override valid_target() to refine selection criteria. + * looking_for is an optional typecache pre-filter; pass null to check all atoms via valid_target(). + */ +/datum/bt_node/ai_behavior/bot_search + var/target_key + var/looking_for = null + var/radius = 5 + var/pathing_distance = 10 + var/bypass_add_blacklist = FALSE + var/turf_search = FALSE + /// How close the path must get to the target (0 = onto/adjacent). Repairbot raises this so it stops next to the walls/girders it repairs. + var/minimum_distance = 0 + time_between_perform = 2 SECONDS + /// Stashed candidate list between perform() and the async worker (not a blackboard value). + VAR_PRIVATE/list/candidate_stash + +/datum/bt_node/ai_behavior/bot_search/perform(seconds_per_tick, datum/ai_controller/basic_controller/bot/controller) + if(!istype(controller)) + stack_trace("attempted to give [controller.pawn] the bot search behavior!") + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + var/async_flags = handle_async() + if(async_flags) + return async_flags + + if(isnull(looking_for)) + looking_for = get_looking_for_typecache() + + // Build candidate list synchronously (no sleeping), then hand off to async. + var/mob/living/living_pawn = controller.pawn + var/list/ignore_list = controller.blackboard[BB_TEMPORARY_IGNORE_LIST] + var/list/candidates = list() + for(var/atom/potential_target as anything in (turf_search ? RANGE_TURFS(radius, controller.pawn) : oview(radius, controller.pawn))) + if(!isnull(looking_for) && !is_type_in_typecache(potential_target, looking_for)) + continue + if(LAZYACCESS(ignore_list, potential_target)) + continue + if(!valid_target(controller, potential_target)) + continue + if(!can_see(controller.pawn, potential_target, radius)) + continue + candidates += potential_target + + if(!length(candidates)) + EVLOG_TEXT(controller, EVLOG_CATEGORY_AI_BEHAVIORS, "[living_pawn] bot_search ([type]): no valid target found in radius [radius]") + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + candidate_stash = candidates + return start_async() + +/datum/bt_node/ai_behavior/bot_search/perform_async(datum/ai_controller/basic_controller/bot/controller) + var/mob/living/living_pawn = controller.pawn + var/found = FALSE + for(var/atom/potential_target as anything in candidate_stash) + if(!async_still_valid()) + break + if(controller.set_if_can_reach(key = target_key, target = potential_target, distance = pathing_distance, bypass_add_to_blacklist = bypass_add_blacklist, minimum_distance = minimum_distance)) + found = TRUE + break + if(!async_still_valid()) + return + if(!found) + EVLOG_TEXT(controller, EVLOG_CATEGORY_AI_BEHAVIORS, "[living_pawn] bot_search ([type]): no reachable target found") + finish_async(found ? AI_BEHAVIOR_SUCCEEDED : AI_BEHAVIOR_FAILED) + +/datum/bt_node/ai_behavior/bot_search/finish_action(datum/ai_controller/controller, succeeded) + . = ..() + candidate_stash = null + +/datum/bt_node/ai_behavior/bot_search/proc/get_looking_for_typecache() + return + +/datum/bt_node/ai_behavior/bot_search/proc/valid_target(datum/ai_controller/basic_controller/bot/controller, atom/my_target) + return TRUE + + +///Performs bot speech from a list of options +/datum/bt_node/ai_behavior/bot_speech + var/list/list_to_pick_from + var/announce_key + time_between_perform = 5 SECONDS + +/datum/bt_node/ai_behavior/bot_speech/perform(seconds_per_tick, datum/ai_controller/controller) + var/datum/action/cooldown/bot_announcement/announcement = controller.blackboard[announce_key] + if(isnull(announcement) || !length(list_to_pick_from)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + announcement.announce(pick(list_to_pick_from)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + + +///Interact with an object. Could probably be moved to a generic behavior as the only unique thing is the blacklist. +/datum/bt_node/ai_behavior/bot_interact + var/target_key + var/clear_target = TRUE + +/datum/bt_node/ai_behavior/bot_interact/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/basic/living_pawn = controller.pawn + var/atom/target = controller.blackboard[target_key] + if(QDELETED(target)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + if(get_dist(living_pawn, target) > 1) + return AI_BEHAVIOR_INSTANT + living_pawn.UnarmedAttack(target, proximity_flag = TRUE) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + +/datum/bt_node/ai_behavior/bot_interact/finish_action(datum/ai_controller/basic_controller/bot/controller, succeeded) + . = ..() + var/atom/target = controller.blackboard[target_key] + if(clear_target) + controller.clear_blackboard_key(target_key) + if(!succeeded && !isnull(target)) + controller.add_to_blacklist(target) + +/// Variant that keeps the target key after interacting (caller must clear it). +/datum/bt_node/ai_behavior/bot_interact/keep_target + clear_target = FALSE + + +/// Searches GLOB.deliverybeacons for a beacon whose location matches the tag in tag_key, and sets it as target_key. +/datum/bt_node/ai_behavior/find_delivery_beacon + var/target_key + /// Blackboard key holding the location tag string to match against beacon.location. + var/tag_key + time_between_perform = 2 SECONDS + +/datum/bt_node/ai_behavior/find_delivery_beacon/perform(seconds_per_tick, datum/ai_controller/controller) + var/beacon_tag = controller.blackboard[tag_key] + if(isnull(beacon_tag)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + for(var/obj/machinery/navbeacon/beacon as anything in GLOB.deliverybeacons) + if(beacon.location != beacon_tag) + continue + controller.set_blackboard_key(target_key, beacon) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + +///Find the closest beacon and set it as the target +/datum/bt_node/ai_behavior/find_first_beacon_target + var/target_key + +/datum/bt_node/ai_behavior/find_first_beacon_target/perform(seconds_per_tick, datum/ai_controller/controller) + var/closest_distance = INFINITY + var/mob/living/basic/bot/bot_pawn = controller.pawn + var/atom/final_target + var/atom/previous_target = controller.blackboard[BB_PREVIOUS_BEACON_TARGET] + for(var/obj/machinery/navbeacon/beacon as anything in GLOB.navbeacons["[bot_pawn.z]"]) + var/dist = get_dist(bot_pawn, beacon) + if(beacon == previous_target || dist <= 1) + continue + if(dist > closest_distance) + continue + closest_distance = dist + final_target = beacon + + if(isnull(final_target)) + EVLOG_TEXT(controller, EVLOG_CATEGORY_AI_BEHAVIORS, "[bot_pawn] find_first_beacon_target: no beacon found on z=[bot_pawn.z] (previous=[previous_target])") + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + EVLOG_MAPTEXT(controller, EVLOG_CATEGORY_AI_BEHAVIORS, "[bot_pawn] first beacon target: [final_target]", get_turf(final_target), "Beacon") + EVLOG_LINES(controller, EVLOG_CATEGORY_AI_BEHAVIORS, "Beacon path", get_turf(bot_pawn), get_turf(final_target)) + controller.set_blackboard_key(target_key, final_target) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + +///Find the next beacon from a previous target and set it as the new target +/datum/bt_node/ai_behavior/find_next_beacon_target + var/target_key + time_between_perform = 5 SECONDS + +/datum/bt_node/ai_behavior/find_next_beacon_target/perform(seconds_per_tick, datum/ai_controller/basic_controller/bot/controller) + + var/mob/living/basic/bot/bot_pawn = controller.pawn + var/obj/machinery/navbeacon/prev_beacon = controller.blackboard[BB_PREVIOUS_BEACON_TARGET] + if(QDELETED(prev_beacon)) + EVLOG_TEXT(controller, EVLOG_CATEGORY_AI_BEHAVIORS, "[bot_pawn] find_next_beacon_target: previous beacon is deleted") + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + var/atom/final_target + for(var/obj/machinery/navbeacon/beacon as anything in GLOB.navbeacons["[bot_pawn.z]"]) + if(beacon.location == prev_beacon.codes[NAVBEACON_PATROL_NEXT]) + final_target = beacon + break + + if(isnull(final_target)) + EVLOG_TEXT(controller, EVLOG_CATEGORY_AI_BEHAVIORS, "[bot_pawn] find_next_beacon_target: no beacon with location=[prev_beacon.codes[NAVBEACON_PATROL_NEXT]] (prev=[prev_beacon])") + controller.clear_blackboard_key(BB_PREVIOUS_BEACON_TARGET) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + controller.set_blackboard_key(BB_PREVIOUS_BEACON_TARGET, final_target) + controller.set_blackboard_key(target_key, final_target) + return AI_BEHAVIOR_SUCCEEDED + +/datum/bt_node/ai_behavior/find_next_beacon_target/finish_action(datum/ai_controller/controller, succeeded) + . = ..() + + +/// Records the beacon as visited and clears the target key once the bot is on the same turf. +/datum/bt_node/ai_behavior/arrive_at_beacon + var/target_key + +/datum/bt_node/ai_behavior/arrive_at_beacon/perform(seconds_per_tick, datum/ai_controller/basic_controller/bot/controller) + var/obj/machinery/navbeacon/beacon = controller.blackboard[target_key] + if(QDELETED(beacon)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + if(get_dist(controller.pawn, beacon) > 0) + return AI_BEHAVIOR_INSTANT + controller.set_blackboard_key(BB_PREVIOUS_BEACON_TARGET, beacon) + controller.clear_blackboard_key(target_key) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED + + +/// Completes summon travel once the bot reaches the summon target's turf. +/datum/bt_node/ai_behavior/complete_summon_travel + var/target_key + +/datum/bt_node/ai_behavior/complete_summon_travel/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/basic/bot/bot_pawn = controller.pawn + if(QDELETED(bot_pawn)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + var/atom/target = controller.blackboard[target_key] + if(get_dist(bot_pawn, target) > 0) + return AI_BEHAVIOR_INSTANT + bot_pawn.calling_ai_ref = null + bot_pawn.update_bot_mode(new_mode = BOT_IDLE) + controller.clear_blackboard_key(target_key) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED + +///Find a valid authority to salute and set them as the target +/datum/bt_node/ai_behavior/find_valid_authority + var/target_key + +/datum/bt_node/ai_behavior/find_valid_authority/perform(seconds_per_tick, datum/ai_controller/controller) + for(var/mob/living/nearby_mob in oview(7, controller.pawn)) + if(!HAS_TRAIT(nearby_mob, TRAIT_COMMISSIONED)) + continue + controller.set_blackboard_key(target_key, nearby_mob) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + +///Salute the authority /(o.o) +/datum/bt_node/ai_behavior/salute_authority + var/target_key + var/salute_keys + +/datum/bt_node/ai_behavior/salute_authority/perform(seconds_per_tick, datum/ai_controller/controller) + if(!controller.blackboard_key_exists(target_key)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + var/list/salute_list = controller.blackboard[salute_keys] + if(!length(salute_list)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + var/mob/living/basic/bot/bot_pawn = controller.pawn + var/obj/item/our_hat = (locate(/obj/item/clothing/head) in bot_pawn) + if(our_hat) + salute_list += "tips [our_hat] at " + bot_pawn.manual_emote(pick(salute_list) + " [controller.blackboard[target_key]]!") + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + +/datum/bt_node/ai_behavior/salute_authority/finish_action(datum/ai_controller/controller, succeeded) + . = ..() + controller.clear_blackboard_key(target_key) + + +/// Travel to BB_BOT_SUMMON_TARGET if set, completing when on the same turf. +/datum/bt_node/subtree/bot_respond_to_summon + behavior_tree_json = "code/datums/ai/bots/bot_respond_to_summon.bt.json" + +/// Salute any commissioned officer in range +/datum/bt_node/subtree/bot_salute_authority + behavior_tree_json = "code/datums/ai/bots/bot_salute_authority.bt.json" + +/** + * Patrol to navbeacons in sequence when autopatrol is enabled and not on cooldown. + * Priority: travel to current target -> find next in chain -> find first (nearest) beacon. + */ +/datum/bt_node/subtree/bot_patrol + behavior_tree_json = "code/datums/ai/bots/bot_patrol.bt.json" + + +#undef BOT_NO_BEACON_PATH_PENALTY diff --git a/code/datums/ai/bt_viewer.dm b/code/datums/ai/bt_viewer.dm new file mode 100644 index 00000000000..04106fca6d1 --- /dev/null +++ b/code/datums/ai/bt_viewer.dm @@ -0,0 +1,161 @@ +GLOBAL_DATUM_INIT(bt_viewer, /datum/bt_viewer, new()) + +/datum/bt_viewer + /// The controller currently being viewed. + var/datum/ai_controller/viewing_controller = null + /// The mob owning the controller. + var/mob/viewing_mob = null + /// TRUE while waiting for admin to click a mob. + var/awaiting_pick = FALSE + /// The admin waiting to pick, used to unregister the click signal. + var/mob/awaiting_pick_user = null + +/datum/bt_viewer/Destroy() + _clear_target() + _end_pick() + return ..() + +/datum/bt_viewer/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "BehaviorTreeViewer", "Behavior Tree Viewer") + ui.open() + +/datum/bt_viewer/ui_close(mob/user) + . = ..() + if(awaiting_pick_user == user) + _end_pick() + +/datum/bt_viewer/ui_state(mob/user) + return ADMIN_STATE(R_DEBUG) + +/datum/bt_viewer/ui_assets(mob/user) + return list(get_asset_datum(/datum/asset/simple/plane_background)) + +/datum/bt_viewer/ui_data(mob/user) + var/list/data = list() + data["mob_name"] = viewing_mob ? viewing_mob.name : null + data["controller_type"] = viewing_controller ? "[viewing_controller.type]" : null + data["active_execution_index"] = viewing_controller ? viewing_controller.active_execution_index : 0 + if(viewing_controller?.bt_execution_log != null) + data["fired_indices"] = viewing_controller.bt_execution_log.Copy() + viewing_controller.bt_execution_log.Cut() //Clear the list every time we update + else + data["fired_indices"] = list() + data["awaiting_pick"] = awaiting_pick + if(viewing_controller) + var/list/root_indices = list() + var/list/node_list = list() + collect_nodes(root_indices, node_list) + data["roots"] = root_indices + data["nodes"] = node_list + var/list/bb_entries = list() + var/list/bb = viewing_controller.blackboard + for(var/key in bb) + var/value = bb[key] + var/str_val + if(isnull(value)) + str_val = "null" + else if(islist(value)) + str_val = "list([length(value)])" + else + str_val = "[value]" + bb_entries += list(list("key" = key, "value" = str_val)) + data["blackboard"] = bb_entries + else + data["roots"] = list() + data["nodes"] = list() + data["blackboard"] = list() + return data + +/datum/bt_viewer/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) + . = ..() + if(.) + return + var/mob/user = ui.user + switch(action) + if("pick_target") + _end_pick() + awaiting_pick = TRUE + awaiting_pick_user = user + RegisterSignal(user, COMSIG_MOB_CLICKON, PROC_REF(on_pick_click)) + return TRUE + if("clear") + _clear_target() + return TRUE + +/datum/bt_viewer/proc/on_pick_click(mob/source, atom/clicked, list/modifiers) + SIGNAL_HANDLER + _end_pick() + + var/atom/target = clicked + if(!target.ai_controller) + return NONE + set_target(target) + return NONE + +/datum/bt_viewer/proc/set_target(mob/target) + _clear_target() + viewing_mob = target + viewing_controller = target.ai_controller + viewing_controller.bt_execution_log = list() + RegisterSignal(viewing_mob, COMSIG_PREQDELETED, PROC_REF(on_mob_deleted)) + +/datum/bt_viewer/proc/on_mob_deleted(datum/source) + SIGNAL_HANDLER + _clear_target() + +/datum/bt_viewer/proc/_clear_target() + if(viewing_mob) + UnregisterSignal(viewing_mob, COMSIG_PREQDELETED) + if(viewing_controller) + viewing_controller.bt_execution_log = null + viewing_mob = null + viewing_controller = null + +/datum/bt_viewer/proc/_end_pick() + awaiting_pick = FALSE + if(awaiting_pick_user) + UnregisterSignal(awaiting_pick_user, COMSIG_MOB_CLICKON) + awaiting_pick_user = null + +/datum/bt_viewer/proc/collect_nodes(list/root_indices, list/node_list) + var/priority = 1 + for(var/datum/bt_node/root as anything in viewing_controller.behavior_nodes) + root_indices += root.execution_index + _collect_node(root, node_list, priority++) + +// Recursively adds node and all descendants to node_list as flat entries. +// Each entry has exec_index as its unique key, with children as a flat list of child exec_indices. +/datum/bt_viewer/proc/_collect_node(datum/bt_node/node, list/node_list, priority_index) + var/exec = node.execution_index || 0 + var/last = node.last_execution_index || 0 + + var/list/node_data = list( + "exec_index" = exec, + "label" = node.label, + "node_type" = node.node_type, + "priority" = priority_index, + ) + if(last != exec) + node_data["last_exec_index"] = last + + if(node.node_type == BT_NODE_DECORATOR) + var/datum/bt_node/decorator/dec = node + if(dec.observer_abort) + node_data["observer_abort"] = dec.observer_abort + if(dec.invert) + node_data["invert"] = TRUE + + var/list/children = node.get_children() + if(length(children)) + var/list/child_indices = list() + for(var/i in 1 to length(children)) + var/datum/bt_node/child = children[i] + if(!child) + continue + child_indices += child.execution_index + _collect_node(child, node_list, i) + node_data["children"] = child_indices + + node_list += list(node_data) diff --git a/code/datums/ai/cursed/cursed.bt.json b/code/datums/ai/cursed/cursed.bt.json new file mode 100644 index 00000000000..9ec75cdfe20 --- /dev/null +++ b/code/datums/ai/cursed/cursed.bt.json @@ -0,0 +1,72 @@ +{ + "dm_type": "/datum/ai_controller/cursed", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_HAUNT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_FAIL_ON_FAILURE", + "loop_delay": 20, + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/throw_attack/haunted", + "vars": { + "target_key": "BB_HAUNT_TARGET", + "throw_count_key": "BB_HAUNTED_THROW_ATTEMPT_COUNT", + "haunt_list_key": "BB_TO_HAUNT_LIST" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_HAUNT_TARGET", + "required_dist": 3, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/idle_ghost_item" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_CURSE_TARGET", + "target_source": "/datum/target_source/oview_single_type/carbon_mob", + "targeting_strategy": "/datum/targeting_strategy/anything", + "vision_range": "CURSED_VIEW_RANGE" + } + } + ] +} diff --git a/code/datums/ai/cursed/cursed_behaviors.dm b/code/datums/ai/cursed/cursed_behaviors.dm index 63e7310f968..76c991e08d7 100644 --- a/code/datums/ai/cursed/cursed_behaviors.dm +++ b/code/datums/ai/cursed/cursed_behaviors.dm @@ -1,7 +1,8 @@ -/datum/ai_behavior/item_move_close_and_attack/ghostly/cursed +/// Cursed variant: keeps target if still in range when throws are exhausted. +/datum/bt_node/ai_behavior/throw_attack/cursed -/datum/ai_behavior/item_move_close_and_attack/ghostly/cursed/reset_blackboard(datum/ai_controller/controller, succeeded, target_key, throw_count_key) - var/atom/throw_target = controller.blackboard[target_key] - //dropping our target from the blackboard if they are no longer a valid target after the attack behavior +/datum/bt_node/ai_behavior/throw_attack/cursed/on_throws_exhausted(datum/ai_controller/controller, atom/throw_target, target_key, throw_count_key) + controller.set_blackboard_key(throw_count_key, 0) if(get_dist(throw_target, controller.pawn) > CURSED_VIEW_RANGE) controller.clear_blackboard_key(target_key) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED diff --git a/code/datums/ai/cursed/cursed_controller.dm b/code/datums/ai/cursed/cursed_controller.dm index aa32496f357..ba38888c3ea 100644 --- a/code/datums/ai/cursed/cursed_controller.dm +++ b/code/datums/ai/cursed/cursed_controller.dm @@ -6,14 +6,13 @@ * Added by /datum/element/cursed, and as such will try to remove this element and go dormant when it finds a victim to curse */ /datum/ai_controller/cursed + behavior_tree_json = "code/datums/ai/cursed/cursed.bt.json" movement_delay = 0.4 SECONDS blackboard = list( BB_CURSE_TARGET, BB_TARGET_SLOT, BB_CURSED_THROW_ATTEMPT_COUNT ) - planning_subtrees = list(/datum/ai_planning_subtree/cursed) - idle_behavior = /datum/idle_behavior/idle_ghost_item /datum/ai_controller/cursed/TryPossessPawn(atom/new_pawn) if(!isitem(new_pawn)) diff --git a/code/datums/ai/cursed/cursed_subtrees.dm b/code/datums/ai/cursed/cursed_subtrees.dm deleted file mode 100644 index dd33312900a..00000000000 --- a/code/datums/ai/cursed/cursed_subtrees.dm +++ /dev/null @@ -1,13 +0,0 @@ -/datum/ai_planning_subtree/cursed/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/obj/item/item_pawn = controller.pawn - - //make sure we have a target - var/mob/living/carbon/curse_target = controller.blackboard[BB_CURSE_TARGET] - if(!curse_target) - controller.queue_behavior(/datum/ai_behavior/find_and_set, BB_CURSE_TARGET, /mob/living/carbon, CURSED_VIEW_RANGE) - return - //make sure attack is valid - if(get_dist(curse_target, item_pawn) > CURSED_VIEW_RANGE) - controller.clear_blackboard_key(BB_CURSE_TARGET) - return - controller.queue_behavior(/datum/ai_behavior/item_move_close_and_attack/ghostly/cursed, BB_CURSE_TARGET) diff --git a/code/datums/ai/dog/dog.bt.json b/code/datums/ai/dog/dog.bt.json new file mode 100644 index 00000000000..102947488c0 --- /dev/null +++ b/code/datums/ai/dog/dog.bt.json @@ -0,0 +1,44 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/dog", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree", + "override_id": "SUBPLAN_ID_PET_COMMAND" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/dog_harassment" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "1 SECONDS", + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_FAIL_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/idle_dog" + } + ] + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop" + } + ] + } + ] +} diff --git a/code/datums/ai/dog/dog_behaviors.dm b/code/datums/ai/dog/dog_behaviors.dm deleted file mode 100644 index dc031a1d3f1..00000000000 --- a/code/datums/ai/dog/dog_behaviors.dm +++ /dev/null @@ -1,49 +0,0 @@ -/** - * Pursue the target, growl if we're close, and bite if we're adjacent - * Dogs are actually not very aggressive and won't attack unless you approach them - * Adds a floor to the melee damage of the dog, as most pet dogs don't actually have any melee strength - */ -/datum/ai_behavior/basic_melee_attack/dog - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_MOVE_AND_PERFORM - required_distance = 3 - -/datum/ai_behavior/basic_melee_attack/dog/perform(seconds_per_tick, datum/ai_controller/controller, target_key, targeting_strategy_key, hiding_location_key) - controller.behavior_cooldowns[src] = world.time + get_cooldown(controller) - var/mob/living/living_pawn = controller.pawn - if(!(isturf(living_pawn.loc) || HAS_TRAIT(living_pawn, TRAIT_AI_BAGATTACK))) // Void puppies can attack from inside bags - return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED - - // Unfortunately going to repeat this check in parent call but what can you do - var/atom/target = controller.blackboard[target_key] - var/datum/targeting_strategy/targeting_strategy = GET_TARGETING_STRATEGY(controller.blackboard[targeting_strategy_key]) - if (!targeting_strategy.can_attack(living_pawn, target)) - return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED - - if (!living_pawn.Adjacent(target)) - growl_at(living_pawn, target, seconds_per_tick) - return AI_BEHAVIOR_INSTANT - - if(!controller.blackboard[BB_DOG_HARASS_HARM]) - paw_harmlessly(living_pawn, target, seconds_per_tick) - return AI_BEHAVIOR_INSTANT - - . = ..() // Bite time - - return AI_BEHAVIOR_DELAY - -/// Swat at someone we don't like but won't hurt -/datum/ai_behavior/basic_melee_attack/dog/proc/paw_harmlessly(mob/living/living_pawn, atom/target, seconds_per_tick) - if(!SPT_PROB(20, seconds_per_tick)) - return - living_pawn.do_attack_animation(target, ATTACK_EFFECT_DISARM) - playsound(target, 'sound/items/weapons/thudswoosh.ogg', 50, TRUE, -1) - target.visible_message(span_danger("[living_pawn] paws ineffectually at [target]!"), span_danger("[living_pawn] paws ineffectually at you!")) - -/// Let them know we mean business -/datum/ai_behavior/basic_melee_attack/dog/proc/growl_at(mob/living/living_pawn, atom/target, seconds_per_tick) - if(!SPT_PROB(15, seconds_per_tick)) - return - living_pawn.manual_emote("[pick("barks", "growls", "stares")] menacingly at [target]!") - if(!SPT_PROB(40, seconds_per_tick)) - return - playsound(living_pawn, SFX_GROWL, 50, TRUE, -1) diff --git a/code/datums/ai/dog/dog_bt.dm b/code/datums/ai/dog/dog_bt.dm new file mode 100644 index 00000000000..fc5573faef7 --- /dev/null +++ b/code/datums/ai/dog/dog_bt.dm @@ -0,0 +1,113 @@ + +/** + * BT version of basic_melee_attack/dog. + * When adjacent and BB_DOG_HARASS_HARM = FALSE: paws harmlessly (animation, no damage). + * When adjacent and BB_DOG_HARASS_HARM = TRUE: bites normally. + * Returns FAILURE if targeting strategy rejects the target. + */ +/datum/bt_node/ai_behavior/basic_melee_attack/dog + +/datum/bt_node/ai_behavior/basic_melee_attack/dog/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/living_pawn = controller.pawn + if(!(isturf(living_pawn.loc) || HAS_TRAIT(living_pawn, TRAIT_AI_BAGATTACK))) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + var/atom/target = controller.blackboard[target_key] + + if(!ispath(targeting_strategy)) + targeting_strategy = controller.blackboard[targeting_strategy] + + var/datum/targeting_strategy/strategy = GET_TARGETING_STRATEGY(targeting_strategy) + if(QDELETED(target) || !strategy?.is_valid_target(living_pawn, target)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + if(!controller.blackboard[BB_DOG_HARASS_HARM]) + paw_harmlessly(living_pawn, target, seconds_per_tick) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + + return ..() + +/// Swat at someone we don't like but won't hurt +/datum/bt_node/ai_behavior/basic_melee_attack/dog/proc/paw_harmlessly(mob/living/living_pawn, atom/target, seconds_per_tick) + if(!SPT_PROB(20, seconds_per_tick)) + return + living_pawn.do_attack_animation(target, ATTACK_EFFECT_DISARM) + playsound(target, 'sound/items/weapons/thudswoosh.ogg', 50, TRUE, -1) + target.visible_message(span_danger("[living_pawn] paws ineffectually at [target]!"), span_danger("[living_pawn] paws ineffectually at you!")) + + +/** + * Searches for a target with TRAIT_HATED_BY_DOGS within 2 tiles. Sets BB_DOG_HARASS_TARGET and + * BB_DOG_HARASS_HARM = FALSE if found. Returns FAILURE if no valid target is found. + * Combine with a cooldown decorator or embed the SPT_PROB gate in the parent tree. + */ +/datum/bt_node/ai_behavior/find_hated_dog_target + var/target_key + var/targeting_strategy = BB_TARGETING_STRATEGY + +/datum/bt_node/ai_behavior/find_hated_dog_target/perform(seconds_per_tick, datum/ai_controller/controller) + if(!SPT_PROB(10, seconds_per_tick)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + var/mob/living/dog = controller.pawn + + if(!ispath(targeting_strategy)) + targeting_strategy = controller.blackboard[targeting_strategy] + + + var/datum/targeting_strategy/strategy = GET_TARGETING_STRATEGY(targeting_strategy) + for(var/mob/living/iter_living in oview(2, dog)) + if(iter_living.stat != CONSCIOUS || !HAS_TRAIT(iter_living, TRAIT_HATED_BY_DOGS)) + continue + if(!isnull(dog.buckled)) + dog.audible_message(span_notice("[dog] growls at [iter_living], yet [dog.p_they()] [dog.p_are()] much too comfy to move."), hearing_distance = COMBAT_MESSAGE_RANGE) + continue + if(!strategy?.is_valid_target(dog, iter_living)) + continue + dog.audible_message(span_warning("[dog] growls at [iter_living], seemingly annoyed by [iter_living.p_their()] presence."), hearing_distance = COMBAT_MESSAGE_RANGE) + controller.set_blackboard_key(target_key, iter_living) + controller.set_blackboard_key(BB_DOG_HARASS_HARM, FALSE) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + + controller.clear_blackboard_key(target_key) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + +/** + * Dog-specific idle: random walks at a dog-appropriate rate, and occasionally spins/tail-chases. + * Reads BB_DOG_IS_SLOW to determine movement chance. Always returns BT_RUNNING. + */ +/datum/bt_node/ai_behavior/idle_dog + +/datum/bt_node/ai_behavior/idle_dog/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/living_pawn = controller.pawn + + var/obj/item/carry_item = controller.blackboard[BB_SIMPLE_CARRY_ITEM] + if(carry_item && SPT_PROB(5, seconds_per_tick)) + living_pawn.visible_message(span_notice("[living_pawn] gently teethes on \the [carry_item] in [living_pawn.p_their()] mouth."), vision_distance = COMBAT_MESSAGE_RANGE) + + var/move_chance = controller.blackboard[BB_DOG_IS_SLOW] ? 2.5 : 5 + if(isturf(living_pawn.loc) && !living_pawn.pulledby) + if(SPT_PROB(move_chance, seconds_per_tick) && (living_pawn.mobility_flags & MOBILITY_MOVE)) + var/move_dir = pick(GLOB.alldirs) + living_pawn.Move(get_step(living_pawn, move_dir), move_dir) + else if(SPT_PROB(2, seconds_per_tick)) + living_pawn.manual_emote(pick("dances around.", "chases [living_pawn.p_their()] tail!")) + living_pawn.AddComponent(/datum/component/spinny) + + return AI_BEHAVIOR_DELAY + +///Dog speech updates the BB keys based on the dogs swag +/datum/bt_node/ai_behavior/random_speech_blackboard/dog_random_speech + +/datum/bt_node/ai_behavior/random_speech_blackboard/dog/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/basic/pet/dog/dog_pawn = controller.pawn + if(!istype(dog_pawn)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + dog_pawn.update_dog_speak_blackboard(controller) + return ..() + + +/// Dog harassment: find a TRAIT_HATED_BY_DOGS target nearby, then approach and paw/bite it. +/datum/bt_node/subtree/dog_harassment + behavior_tree_json = "code/datums/ai/dog/dog_harassment.bt.json" diff --git a/code/datums/ai/dog/dog_controller.dm b/code/datums/ai/dog/dog_controller.dm index cb17546bfbb..65d6c05a9db 100644 --- a/code/datums/ai/dog/dog_controller.dm +++ b/code/datums/ai/dog/dog_controller.dm @@ -5,17 +5,14 @@ BB_PET_TARGETING_STRATEGY = /datum/targeting_strategy/basic/not_friends, ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_dog - planning_subtrees = list( - /datum/ai_planning_subtree/random_speech/dog, - /datum/ai_planning_subtree/pet_planning, - /datum/ai_planning_subtree/dog_harassment, - ) + behavior_tree_json = "code/datums/ai/dog/dog.bt.json" /** * Same thing but with make tiny corgis and use access cards. */ /datum/ai_controller/basic_controller/dog/corgi + behavior_tree_json = "code/datums/ai/dog/dog_corgi.bt.json" + blackboard = list( BB_DOG_HARASS_HARM = TRUE, BB_VISION_RANGE = AI_DOG_VISION_RANGE, @@ -28,17 +25,6 @@ BB_BABIES_CHILD_TYPES = list(/mob/living/basic/pet/dog/corgi/puppy), ) - planning_subtrees = list( - /datum/ai_planning_subtree/random_speech/dog, - /datum/ai_planning_subtree/make_babies, // Ian WILL prioritise sex over following your instructions - /datum/ai_planning_subtree/pet_planning, - /datum/ai_planning_subtree/dog_harassment, - // Find targets to run away from (uses the targeting strategy from above) - /datum/ai_planning_subtree/simple_find_target, - // Flee from that target - /datum/ai_planning_subtree/flee_target, - ) - /datum/ai_controller/basic_controller/dog/corgi/get_access() var/mob/living/basic/pet/dog/corgi/corgi_pawn = pawn if(!istype(corgi_pawn)) @@ -54,11 +40,5 @@ BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/holding_object, // With tongs in hand! BB_TARGET_HELD_ITEM = /obj/item/kitchen/tongs, - ) - planning_subtrees = list( - /datum/ai_planning_subtree/random_speech/dog, - /datum/ai_planning_subtree/pet_planning, - /datum/ai_planning_subtree/dog_harassment, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/flee_target, + BB_FUCKS = FALSE, // Puppies don't ) diff --git a/code/datums/ai/dog/dog_corgi.bt.json b/code/datums/ai/dog/dog_corgi.bt.json new file mode 100644 index 00000000000..3248cba5b99 --- /dev/null +++ b/code/datums/ai/dog/dog_corgi.bt.json @@ -0,0 +1,74 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/dog/corgi", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree", + "override_id": "SUBPLAN_ID_PET_COMMAND" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/dog_harassment" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/run_away_from_target" + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/make_babies" + }, + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_FAIL_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/idle_dog" + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/find_partner" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop" + } + ] + } + ] +} diff --git a/code/datums/ai/dog/dog_harassment.bt.json b/code/datums/ai/dog/dog_harassment.bt.json new file mode 100644 index 00000000000..eca37f0a690 --- /dev/null +++ b/code/datums/ai/dog/dog_harassment.bt.json @@ -0,0 +1,39 @@ +{ + "dm_type": "/datum/bt_node/subtree/dog_harassment", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/random_chance", + "vars": { + "chance": 0.1 + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/find_hated_dog_target", + "vars": { + "target_key": "BB_DOG_HARASS_TARGET", + "targeting_strategy": "BB_PET_TARGETING_STRATEGY" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_DOG_HARASS_TARGET", + "required_dist": 1, + "finish_on_arrival": true + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack/dog", + "vars": { + "target_key": "BB_DOG_HARASS_TARGET", + "targeting_strategy": "BB_PET_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } +} diff --git a/code/datums/ai/dog/dog_subtrees.dm b/code/datums/ai/dog/dog_subtrees.dm deleted file mode 100644 index 74c075adad3..00000000000 --- a/code/datums/ai/dog/dog_subtrees.dm +++ /dev/null @@ -1,38 +0,0 @@ -/// Find someone we don't like and annoy them -/datum/ai_planning_subtree/dog_harassment - -/datum/ai_planning_subtree/dog_harassment/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(!SPT_PROB(10, seconds_per_tick)) - return - controller.queue_behavior(/datum/ai_behavior/find_hated_dog_target, BB_DOG_HARASS_TARGET, BB_PET_TARGETING_STRATEGY) - var/atom/harass_target = controller.blackboard[BB_DOG_HARASS_TARGET] - if (isnull(harass_target)) - return - - controller.queue_behavior(/datum/ai_behavior/basic_melee_attack/dog, BB_DOG_HARASS_TARGET, BB_PET_TARGETING_STRATEGY) - return SUBTREE_RETURN_FINISH_PLANNING - -/datum/ai_behavior/find_hated_dog_target - -/datum/ai_behavior/find_hated_dog_target/setup(datum/ai_controller/controller, target_key, targeting_strategy_key) - . = ..() - var/mob/living/dog = controller.pawn - var/datum/targeting_strategy/targeting_strategy = GET_TARGETING_STRATEGY(controller.blackboard[targeting_strategy_key]) - for(var/mob/living/iter_living in oview(2, dog)) - if(iter_living.stat != CONSCIOUS || !HAS_TRAIT(iter_living, TRAIT_HATED_BY_DOGS)) - continue - if(!isnull(dog.buckled)) - dog.audible_message(span_notice("[dog] growls at [iter_living], yet [dog.p_they()] [dog.p_are()] much too comfy to move."), hearing_distance = COMBAT_MESSAGE_RANGE) - continue - if(!targeting_strategy.can_attack(dog, iter_living)) - continue - - dog.audible_message(span_warning("[dog] growls at [iter_living], seemingly annoyed by [iter_living.p_their()] presence."), hearing_distance = COMBAT_MESSAGE_RANGE) - controller.set_blackboard_key(target_key, iter_living) - controller.set_blackboard_key(BB_DOG_HARASS_HARM, FALSE) - return TRUE - - controller.clear_blackboard_key(target_key) - -/datum/ai_behavior/find_hated_dog_target/perform(seconds_per_tick, datum/ai_controller/controller, target_key) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED diff --git a/code/datums/ai/generic/find_and_set.dm b/code/datums/ai/generic/find_and_set.dm deleted file mode 100644 index ced56912c91..00000000000 --- a/code/datums/ai/generic/find_and_set.dm +++ /dev/null @@ -1,223 +0,0 @@ -/**find and set - * Finds an item near themselves, sets a blackboard key as it. Very useful for ais that need to use machines or something. - * if you want to do something more complicated than find a single atom, change the search_tactic() proc - * cool tip: search_tactic() can set lists - */ -/datum/ai_behavior/find_and_set - action_cooldown = 2 SECONDS - -/datum/ai_behavior/find_and_set/perform(seconds_per_tick, datum/ai_controller/controller, set_key, locate_path, search_range) - if (controller.blackboard_key_exists(set_key)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - if(QDELETED(controller.pawn)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - var/find_this_thing = search_tactic(controller, locate_path, search_range) - if(isnull(find_this_thing)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - EVLOG_MAPTEXT(controller, EVLOG_CATEGORY_AI_TARGETING, "[controller.pawn] has selected [find_this_thing] as a target for blackboard key [set_key]! Behavior: [src]", get_turf(find_this_thing), "Target: [find_this_thing]") - EVLOG_LINES(controller, EVLOG_CATEGORY_AI_TARGETING, "Line to target", get_turf(controller.pawn), get_turf(find_this_thing)) - controller.set_blackboard_key(set_key, find_this_thing) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -/datum/ai_behavior/find_and_set/proc/search_tactic(datum/ai_controller/controller, locate_path, search_range = 3) - return locate(locate_path) in oview(search_range, controller.pawn) - -/** - * Variant of find and set that fails if the living pawn doesn't hold something - */ -/datum/ai_behavior/find_and_set/pawn_must_hold_item - -/datum/ai_behavior/find_and_set/pawn_must_hold_item/search_tactic(datum/ai_controller/controller) - var/mob/living/living_pawn = controller.pawn - if(!living_pawn.get_num_held_items()) - return //we want to fail the search if we don't have something held - return ..() - -/** - * Variant of find and set that also requires the item to be edible. checks hands too - */ -/datum/ai_behavior/find_and_set/food_or_drink - var/force_find_drinks = FALSE - -/datum/ai_behavior/find_and_set/food_or_drink/search_tactic(datum/ai_controller/controller, locate_path, search_range = SEARCH_TACTIC_DEFAULT_RANGE) - var/mob/living/living_pawn = controller.pawn - var/find_drinks = force_find_drinks || controller.blackboard[BB_IGNORE_DRINKS] || FALSE - - for(var/atom/held_candidate in living_pawn.held_items) - if(is_food_or_drink(controller, held_candidate, find_drinks)) - return held_candidate - - for(var/atom/local_candidate in oview(search_range, controller.pawn)) - if(is_food_or_drink(controller, local_candidate, find_drinks) && istype(local_candidate, locate_path)) - return local_candidate - - return null - -/datum/ai_behavior/find_and_set/food_or_drink/proc/is_food_or_drink(datum/ai_controller/controller, obj/item/thing, find_drinks = FALSE) - return is_food(thing) || (find_drinks && is_drink(thing)) - -/datum/ai_behavior/find_and_set/food_or_drink/proc/is_food(obj/item/thing) - if(IS_EDIBLE(thing)) - return TRUE - if(istype(thing, /obj/item/reagent_containers/cup/bowl)) - return thing.reagents.total_volume > 0 - return FALSE - -/datum/ai_behavior/find_and_set/food_or_drink/proc/is_drink(obj/item/thing) - if(istype(thing, /obj/item/reagent_containers/cup/glass)) - return thing.reagents.total_volume > 0 - return FALSE - -/datum/ai_behavior/find_and_set/food_or_drink/to_eat - -/datum/ai_behavior/find_and_set/food_or_drink/to_serve - force_find_drinks = TRUE - -/** - * Variant of find and set that only checks in hands, search range should be excluded for this - */ -/datum/ai_behavior/find_and_set/in_hands - -/datum/ai_behavior/find_and_set/in_hands/search_tactic(datum/ai_controller/controller, locate_path) - var/mob/living/living_pawn = controller.pawn - return locate(locate_path) in living_pawn.held_items - -/datum/ai_behavior/find_and_set/in_hands/given_list - -/datum/ai_behavior/find_and_set/in_hands/given_list/search_tactic(datum/ai_controller/controller, locate_paths) - var/list/found = typecache_filter_list(controller.pawn, locate_paths) - if(length(found)) - return pick(found) - -/** - * Variant of find and set that takes a list of things to find. - */ -/datum/ai_behavior/find_and_set/in_list - -/datum/ai_behavior/find_and_set/in_list/search_tactic(datum/ai_controller/controller, locate_paths, search_range = SEARCH_TACTIC_DEFAULT_RANGE) - var/list/found = typecache_filter_list(oview(search_range, controller.pawn), locate_paths) - if(length(found)) - return pick(found) - -/// Like find_and_set/in_list, but we return the turf location of the item instead of the item itself. -/datum/ai_behavior/find_and_set/in_list/turf_location - -/datum/ai_behavior/find_and_set/in_list/turf_location/search_tactic(datum/ai_controller/controller, locate_paths, search_range) - . = ..() - if(isnull(.)) - return null - - return get_turf(.) - -/** - * Variant of find and set which returns an object which can be animated with a staff of change - */ -/datum/ai_behavior/find_and_set/animatable - -/datum/ai_behavior/find_and_set/animatable/search_tactic(datum/ai_controller/controller, locate_path, search_range = SEARCH_TACTIC_DEFAULT_RANGE) - var/mob/living/living_pawn = controller.pawn - - var/list/nearby_items = list() - for (var/obj/new_friend in oview(search_range, controller.pawn)) - if (!isitem(new_friend) && !isstructure(new_friend)) - continue - if (is_type_in_list(new_friend, GLOB.animatable_blacklist)) - continue - if (living_pawn.see_invisible < new_friend.invisibility) - continue - nearby_items += new_friend - - if(nearby_items.len) - return pick(nearby_items) - -/** - * Variant of find and set which returns the nearest wall which isn't invulnerable - */ -/datum/ai_behavior/find_and_set/nearest_wall - -/datum/ai_behavior/find_and_set/nearest_wall/search_tactic(datum/ai_controller/controller, locate_path, search_range = SEARCH_TACTIC_DEFAULT_RANGE) - var/mob/living/living_pawn = controller.pawn - - var/list/nearby_walls = list() - for (var/turf/closed/new_wall in oview(search_range, controller.pawn)) - if (isindestructiblewall(new_wall)) - continue - nearby_walls += new_wall - - if(nearby_walls.len) - return get_closest_atom(/turf/closed/, nearby_walls, living_pawn) - -/** - * Variant of find and set which returns corpses who share your faction - */ -/datum/ai_behavior/find_and_set/friendly_corpses - -/datum/ai_behavior/find_and_set/friendly_corpses/search_tactic(datum/ai_controller/controller, locate_path, search_range = SEARCH_TACTIC_DEFAULT_RANGE) - var/mob/living/living_pawn = controller.pawn - var/list/nearby_bodies = list() - for (var/mob/living/dead_pal in oview(search_range, controller.pawn)) - if (!isturf(dead_pal.loc)) - continue - if (!dead_pal.stat || dead_pal.health > 0) - continue - if (living_pawn.see_invisible < dead_pal.invisibility) - continue - if (!living_pawn.faction_check_atom(dead_pal)) - continue - nearby_bodies += dead_pal - - if (nearby_bodies.len) - return pick(nearby_bodies) - -/** - * A variant that looks for a human who is not dead or incapacitated, and has a mind - */ -/datum/ai_behavior/find_and_set/conscious_person - -/datum/ai_behavior/find_and_set/conscious_person/search_tactic(datum/ai_controller/controller, locate_path, search_range = SEARCH_TACTIC_DEFAULT_RANGE) - var/list/customers = list() - for(var/mob/living/carbon/human/target in oview(search_range, controller.pawn)) - if(IS_DEAD_OR_INCAP(target) || !target.mind) - continue - customers += target - - if(customers.len) - return pick(customers) - - return null - -/datum/ai_behavior/find_and_set/nearby_friends - action_cooldown = 2 SECONDS - -/datum/ai_behavior/find_and_set/nearby_friends/search_tactic(datum/ai_controller/controller, locate_path, search_range = SEARCH_TACTIC_DEFAULT_RANGE) - var/atom/friend = locate(/mob/living/carbon/human) in oview(search_range, controller.pawn) - - if(isnull(friend)) - return null - - var/mob/living/living_pawn = controller.pawn - var/potential_friend = living_pawn.has_ally(friend) ? friend : null - return potential_friend - - -/datum/ai_behavior/find_and_set/in_list/turf_types - -/datum/ai_behavior/find_and_set/in_list/turf_types/search_tactic(datum/ai_controller/controller, locate_paths, search_range = SEARCH_TACTIC_DEFAULT_RANGE) - var/list/found = RANGE_TURFS(search_range, controller.pawn) - shuffle_inplace(found) - for(var/turf/possible_turf as anything in found) - if(!is_type_in_typecache(possible_turf, locate_paths)) - continue - if(can_see(controller.pawn, possible_turf, search_range)) - return possible_turf - return null - -/datum/ai_behavior/find_and_set/in_list/closest_turf - -/datum/ai_behavior/find_and_set/in_list/closest_turf/search_tactic(datum/ai_controller/controller, locate_paths, search_range = SEARCH_TACTIC_DEFAULT_RANGE) - var/list/found = RANGE_TURFS(search_range, controller.pawn) - for(var/turf/possible_turf as anything in found) - if(!is_type_in_typecache(possible_turf, locate_paths) || !can_see(controller.pawn, possible_turf, search_range)) - found -= possible_turf - return (length(found)) ? get_closest_atom(/turf, found, controller.pawn) : null diff --git a/code/datums/ai/generic/generic_behaviors.dm b/code/datums/ai/generic/generic_behaviors.dm deleted file mode 100644 index d0e8c25c9d7..00000000000 --- a/code/datums/ai/generic/generic_behaviors.dm +++ /dev/null @@ -1,377 +0,0 @@ - -/datum/ai_behavior/resist/perform(seconds_per_tick, datum/ai_controller/controller) - var/mob/living/living_pawn = controller.pawn - living_pawn.ai_controller.set_blackboard_key(BB_RESISTING, TRUE) - living_pawn.execute_resist() - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -/datum/ai_behavior/battle_screech - ///List of possible screeches the behavior has - var/list/screeches - -/datum/ai_behavior/battle_screech/perform(seconds_per_tick, datum/ai_controller/controller) - var/mob/living/living_pawn = controller.pawn - INVOKE_ASYNC(living_pawn, TYPE_PROC_REF(/mob, emote), pick(screeches)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -///Moves to target then finishes -/datum/ai_behavior/move_to_target - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT - -/datum/ai_behavior/move_to_target/perform(seconds_per_tick, datum/ai_controller/controller) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - - -/datum/ai_behavior/break_spine - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH - action_cooldown = 0.7 SECONDS - var/give_up_distance = 10 - -/datum/ai_behavior/break_spine/setup(datum/ai_controller/controller, target_key) - . = ..() - var/atom/target = controller.blackboard[target_key] - if(QDELETED(target)) - return FALSE - set_movement_target(controller, target) - -/datum/ai_behavior/break_spine/perform(seconds_per_tick, datum/ai_controller/controller, target_key) - var/mob/living/batman = controller.blackboard[target_key] - var/mob/living/big_guy = controller.pawn //he was molded by the darkness - - if(QDELETED(batman) || get_dist(batman, big_guy) >= give_up_distance) - return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED - - if(batman.stat != CONSCIOUS) - return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED - - big_guy.start_pulling(batman) - big_guy.face_atom(batman) - - batman.visible_message(span_warning("[batman] gets a slightly too tight hug from [big_guy]!"), span_userdanger("You feel your body break as [big_guy] embraces you!")) - - for(var/zone in GLOB.all_body_zones - BODY_ZONE_HEAD) - batman.apply_damage(15, BRUTE, zone, wound_bonus = 35) - - return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED - -/datum/ai_behavior/break_spine/finish_action(datum/ai_controller/controller, succeeded, target_key) - if(succeeded) - var/mob/living/bane = controller.pawn - if(QDELETED(bane)) // pawn can be null at this point - return ..() - bane.stop_pulling() - controller.clear_blackboard_key(target_key) - return ..() - -/// Use in hand the currently held item -/datum/ai_behavior/use_in_hand - behavior_flags = AI_BEHAVIOR_MOVE_AND_PERFORM - - -/datum/ai_behavior/use_in_hand/perform(seconds_per_tick, datum/ai_controller/controller) - var/mob/living/pawn = controller.pawn - var/obj/item/held = pawn.get_active_held_item() - if(!held) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - pawn.activate_hand() - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -/// Use the currently held item, or unarmed, on a weakref to an object in the world -/datum/ai_behavior/use_on_object - required_distance = 1 - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH - -/datum/ai_behavior/use_on_object/setup(datum/ai_controller/controller, target_key) - . = ..() - var/atom/target = controller.blackboard[target_key] - if(QDELETED(target)) - return FALSE - if(target == controller.pawn) // this can sometimes end up as ourselves, in which case there is no reason to move - return FALSE - set_movement_target(controller, target) - -/datum/ai_behavior/use_on_object/perform(seconds_per_tick, datum/ai_controller/controller, target_key) - var/atom/target = controller.blackboard[target_key] - if(QDELETED(target)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - controller.ai_interact(target = target, combat_mode = FALSE) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -/datum/ai_behavior/give - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH - - -/datum/ai_behavior/give/setup(datum/ai_controller/controller, target_key) - . = ..() - set_movement_target(controller, controller.blackboard[target_key]) - -/datum/ai_behavior/give/perform(seconds_per_tick, datum/ai_controller/controller, target_key) - var/mob/living/pawn = controller.pawn - var/obj/item/held_item = pawn.get_active_held_item() - var/atom/target = controller.blackboard[target_key] - - if(!held_item) //if held_item is null, we pretend that action was successful - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - - if(QDELETED(target) || !target.IsReachableBy(pawn)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - var/mob/living/living_target = target - if(!isliving(living_target)) // target should reasonably only ever be set to a living mob - stack_trace("Tried to give an item to a non-living target!") - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - var/perform_flags = try_to_give_item(controller, living_target, held_item) - if(perform_flags & AI_BEHAVIOR_FAILED) - return perform_flags - controller.PauseAi(1.5 SECONDS) - living_target.visible_message( - span_info("[pawn] starts trying to give [held_item] to [living_target]!"), - span_warning("[pawn] tries to give you [held_item]!") - ) - if(!do_after(pawn, 1 SECONDS, living_target)) - return AI_BEHAVIOR_DELAY | perform_flags - - perform_flags |= try_to_give_item(controller, living_target, held_item, actually_give = TRUE) - return AI_BEHAVIOR_DELAY | perform_flags - -/datum/ai_behavior/give/proc/try_to_give_item(datum/ai_controller/controller, mob/living/target, obj/item/held_item, actually_give) - if(QDELETED(held_item) || QDELETED(target)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - var/has_left_pocket = target.can_equip(held_item, ITEM_SLOT_LPOCKET) - var/has_right_pocket = target.can_equip(held_item, ITEM_SLOT_RPOCKET) - var/has_valid_hand - - for(var/hand_index in target.get_empty_held_indexes()) - if(target.can_put_in_hand(held_item, hand_index)) - has_valid_hand = TRUE - break - - if(!has_left_pocket && !has_right_pocket && !has_valid_hand) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - if(!actually_give) - return AI_BEHAVIOR_DELAY - - if(!has_valid_hand || prob(50)) - target.equip_to_slot_if_possible(held_item, (!has_left_pocket ? ITEM_SLOT_RPOCKET : (prob(50) ? ITEM_SLOT_LPOCKET : ITEM_SLOT_RPOCKET))) - else - target.put_in_hands(held_item) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -/datum/ai_behavior/give/finish_action(datum/ai_controller/controller, succeeded, target_key) - . = ..() - controller.clear_blackboard_key(target_key) - -/datum/ai_behavior/consume - action_cooldown = 2 SECONDS - -/datum/ai_behavior/consume/perform(seconds_per_tick, datum/ai_controller/controller, target_key, hunger_timer_key) - var/mob/living/living_pawn = controller.pawn - var/obj/item/target = controller.blackboard[target_key] - if(QDELETED(target) || !living_pawn.is_holding(target)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - controller.ai_interact(target = living_pawn, combat_mode = FALSE) - - return AI_BEHAVIOR_DELAY | (is_content(living_pawn, target) ? AI_BEHAVIOR_SUCCEEDED : AI_BEHAVIOR_FAILED) - -/datum/ai_behavior/consume/finish_action(datum/ai_controller/controller, succeeded, target_key, hunger_timer_key) - . = ..() - if(!succeeded) - return - controller.set_blackboard_key(hunger_timer_key, world.time + rand(12 SECONDS, 60 SECONDS)) - - var/mob/living/living_pawn = controller.pawn - var/obj/item/target = controller.blackboard[target_key] - if(!QDELETED(target) && !DOING_INTERACTION_WITH_TARGET(living_pawn, target)) - controller.clear_blackboard_key(target_key) - living_pawn.dropItemToGround(target) // drops empty drink glasses - for(var/obj/item/trash/trash in living_pawn.held_items) - living_pawn.dropItemToGround(trash) // drops spawned trash items - -/// Check if the target is fully consumed, or being actively consumed, or if we're just bored of eating it -/datum/ai_behavior/consume/proc/is_content(mob/living/living_pawm, obj/item/target) - if(QDELETED(target)) - return TRUE - if(DOING_INTERACTION_WITH_TARGET(living_pawm, target)) - return TRUE - if(target.reagents?.total_volume <= 0) - return TRUE - // Even if we don't finish it all we can randomly decide to be done - return prob(10) - -// navigate to target item and pick it up if we can -/datum/ai_behavior/navigate_to_and_pick_up - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH - action_cooldown = 2 SECONDS - -/datum/ai_behavior/navigate_to_and_pick_up/setup(datum/ai_controller/controller, target_key, drop_held = TRUE) - . = ..() - set_movement_target(controller, controller.blackboard[target_key]) - -/datum/ai_behavior/navigate_to_and_pick_up/setup(datum/ai_controller/controller, target_key, drop_held = TRUE) - var/mob/living/living_pawn = controller.pawn - var/obj/item/target = controller.blackboard[target_key] - if(QDELETED(target)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - if(living_pawn.is_holding(target)) // already in hands - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - if(!target.IsReachableBy(living_pawn)) // can't reach it, despite being adjacent - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - if(living_pawn.get_active_held_item()) // something is in our hands already - if(!drop_held || !living_pawn.dropItemToGround(living_pawn.get_active_held_item())) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - controller.ai_interact(target, combat_mode = FALSE) - return AI_BEHAVIOR_DELAY | (target.loc == living_pawn ? AI_BEHAVIOR_SUCCEEDED : AI_BEHAVIOR_FAILED) - -/** - * Drops items in hands, very important for future behaviors that require the pawn to grab stuff - */ -/datum/ai_behavior/drop_item - -/datum/ai_behavior/drop_item/perform(seconds_per_tick, datum/ai_controller/controller) - var/mob/living/living_pawn = controller.pawn - var/list/my_held_items = living_pawn.held_items - GetBestWeapon(controller, null, living_pawn.held_items) - if(!length(my_held_items)) - return AI_BEHAVIOR_FAILED | AI_BEHAVIOR_DELAY - living_pawn.dropItemToGround(pick(my_held_items)) - return AI_BEHAVIOR_SUCCEEDED | AI_BEHAVIOR_DELAY - -/// This behavior involves attacking a target. -/datum/ai_behavior/attack - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_MOVE_AND_PERFORM | AI_BEHAVIOR_REQUIRE_REACH - -/datum/ai_behavior/attack/perform(seconds_per_tick, datum/ai_controller/controller) - var/mob/living/living_pawn = controller.pawn - if(!istype(living_pawn) || !isturf(living_pawn.loc)) - return AI_BEHAVIOR_DELAY - - var/atom/movable/attack_target = controller.blackboard[BB_ATTACK_TARGET] - if(!attack_target || !can_see(living_pawn, attack_target, length = controller.blackboard[BB_VISION_RANGE])) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - var/mob/living/living_target = attack_target - if(istype(living_target) && (living_target.stat == DEAD)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - - set_movement_target(controller, living_target) - attack(controller, living_target) - return AI_BEHAVIOR_DELAY - -/datum/ai_behavior/attack/finish_action(datum/ai_controller/controller, succeeded) - . = ..() - controller.clear_blackboard_key(BB_ATTACK_TARGET) - -/// A proc representing when the mob is pushed to actually attack the target. Again, subtypes can be used to represent different attacks from different animals, or it can be some other generic behavior -/datum/ai_behavior/attack/proc/attack(datum/ai_controller/controller, mob/living/living_target) - var/mob/living/living_pawn = controller.pawn - if(!istype(living_pawn)) - return - living_pawn.ClickOn(living_target, list()) - -/// This behavior involves attacking a target. -/datum/ai_behavior/follow - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_MOVE_AND_PERFORM - -/datum/ai_behavior/follow/perform(seconds_per_tick, datum/ai_controller/controller) - var/mob/living/living_pawn = controller.pawn - if(!istype(living_pawn) || !isturf(living_pawn.loc)) - return AI_BEHAVIOR_DELAY - - var/atom/movable/follow_target = controller.blackboard[BB_FOLLOW_TARGET] - if(!follow_target || get_dist(living_pawn, follow_target) > controller.blackboard[BB_VISION_RANGE]) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - var/mob/living/living_target = follow_target - if(istype(living_target) && (living_target.stat == DEAD)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - - set_movement_target(controller, living_target) - return AI_BEHAVIOR_DELAY - -/datum/ai_behavior/follow/finish_action(datum/ai_controller/controller, succeeded) - . = ..() - controller.clear_blackboard_key(BB_FOLLOW_TARGET) - -/datum/ai_behavior/perform_emote - -/datum/ai_behavior/perform_emote/perform(seconds_per_tick, datum/ai_controller/controller, emote, speech_sound) - . = ..() - var/mob/living/living_pawn = controller.pawn - if(!istype(living_pawn)) - return AI_BEHAVIOR_INSTANT - living_pawn.manual_emote(emote) - if(speech_sound) // Only audible emotes will pass in a sound - playsound(living_pawn, speech_sound, 80, vary = TRUE, pressure_affected =TRUE, ignore_walls = FALSE) - return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED - -/datum/ai_behavior/perform_speech - -/datum/ai_behavior/perform_speech/perform(seconds_per_tick, datum/ai_controller/controller, speech, speech_sound) - . = ..() - - var/mob/living/living_pawn = controller.pawn - if(!istype(living_pawn)) - return AI_BEHAVIOR_INSTANT - living_pawn.say(speech, forced = "AI Controller") - if(speech_sound) - playsound(living_pawn, speech_sound, 80, vary = TRUE) - return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED - -/datum/ai_behavior/perform_speech_radio - -/datum/ai_behavior/perform_speech_radio/perform(seconds_per_tick, datum/ai_controller/controller, speech, obj/item/radio/speech_radio, list/try_channels = list(RADIO_CHANNEL_COMMON)) - var/mob/living/living_pawn = controller.pawn - if(!istype(living_pawn) || !istype(speech_radio) || QDELETED(speech_radio) || !length(try_channels)) - return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED - speech_radio.talk_into(living_pawn, speech, pick(try_channels)) - return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED - -//song behaviors - -/datum/ai_behavior/setup_instrument - -/datum/ai_behavior/setup_instrument/perform(seconds_per_tick, datum/ai_controller/controller, song_instrument_key, song_lines_key) - var/obj/item/instrument/song_instrument = controller.blackboard[song_instrument_key] - var/datum/song/song = song_instrument.song - var/song_lines = controller.blackboard[song_lines_key] - - //just in case- it won't do anything if the instrument isn't playing - song.stop_playing() - song.ParseSong(new_song = song_lines) - song.repeat = 10 - song.volume = song.max_volume - 10 - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -/datum/ai_behavior/play_instrument - -/datum/ai_behavior/play_instrument/perform(seconds_per_tick, datum/ai_controller/controller, song_instrument_key) - var/obj/item/instrument/song_instrument = controller.blackboard[song_instrument_key] - var/datum/song/song = song_instrument.song - - song.start_playing(controller.pawn) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -/datum/ai_behavior/find_nearby - -/datum/ai_behavior/find_nearby/perform(seconds_per_tick, datum/ai_controller/controller, target_key) - var/list/possible_targets = list() - for(var/atom/thing in view(2, controller.pawn)) - if(!thing.mouse_opacity) - continue - if(thing.IsObscured()) - continue - if(isitem(thing)) - var/obj/item/item = thing - if(item.item_flags & ABSTRACT) - continue - possible_targets += thing - if(!possible_targets.len) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - controller.set_blackboard_key(target_key, pick(possible_targets)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED diff --git a/code/datums/ai/generic/generic_subtrees.dm b/code/datums/ai/generic/generic_subtrees.dm deleted file mode 100644 index 6fe92668f91..00000000000 --- a/code/datums/ai/generic/generic_subtrees.dm +++ /dev/null @@ -1,98 +0,0 @@ -/** - * Generic Instrument Subtree, For your pawn playing instruments - * - * Requires at least a living mob that can hold items. - * - * relevant blackboards: - * * BB_SONG_INSTRUMENT - set by this subtree, is the song datum the pawn plays music from. - * * BB_SONG_LINES - not set by this subtree, is the song loaded into the song datum. - */ -/datum/ai_planning_subtree/generic_play_instrument/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/obj/item/instrument/song_player = controller.blackboard[BB_SONG_INSTRUMENT] - - if(!song_player) - controller.queue_behavior(/datum/ai_behavior/find_and_set/in_hands, BB_SONG_INSTRUMENT, /obj/item/instrument) - return //we can't play a song since we do not have an instrument - - var/list/parsed_song_lines = splittext(controller.blackboard[BB_SONG_LINES], "\n") - popleft(parsed_song_lines) //remove BPM as it is parsed out - if(!compare_list(song_player.song.lines, parsed_song_lines) || !song_player.song.repeat) - controller.queue_behavior(/datum/ai_behavior/setup_instrument, BB_SONG_INSTRUMENT, BB_SONG_LINES) - - if(!song_player.song.playing) //we may stop playing if we weren't playing before, were setting up dk theme, or ran out of repeats (also causing setup behavior) - controller.queue_behavior(/datum/ai_behavior/play_instrument, BB_SONG_INSTRUMENT) - -/datum/ai_planning_subtree/generic_play_instrument/end_planning - -/datum/ai_planning_subtree/generic_play_instrument/end_planning/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - . = ..() - if (controller.blackboard_key_exists(BB_SONG_INSTRUMENT)) - return SUBTREE_RETURN_FINISH_PLANNING // Don't plan anything else if we're playing an instrument - - -/** - * Generic Resist Subtree, resist if it makes sense to! - * - * Requires nothing beyond a living pawn, makes sense on a good amount of mobs since anything can get buckled. - * - * relevant blackboards: - * * None! - */ -/datum/ai_planning_subtree/generic_resist/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/mob/living/living_pawn = controller.pawn - - if(SHOULD_RESIST(living_pawn) && SPT_PROB(RESIST_SUBTREE_PROB, seconds_per_tick)) - controller.queue_behavior(/datum/ai_behavior/resist) //BRO IM ON FUCKING FIRE BRO - return SUBTREE_RETURN_FINISH_PLANNING //IM NOT DOING ANYTHING ELSE BUT EXTINGUISH MYSELF, GOOD GOD HAVE MERCY. - -/** - * Generic Hunger Subtree, - * - * Requires at least a living mob that can hold items. - * - * relevant blackboards: - * * BB_NEXT_HUNGRY - set by this subtree, is when the controller is next hungry - */ -/datum/ai_planning_subtree/generic_hunger - -/datum/ai_planning_subtree/generic_hunger/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/mob/living/living_pawn = controller.pawn - if(living_pawn.nutrition > NUTRITION_LEVEL_HUNGRY) - return - - var/next_eat = controller.blackboard[BB_NEXT_HUNGRY] - if(!next_eat) - //inits the blackboard timer - next_eat = world.time + rand(0, 30 SECONDS) - controller.set_blackboard_key(BB_NEXT_HUNGRY, next_eat) - - if(world.time < next_eat) - return - - // find food - var/atom/food_target = controller.blackboard[BB_FOOD_TARGET] - if(isnull(food_target)) - controller.queue_behavior(/datum/ai_behavior/find_and_set/food_or_drink/to_eat, BB_FOOD_TARGET, /obj/item, 2) - return SUBTREE_RETURN_FINISH_PLANNING - - if(living_pawn.is_holding(food_target)) - controller.queue_behavior(/datum/ai_behavior/consume, BB_FOOD_TARGET, BB_NEXT_HUNGRY) - // it's been moved since we found it - else if(!isturf(food_target.loc)) - // someone took it. we will fight over it! - if(isliving(food_target.loc) && will_fight_for_food(food_target.loc, living_pawn, controller)) - controller.add_blackboard_key_assoc(BB_MONKEY_ENEMIES, food_target.loc, MONKEY_FOOD_HATRED_AMOUNT) - // eh, find something else - else - controller.clear_blackboard_key(BB_FOOD_TARGET) - return SUBTREE_RETURN_FINISH_PLANNING - else - controller.queue_behavior(/datum/ai_behavior/navigate_to_and_pick_up, BB_FOOD_TARGET, TRUE) - return SUBTREE_RETURN_FINISH_PLANNING - -/datum/ai_planning_subtree/generic_hunger/proc/will_fight_for_food(mob/living/thief, mob/living/monkey, datum/ai_controller/controller) - if(controller.blackboard[BB_MONKEY_AGGRESSIVE]) - return TRUE - if(controller.blackboard[BB_MONKEY_TAMED]) - return FALSE - return prob(100 * ((NUTRITION_LEVEL_HUNGRY - monkey.nutrition) / NUTRITION_LEVEL_HUNGRY)) diff --git a/code/datums/ai/generic_behaviors/acquire_injured_target.dm b/code/datums/ai/generic_behaviors/acquire_injured_target.dm new file mode 100644 index 00000000000..073e109997b --- /dev/null +++ b/code/datums/ai/generic_behaviors/acquire_injured_target.dm @@ -0,0 +1,15 @@ +/datum/bt_node/ai_behavior/retrieve_injured_rider + ///where do we save our target + var/target_key + +/datum/bt_node/ai_behavior/retrieve_injured_rider/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/living_mob = controller.pawn + if (!length(living_mob.buckled_mobs) || !isliving(living_mob.buckled_mobs[1])) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + var/mob/living/rider = living_mob.buckled_mobs[1] + if (rider.stat == CONSCIOUS || rider.stat == DEAD || rider.health >= rider.maxHealth) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + controller.set_blackboard_key(target_key, rider) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED diff --git a/code/datums/ai/generic_behaviors/acquire_target.dm b/code/datums/ai/generic_behaviors/acquire_target.dm new file mode 100644 index 00000000000..73b1608f13d --- /dev/null +++ b/code/datums/ai/generic_behaviors/acquire_target.dm @@ -0,0 +1,196 @@ +///Baseline for target picking behaviors +/datum/bt_node/ai_behavior/acquire_target + /// Blackboard key to write the found target into. + var/target_key + /// Either a /datum/targeting_strategy typepath (resolved directly) or a blackboard key string + var/targeting_strategy = BB_TARGETING_STRATEGY + /// Typepath of the /datum/target_source type used to gather candidates. turned into the singleton instance when used + var/target_source = /datum/target_source/oview + /// How far to scan for candidates (passed to the target source). Can be a key too. + var/vision_range = 7 + /// How to behave when a target is already set. See TARGET_* defines in ai.dm. + var/revalidation_mode = TARGET_REVALIDATE + /// Extended range for retaining an existing target when candidates run dry. 0 = disabled. + var/target_loss_distance = 12 + /// Optional blackboard key holding a lazylist of atoms to skip while filtering candidates. + var/ignore_list_key + /// If TRUE, only candidates the controller can path to are eligible; unreachable ones are reported via note_unreachable_target. + var/must_be_reachable = FALSE + /// Pathfinding distance limit used when must_be_reachable is set. + var/reach_distance = 10 + /// How close the reachability path must get to the target (0 = onto/adjacent). Passed to can_reach_target when must_be_reachable is set. + var/minimum_distance = 0 + /// Strategy/range snapshot from the perform() that kicked off the current async search. + VAR_PRIVATE/datum/targeting_strategy/search_strategy + /// Range snapshot from the perform() that kicked off the current async search. + VAR_PRIVATE/search_range + +/datum/bt_node/ai_behavior/acquire_target/perform(seconds_per_tick, datum/ai_controller/controller) + var/async_flags = handle_async() + if(async_flags) + return async_flags + + if(!can_search(controller)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + var/datum/targeting_strategy/strategy = get_targeting_strategy(controller) + var/atom/current_target = controller.blackboard[target_key] + + if(should_keep_target(controller, strategy, current_target)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + + if(!isnum(vision_range)) //If there's a blackboard override, use that + vision_range = controller.blackboard[vision_range] || vision_range + + if(!must_be_reachable) + return find_and_set_target(controller, strategy, vision_range) + + // Kick off async reachability search. + search_strategy = strategy + search_range = vision_range + return start_async() + +/datum/bt_node/ai_behavior/acquire_target/finish_action(datum/ai_controller/controller, succeeded) + . = ..() + search_strategy = null + search_range = null + +/// Returns TRUE to abort the search before it starts (e.g. a detection field is already active). +/datum/bt_node/ai_behavior/acquire_target/proc/can_search(datum/ai_controller/controller) + return TRUE + +/// Returns TRUE if the current target is still good and we should skip the search. +/datum/bt_node/ai_behavior/acquire_target/proc/should_keep_target(datum/ai_controller/controller, datum/targeting_strategy/strategy, atom/current_target) + switch(revalidation_mode) + if(TARGET_KEEP_IF_SET) + return !isnull(current_target) + if(TARGET_REVALIDATE) + return !isnull(current_target) && strategy.is_valid_target(controller.pawn, current_target, vision_range, controller) + return FALSE + +///Resolves the targeting strategy for this behavior +/datum/bt_node/ai_behavior/acquire_target/proc/get_targeting_strategy(datum/ai_controller/controller) + if(!targeting_strategy) + return GET_TARGETING_STRATEGY(/datum/targeting_strategy/anything) + if(ispath(targeting_strategy)) + return GET_TARGETING_STRATEGY(targeting_strategy) + var/datum/targeting_strategy/strategy = GET_TARGETING_STRATEGY(controller.blackboard[targeting_strategy]) + if(!strategy) + CRASH("No targeting strategy was supplied in the blackboard for [controller.pawn]") + return strategy + + +///Actual behavior for collecting and filtering targets +/datum/bt_node/ai_behavior/acquire_target/proc/find_and_set_target(datum/ai_controller/controller, datum/targeting_strategy/targeting_strategy, range) + var/mob/living/living_mob = controller.pawn + var/datum/target_source/source = GET_TARGET_SOURCE(target_source) + if(!source) + CRASH("No target source found for type [target_source] on [controller.pawn]") + + var/atom/current_target = controller.blackboard[target_key] + var/list/candidates = source.collect_candidates(living_mob, controller, range) + + if(!length(candidates)) + candidates = on_no_candidates(controller, current_target, targeting_strategy, range) + if(!length(candidates)) + clear_stale_target(controller, current_target) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + var/list/filtered = filter_candidates(controller, candidates, targeting_strategy, current_target) + + if(!length(filtered)) + on_no_valid_candidates(controller, current_target) + clear_stale_target(controller, current_target) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + var/atom/target = pick_final_target(controller, filtered) + + if(isnull(target)) + on_no_valid_candidates(controller, current_target) + clear_stale_target(controller, current_target) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + EVLOG_MAPTEXT(controller, EVLOG_CATEGORY_AI_TARGETING, "[controller.pawn] has selected [target] as a target for blackboard key [target_key]! Behavior: [src]", get_turf(target), "Target: [target]") + EVLOG_LINES(controller, EVLOG_CATEGORY_AI_TARGETING, "Line to target", get_turf(controller.pawn), get_turf(target)) + + if(target != current_target) + controller.set_blackboard_key(target_key, target) + + on_target_found(controller, target, targeting_strategy) + + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + +/// Clears the target key when revalidating and the search turned up nothing, so a stale target doesn't linger. +/datum/bt_node/ai_behavior/acquire_target/proc/clear_stale_target(datum/ai_controller/controller, atom/current_target) + if(revalidation_mode != TARGET_REVALIDATE || isnull(current_target)) + return + controller.clear_blackboard_key(target_key) + +///Fallback for no targets found. +/datum/bt_node/ai_behavior/acquire_target/proc/on_no_candidates(datum/ai_controller/controller, atom/current_target, datum/targeting_strategy/strategy, range) + if(!target_loss_distance || !current_target) + return list() + if(strategy.can_keep_target(controller.pawn, current_target, target_loss_distance)) + return list(current_target) + return list() + +/// Filters the candidate list to valid targets. Override to add priority filtering or other per-candidate criteria. +/datum/bt_node/ai_behavior/acquire_target/proc/filter_candidates(datum/ai_controller/controller, list/candidates, datum/targeting_strategy/strategy, atom/current_target) + var/mob/living/pawn = controller.pawn + var/list/ignore_list = ignore_list_key ? controller.blackboard[ignore_list_key] : null + var/list/filtered = list() + for(var/atom/candidate as anything in candidates) + if(LAZYACCESS(ignore_list, candidate)) + continue + if(!strategy.is_valid_target(pawn, candidate, vision_range, controller)) + continue + filtered += candidate + return filtered + +/// Called when filter_candidates produces nothing. Override to trigger side effects (e.g. spawning a detection field). +/datum/bt_node/ai_behavior/acquire_target/proc/on_no_valid_candidates(datum/ai_controller/controller, atom/current_target) + return + +/// Called after a target is selected and written to the blackboard. Override for post-selection side effects. +/datum/bt_node/ai_behavior/acquire_target/proc/on_target_found(datum/ai_controller/controller, atom/target, datum/targeting_strategy/strategy) + return + +/// Picks the final target from filtered candidates. Only valid for the non-reachable path; the reachable path goes async. +/datum/bt_node/ai_behavior/acquire_target/proc/pick_final_target(datum/ai_controller/controller, list/filtered_targets) + return pick(filtered_targets) + +/// perform_async(): walks filtered candidates checking reachability (may sleep), then commits the result via finish_async(). +/datum/bt_node/ai_behavior/acquire_target/perform_async(datum/ai_controller/controller) + var/datum/targeting_strategy/strategy = search_strategy + var/range = search_range + var/mob/living/living_mob = controller.pawn + var/datum/target_source/source = GET_TARGET_SOURCE(target_source) + var/atom/current_target = controller.blackboard[target_key] + var/list/candidates = source.collect_candidates(living_mob, controller, range) + if(!length(candidates)) + candidates = on_no_candidates(controller, current_target, strategy, range) + var/list/filtered = filter_candidates(controller, candidates, strategy, current_target) + var/atom/target + for(var/atom/candidate as anything in filtered) + // get_path_to may sleep here check abort flag after it returns. + if(controller.can_reach_target(candidate, reach_distance, minimum_distance)) + target = candidate + break + controller.note_unreachable_target(candidate) + // If finish_action fired while we were sleeping, bail without touching anything. + if(!async_still_valid()) + return + if(!isnull(target)) + if(target != controller.blackboard[target_key]) + controller.set_blackboard_key(target_key, target) + on_target_found(controller, target, strategy) + else + on_no_valid_candidates(controller, current_target) + clear_stale_target(controller, current_target) + finish_async(isnull(target) ? AI_BEHAVIOR_FAILED : AI_BEHAVIOR_SUCCEEDED) + +///Finds a nearby target to interact with, used as a baseline for behaviors that need to interact with something nearby. +/datum/bt_node/ai_behavior/acquire_target/update_interaction_target + target_source = /datum/target_source/oview + revalidation_mode = TARGET_REVALIDATE + time_between_perform = 2 SECONDS diff --git a/code/datums/ai/generic_behaviors/ai_interact.dm b/code/datums/ai/generic_behaviors/ai_interact.dm new file mode 100644 index 00000000000..29e54e626e2 --- /dev/null +++ b/code/datums/ai/generic_behaviors/ai_interact.dm @@ -0,0 +1,17 @@ +/// Interacts with the atom at target_key once, with optional combat_mode (default FALSE). +/datum/bt_node/ai_behavior/ai_interact + /// Blackboard key holding the atom to interact with. + var/target_key + /// Whether to interact in combat mode. + var/combat_mode = FALSE + +/datum/bt_node/ai_behavior/ai_interact/setup(datum/ai_controller/controller) + var/atom/target = controller.blackboard[target_key] + return !QDELETED(target) + +/datum/bt_node/ai_behavior/ai_interact/perform(seconds_per_tick, datum/ai_controller/controller) + var/atom/target = controller.blackboard[target_key] + if(QDELETED(target)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + INVOKE_ASYNC(controller, TYPE_PROC_REF(/datum/ai_controller, ai_interact), target, combat_mode) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED diff --git a/code/datums/ai/generic_behaviors/attack_obstacles.dm b/code/datums/ai/generic_behaviors/attack_obstacles.dm new file mode 100644 index 00000000000..aeb9a7476eb --- /dev/null +++ b/code/datums/ai/generic_behaviors/attack_obstacles.dm @@ -0,0 +1,65 @@ + +///Destroy shit int he way +/datum/bt_node/ai_behavior/attack_obstructions + var/target_key + time_between_perform = 2 SECONDS + /// If we should attack walls, be prepared for complaints about breaches + var/can_attack_turfs = FALSE + /// For if you want your mob to be able to attack dense objects + var/can_attack_dense_objects = FALSE + +/datum/bt_node/ai_behavior/attack_obstructions/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/basic/basic_mob = controller.pawn + var/atom/target = controller.blackboard[target_key] + + if(QDELETED(target)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + + var/turf/next_step = get_step_towards(basic_mob, target) + if(!next_step.is_blocked_turf(exclude_mobs = TRUE, source_atom = controller.pawn)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED // Path clear let selector fall through to melee + + var/dir_to_next_step = get_dir(basic_mob, next_step) + var/list/dirs_to_move = list() + if(ISDIAGONALDIR(dir_to_next_step)) + for(var/direction in GLOB.cardinals) + if(direction & dir_to_next_step) + dirs_to_move += direction + else + dirs_to_move += dir_to_next_step + + for(var/direction in dirs_to_move) + if(attack_in_direction(controller, basic_mob, direction)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED // Nothing smashable let selector fall through + +/datum/bt_node/ai_behavior/attack_obstructions/proc/attack_in_direction(datum/ai_controller/controller, mob/living/basic/basic_mob, direction) + var/turf/next_step = get_step(basic_mob, direction) + if(!next_step.is_blocked_turf(exclude_mobs = TRUE, source_atom = controller.pawn)) + return FALSE + + for(var/obj/object as anything in next_step.contents) + if(!can_smash_object(basic_mob, object)) + continue + basic_mob.melee_attack(object) + return TRUE + + if(can_attack_turfs) + basic_mob.melee_attack(next_step) + return TRUE + return FALSE + +/datum/bt_node/ai_behavior/attack_obstructions/proc/can_smash_object(mob/living/basic/basic_mob, obj/object) + if(!object.density && !can_attack_dense_objects) + return FALSE + if(object.IsObscured()) + return FALSE + if(basic_mob.see_invisible < object.invisibility) + return FALSE + var/list/whitelist = basic_mob.ai_controller.blackboard[BB_OBSTACLE_TARGETING_WHITELIST] + if(whitelist && !is_type_in_typecache(object, whitelist)) + return FALSE + return TRUE + +/datum/bt_node/ai_behavior/attack_obstructions/attack_turfs + can_attack_turfs = TRUE diff --git a/code/datums/ai/generic_behaviors/battle_screech.dm b/code/datums/ai/generic_behaviors/battle_screech.dm new file mode 100644 index 00000000000..eee357e942e --- /dev/null +++ b/code/datums/ai/generic_behaviors/battle_screech.dm @@ -0,0 +1,10 @@ +/// Emotes a random screech from a list of screeches defined on the subtype. +/datum/bt_node/ai_behavior/battle_screech + /// List of possible screeches the behavior has + var/list/screeches + +/datum/bt_node/ai_behavior/battle_screech/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/living_pawn = controller.pawn + INVOKE_ASYNC(living_pawn, TYPE_PROC_REF(/mob, emote), pick(screeches)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + diff --git a/code/datums/ai/generic_behaviors/break_out_of_object.dm b/code/datums/ai/generic_behaviors/break_out_of_object.dm new file mode 100644 index 00000000000..239222e55f5 --- /dev/null +++ b/code/datums/ai/generic_behaviors/break_out_of_object.dm @@ -0,0 +1,40 @@ +/// Attacks an escape target until the pawn is no longer buckled to or contained by it. +/datum/bt_node/ai_behavior/break_out_of_object + time_between_perform = 0.2 SECONDS + /// The object to break out of by attacking it. + var/atom/target_atom + +/datum/bt_node/ai_behavior/break_out_of_object/setup(datum/ai_controller/controller) + if (!should_attack_target(controller, target_atom)) + return FALSE + return TRUE + +/datum/bt_node/ai_behavior/break_out_of_object/perform(seconds_per_tick, datum/ai_controller/controller) + if (!should_attack_target(controller, target_atom)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + INVOKE_ASYNC(controller, TYPE_PROC_REF(/datum/ai_controller, ai_interact), target_atom, TRUE) + return AI_BEHAVIOR_DELAY + +/datum/bt_node/ai_behavior/break_out_of_object/proc/should_attack_target(datum/ai_controller/controller, atom/target) + if (QDELETED(target)) + return FALSE + var/mob/living/pawn = controller.pawn + if (!target.IsReachableBy(pawn)) + return FALSE + return pawn.loc == target || pawn.buckled == target + +/// Variant that reads the escape target from a blackboard key instead of a direct reference. +/datum/bt_node/ai_behavior/break_out_of_object/from_bb + /// Blackboard key holding the object to break out of. + var/target_key + +/datum/bt_node/ai_behavior/break_out_of_object/from_bb/setup(datum/ai_controller/controller) + var/atom/target = controller.blackboard[target_key] + return should_attack_target(controller, target) + +/datum/bt_node/ai_behavior/break_out_of_object/from_bb/perform(seconds_per_tick, datum/ai_controller/controller) + var/atom/target = controller.blackboard[target_key] + if(!should_attack_target(controller, target)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + INVOKE_ASYNC(controller, TYPE_PROC_REF(/datum/ai_controller, ai_interact), target, TRUE) + return AI_BEHAVIOR_DELAY diff --git a/code/datums/ai/generic_behaviors/break_spine.dm b/code/datums/ai/generic_behaviors/break_spine.dm new file mode 100644 index 00000000000..013c1ee2915 --- /dev/null +++ b/code/datums/ai/generic_behaviors/break_spine.dm @@ -0,0 +1,42 @@ +/// Applies full-body brute damage to a target while pulling them. Used with a move_to_target sequence. +/datum/bt_node/ai_behavior/break_spine + var/target_key + var/give_up_distance = 10 + +/datum/bt_node/ai_behavior/break_spine/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/batman = controller.blackboard[target_key] + var/mob/living/big_guy = controller.pawn + + if(QDELETED(batman) || get_dist(batman, big_guy) >= give_up_distance) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + + if(batman.stat != CONSCIOUS) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED + + INVOKE_ASYNC(big_guy, TYPE_PROC_REF(/atom/movable, start_pulling), batman) + big_guy.face_atom(batman) + batman.visible_message(span_warning("[batman] gets a slightly too tight hug from [big_guy]!"), span_userdanger("You feel your body break as [big_guy] embraces you!")) + for(var/zone in GLOB.all_body_zones - BODY_ZONE_HEAD) + batman.apply_damage(15, BRUTE, zone, wound_bonus = 35) + + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED + +/datum/bt_node/ai_behavior/break_spine/finish_action(datum/ai_controller/controller, succeeded) + . = ..() + if(succeeded) + var/mob/living/attacker = controller.pawn + if(!QDELETED(attacker)) + attacker.stop_pulling() + controller.clear_blackboard_key(target_key) + +/// Bane variant: says a quote from bane.json on successful spine-breaking. +/datum/bt_node/ai_behavior/break_spine/bane + +/datum/bt_node/ai_behavior/break_spine/bane/finish_action(datum/ai_controller/controller, succeeded) + . = ..() + if(succeeded) + var/mob/living/bane = controller.pawn + if(QDELETED(bane)) + return + var/list/bane_quotes = strings("bane.json", "bane") + INVOKE_ASYNC(bane, TYPE_PROC_REF(/atom/movable, say), pick(bane_quotes)) diff --git a/code/datums/ai/generic_behaviors/cancel_current_plan.dm b/code/datums/ai/generic_behaviors/cancel_current_plan.dm new file mode 100644 index 00000000000..74fe54b83e7 --- /dev/null +++ b/code/datums/ai/generic_behaviors/cancel_current_plan.dm @@ -0,0 +1,6 @@ +/// Cancels the controller's current plan, causing the BT to re-evaluate from the root next tick. +/datum/bt_node/ai_behavior/cancel_current_plan + +/datum/bt_node/ai_behavior/cancel_current_plan/perform(seconds_per_tick, datum/ai_controller/controller) + controller.cancel_current_plan() + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED diff --git a/code/datums/ai/generic_behaviors/clear_key.dm b/code/datums/ai/generic_behaviors/clear_key.dm new file mode 100644 index 00000000000..fffbc70e951 --- /dev/null +++ b/code/datums/ai/generic_behaviors/clear_key.dm @@ -0,0 +1,8 @@ +/// BT-native version: clears a single blackboard key. Returns INSTANT SUCCESS. +/datum/bt_node/ai_behavior/clear_key + /// Blackboard key to clear. + var/key + +/datum/bt_node/ai_behavior/clear_key/perform(seconds_per_tick, datum/ai_controller/controller) + controller.clear_blackboard_key(key) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED diff --git a/code/datums/ai/generic_behaviors/consume.dm b/code/datums/ai/generic_behaviors/consume.dm new file mode 100644 index 00000000000..f8562ce6b19 --- /dev/null +++ b/code/datums/ai/generic_behaviors/consume.dm @@ -0,0 +1,43 @@ +/// Uses the pawn's held food/drink item on themselves until consumed. +/datum/bt_node/ai_behavior/consume + time_between_perform = 2 SECONDS + /// Blackboard key holding the food/drink item to consume. + var/target_key + /// Blackboard key used to store the next-hunger time once finished. + var/hunger_timer_key + +/datum/bt_node/ai_behavior/consume/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/living_pawn = controller.pawn + var/obj/item/target = controller.blackboard[target_key] + if(QDELETED(target) || !living_pawn.is_holding(target)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + INVOKE_ASYNC(controller, TYPE_PROC_REF(/datum/ai_controller, ai_interact), living_pawn, FALSE) + + return AI_BEHAVIOR_DELAY | (is_content(living_pawn, target) ? AI_BEHAVIOR_SUCCEEDED : AI_BEHAVIOR_FAILED) + +/datum/bt_node/ai_behavior/consume/finish_action(datum/ai_controller/controller, succeeded) + . = ..() + if(!succeeded) + return + controller.set_blackboard_key(hunger_timer_key, world.time + rand(12 SECONDS, 60 SECONDS)) + + var/mob/living/living_pawn = controller.pawn + var/obj/item/target = controller.blackboard[target_key] + if(!QDELETED(target) && !DOING_INTERACTION_WITH_TARGET(living_pawn, target)) + controller.clear_blackboard_key(target_key) + living_pawn.dropItemToGround(target) // drops empty drink glasses + for(var/obj/item/trash/trash in living_pawn.held_items) + living_pawn.dropItemToGround(trash) // drops spawned trash items + +/// Check if the target is fully consumed, or being actively consumed, or if we're just bored of eating it +/datum/bt_node/ai_behavior/consume/proc/is_content(mob/living/living_pawm, obj/item/target) + if(QDELETED(target)) + return TRUE + if(DOING_INTERACTION_WITH_TARGET(living_pawm, target)) + return TRUE + if(target.reagents?.total_volume <= 0) + return TRUE + // Even if we don't finish it all we can randomly decide to be done + return prob(10) + diff --git a/code/datums/ai/generic_behaviors/copy_bb_key.dm b/code/datums/ai/generic_behaviors/copy_bb_key.dm new file mode 100644 index 00000000000..19cd6e4bd47 --- /dev/null +++ b/code/datums/ai/generic_behaviors/copy_bb_key.dm @@ -0,0 +1,8 @@ +/// Copies the value of source_key to dest_key. +/datum/bt_node/ai_behavior/copy_bb_key + var/dest_key + var/source_key + +/datum/bt_node/ai_behavior/copy_bb_key/perform(seconds_per_tick, datum/ai_controller/controller) + controller.set_blackboard_key(dest_key, controller.blackboard[source_key]) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED diff --git a/code/datums/ai/generic_behaviors/drag_target.dm b/code/datums/ai/generic_behaviors/drag_target.dm new file mode 100644 index 00000000000..2ef882e2c87 --- /dev/null +++ b/code/datums/ai/generic_behaviors/drag_target.dm @@ -0,0 +1,21 @@ +/** + * BT-native drag behavior. Moves to the target and starts pulling it. + * If already pulling the target, returns SUCCESS immediately (idempotent). + * Does NOT clear the target key on finish callers must clear it when done. + * Use move_to_target after this to drag the pulled mob/item to a destination. + */ +/datum/bt_node/ai_behavior/drag_target + /// Blackboard key holding the atom to drag. + var/target_key + +/datum/bt_node/ai_behavior/drag_target/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/our_mob = controller.pawn + var/atom/movable/target = controller.blackboard[target_key] + if(QDELETED(target) || target.anchored || target.pulledby) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + if(our_mob.pulling == target) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + if(!our_mob.Adjacent(target)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + INVOKE_ASYNC(our_mob, TYPE_PROC_REF(/atom/movable, start_pulling), target) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED diff --git a/code/datums/ai/generic_behaviors/drop_all_held_items.dm b/code/datums/ai/generic_behaviors/drop_all_held_items.dm new file mode 100644 index 00000000000..5c4653062aa --- /dev/null +++ b/code/datums/ai/generic_behaviors/drop_all_held_items.dm @@ -0,0 +1,9 @@ +/// Drops everything the pawn is currently holding in its hands onto its current turf. +/datum/bt_node/ai_behavior/drop_all_held_items + +/datum/bt_node/ai_behavior/drop_all_held_items/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/living_pawn = controller.pawn + if(!living_pawn.get_num_held_items()) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + living_pawn.drop_all_held_items() + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED diff --git a/code/datums/ai/generic_behaviors/express_happiness.dm b/code/datums/ai/generic_behaviors/express_happiness.dm new file mode 100644 index 00000000000..eaf181ca696 --- /dev/null +++ b/code/datums/ai/generic_behaviors/express_happiness.dm @@ -0,0 +1,53 @@ +/// Occasionally emotes based on the pawn's current happiness level. +/datum/bt_node/ai_behavior/express_happiness + /// Probability (%) of emoting per per second + var/emote_probability = 5 + /// Happiness >= this threshold -> happy emotions + var/high_happiness_threshold = 0.7 + /// Happiness >= this threshold -> moderate emotions + var/moderate_happiness_threshold = 0.5 + /// Blackboard key holding the happiness value + var/happiness_key = BB_BASIC_HAPPINESS + /// Blackboard key holding a custom happy emotions list + var/happy_key = BB_HAPPY_EMOTIONS + /// Blackboard key holding a custom moderate emotions list + var/moderate_key = BB_MODERATE_EMOTIONS + /// Blackboard key holding a custom sad emotions list + var/sad_key = BB_SAD_EMOTIONS + + var/static/list/default_happy_emotions = list( + "celebrates happily!", + "dances around in excitement!", + ) + var/static/list/default_moderate_emotions = list( + "looks satisfied.", + "trots around.", + ) + var/static/list/default_depressed_emotions = list( + "looks depressed...", + "turns its back and sulks...", + "looks towards the floor in dissapointment...", + ) + +/datum/bt_node/ai_behavior/express_happiness/perform(seconds_per_tick, datum/ai_controller/controller) + if(!SPT_PROB(emote_probability, seconds_per_tick)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED + + var/happiness = controller.blackboard[happiness_key] + if(isnull(happiness)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED + + var/list/emotion_list + if(happiness >= high_happiness_threshold) + emotion_list = controller.blackboard[happy_key] || default_happy_emotions + else if(happiness >= moderate_happiness_threshold) + emotion_list = controller.blackboard[moderate_key] || default_moderate_emotions + else + emotion_list = controller.blackboard[sad_key] || default_depressed_emotions + + if(!length(emotion_list)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED + + var/mob/living/living_pawn = controller.pawn + living_pawn.manual_emote(pick(emotion_list)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED diff --git a/code/datums/ai/generic_behaviors/face_target_or_face_initial.dm b/code/datums/ai/generic_behaviors/face_target_or_face_initial.dm new file mode 100644 index 00000000000..14f216d4202 --- /dev/null +++ b/code/datums/ai/generic_behaviors/face_target_or_face_initial.dm @@ -0,0 +1,24 @@ +/// Faces the target each tick while it's within range. When the target is absent or out of range, resets to BB_STARTING_DIRECTION. +/// Captures BB_STARTING_DIRECTION once on setup from the pawn's current facing direction. +/datum/bt_node/ai_behavior/face_target_or_face_initial + var/target_key = BB_CURRENT_TARGET + +/datum/bt_node/ai_behavior/face_target_or_face_initial/setup(datum/ai_controller/controller) + . = ..() + var/mob/living/we = controller.pawn + if(!istype(we)) + return FALSE + var/atom/movable/target = controller.blackboard[target_key] + if(!ismovable(target) || !isturf(target.loc)) + return FALSE + controller.set_blackboard_key(BB_STARTING_DIRECTION, we.dir) + return TRUE + +/datum/bt_node/ai_behavior/face_target_or_face_initial/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/we = controller.pawn + var/atom/movable/target = controller.blackboard[target_key] + if(isnull(target) || get_dist(we, target) > 8) + we.dir = controller.blackboard[BB_STARTING_DIRECTION] + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + we.face_atom(target) + return AI_BEHAVIOR_DELAY diff --git a/code/datums/ai/generic_behaviors/fail.dm b/code/datums/ai/generic_behaviors/fail.dm new file mode 100644 index 00000000000..6a0e6fe583f --- /dev/null +++ b/code/datums/ai/generic_behaviors/fail.dm @@ -0,0 +1,4 @@ +/datum/bt_node/ai_behavior/fail + +/datum/bt_node/ai_behavior/fail/perform(seconds_per_tick, datum/ai_controller/controller) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED diff --git a/code/datums/ai/generic_behaviors/find_furthest_turf_from_target.dm b/code/datums/ai/generic_behaviors/find_furthest_turf_from_target.dm new file mode 100644 index 00000000000..015d63c8e9e --- /dev/null +++ b/code/datums/ai/generic_behaviors/find_furthest_turf_from_target.dm @@ -0,0 +1,32 @@ +/// Finds the open turf furthest from the keyed target and stores it, used to pick an escape destination. +/// Returns INSTANT SUCCESS if a turf is found, INSTANT FAILURE if none are available. +/datum/bt_node/ai_behavior/find_furthest_turf_from_target + /// Blackboard key holding the atom to flee from. + var/target_key + /// Blackboard key to store the chosen turf in. + var/set_key + /// How many tiles outward from the target to scan. + var/range = 2 + +/datum/bt_node/ai_behavior/find_furthest_turf_from_target/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/living_target = controller.blackboard[target_key] + if(QDELETED(living_target)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + + var/best_distance = 0 + var/turf/chosen_turf + for(var/turf/open/potential_destination in oview(range, living_target)) + if(potential_destination.is_blocked_turf()) + continue + var/new_distance = get_dist(potential_destination, living_target) + if(new_distance > best_distance) + chosen_turf = potential_destination + best_distance = new_distance + if(best_distance == range) + break // already at the furthest possible distance + + if(isnull(chosen_turf)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + + controller.set_blackboard_key(set_key, chosen_turf) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED diff --git a/code/datums/ai/generic_behaviors/find_nearby.dm b/code/datums/ai/generic_behaviors/find_nearby.dm new file mode 100644 index 00000000000..427b7ea79bd --- /dev/null +++ b/code/datums/ai/generic_behaviors/find_nearby.dm @@ -0,0 +1,21 @@ +/// Picks a random visible, non-abstract atom within range 2 and stores it in a blackboard key. +/datum/bt_node/ai_behavior/find_nearby + /// Blackboard key to store the found atom in. + var/target_key + +/datum/bt_node/ai_behavior/find_nearby/perform(seconds_per_tick, datum/ai_controller/controller) + var/list/possible_targets = list() + for(var/atom/thing in view(2, controller.pawn)) + if(!thing.mouse_opacity) + continue + if(thing.IsObscured()) + continue + if(isitem(thing)) + var/obj/item/item = thing + if(item.item_flags & ABSTRACT) + continue + possible_targets += thing + if(!possible_targets.len) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + controller.set_blackboard_key(target_key, pick(possible_targets)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED diff --git a/code/datums/ai/generic_behaviors/find_target_facing_turf.dm b/code/datums/ai/generic_behaviors/find_target_facing_turf.dm new file mode 100644 index 00000000000..5c968ad0d91 --- /dev/null +++ b/code/datums/ai/generic_behaviors/find_target_facing_turf.dm @@ -0,0 +1,19 @@ +/// Finds the turf directly in front of the keyed target (the tile it is facing) and stores it, +/// so we can line up an action on its front. Fails if the turf is missing or blocked. +/datum/bt_node/ai_behavior/find_target_facing_turf + /// Blackboard key holding the target we want to line up against. + var/target_key + /// Blackboard key to write the found turf into. + var/set_key + +/datum/bt_node/ai_behavior/find_target_facing_turf/perform(seconds_per_tick, datum/ai_controller/controller) + var/atom/target = controller.blackboard[target_key] + if(QDELETED(target)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + + var/turf/facing_turf = get_step(target, target.dir) + if(isnull(facing_turf) || facing_turf.is_blocked_turf(ignore_atoms = list(controller.pawn))) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + + controller.set_blackboard_key(set_key, facing_turf) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED diff --git a/code/datums/ai/generic_behaviors/find_unwebbed_turf.dm b/code/datums/ai/generic_behaviors/find_unwebbed_turf.dm new file mode 100644 index 00000000000..5232fe829f9 --- /dev/null +++ b/code/datums/ai/generic_behaviors/find_unwebbed_turf.dm @@ -0,0 +1,46 @@ +/// Find a nearby unwebbed turf to spin webs on and store it in a blackboard key. +/// Returns INSTANT SUCCESS if we already have a valid target, or if we find a new one. +/// Returns INSTANT FAILURE if no valid turf is nearby. +/datum/bt_node/ai_behavior/find_unwebbed_turf + /// How many tiles outward to scan for valid turfs. + var/scan_range = 3 + /// Blackboard key holding/storing the unwebbed turf. + var/target_key + +/datum/bt_node/ai_behavior/find_unwebbed_turf/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/spider = controller.pawn + var/atom/current_target = controller.blackboard[target_key] + if(current_target && !(locate(/obj/structure/spider/stickyweb) in current_target)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED + + controller.clear_blackboard_key(target_key) + var/turf/our_turf = get_turf(spider) + if(is_valid_web_turf(our_turf, spider)) + controller.set_blackboard_key(target_key, our_turf) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED + + var/list/turfs_by_range = list() + for(var/i in 1 to scan_range) + turfs_by_range["[i]"] = list() + for(var/turf/turf_in_view in oview(scan_range, our_turf)) + if(!is_valid_web_turf(turf_in_view, spider)) + continue + turfs_by_range["[get_dist(our_turf, turf_in_view)]"] += turf_in_view + + var/list/final_turfs + for(var/list/turf_list as anything in turfs_by_range) + if(length(turfs_by_range[turf_list])) + final_turfs = turfs_by_range[turf_list] + break + if(!length(final_turfs)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + + controller.set_blackboard_key(target_key, pick(final_turfs)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED + +/datum/bt_node/ai_behavior/find_unwebbed_turf/proc/is_valid_web_turf(turf/target_turf, mob/living/spider) + if(locate(/obj/structure/spider/stickyweb) in target_turf) + return FALSE + if(HAS_TRAIT(target_turf, TRAIT_SPINNING_WEB_TURF)) + return FALSE + return !target_turf.is_blocked_turf(source_atom = spider) diff --git a/code/datums/ai/generic_behaviors/find_valid_teleport_location.dm b/code/datums/ai/generic_behaviors/find_valid_teleport_location.dm new file mode 100644 index 00000000000..4b0eeeb0529 --- /dev/null +++ b/code/datums/ai/generic_behaviors/find_valid_teleport_location.dm @@ -0,0 +1,28 @@ +/// Finds a random open turf near the keyed target that the target can see, used to pick an ambush teleport destination. +/// Returns SUCCESS if a turf is found, FAILURE if none are available. +/datum/bt_node/ai_behavior/find_valid_teleport_location + /// Blackboard key holding the atom to teleport next to. + var/target_key = BB_CURRENT_TARGET + /// Blackboard key to store the chosen turf in. + var/set_key + /// How many tiles outward from the target to scan. + var/range = 3 + +/datum/bt_node/ai_behavior/find_valid_teleport_location/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/target = controller.blackboard[target_key] + if(QDELETED(target)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + var/list/possible_turfs = list() + for(var/turf/open/potential_turf in oview(range, target)) + if(potential_turf.is_blocked_turf()) + continue + if(!can_see(target, potential_turf, range)) + continue + possible_turfs += potential_turf + + if(!length(possible_turfs)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + controller.set_blackboard_key(set_key, pick(possible_turfs)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED diff --git a/code/datums/ai/generic_behaviors/give.dm b/code/datums/ai/generic_behaviors/give.dm new file mode 100644 index 00000000000..7ff038f1d3a --- /dev/null +++ b/code/datums/ai/generic_behaviors/give.dm @@ -0,0 +1,86 @@ +/// gives the pawn's active held item to a blackboard-keyed target. +/datum/bt_node/ai_behavior/give + /// Blackboard key holding the mob to give the held item to. + var/target_key + /// Target snapshotted in perform(), for perform_async() to read. + VAR_PRIVATE/mob/living/give_target + /// Held item snapshotted in perform(), for perform_async() to read. + VAR_PRIVATE/obj/item/give_held_item + +/datum/bt_node/ai_behavior/give/perform(seconds_per_tick, datum/ai_controller/controller) + var/async_flags = handle_async() + if(async_flags) + return async_flags + + var/mob/living/pawn = controller.pawn + var/obj/item/held_item = pawn.get_active_held_item() + var/atom/target = controller.blackboard[target_key] + + if(!held_item) //if held_item is null, we pretend that action was successful + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + + if(QDELETED(target) || !target.IsReachableBy(pawn)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + var/mob/living/living_target = target + if(!isliving(living_target)) // target should reasonably only ever be set to a living mob + stack_trace("Tried to give an item to a non-living target!") + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + if(!can_give_item(living_target, held_item)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + living_target.visible_message( + span_info("[pawn] starts trying to give [held_item] to [living_target]!"), + span_warning("[pawn] tries to give you [held_item]!") + ) + give_target = living_target + give_held_item = held_item + return start_async() + +/datum/bt_node/ai_behavior/give/perform_async(datum/ai_controller/controller) + var/mob/living/pawn = controller.pawn + var/mob/living/living_target = give_target + var/obj/item/held_item = give_held_item + var/result_flags = AI_BEHAVIOR_FAILED + if(do_after(pawn, 1 SECONDS, living_target)) + result_flags = try_to_give_item(living_target, held_item) + finish_async(result_flags) + +/// Returns a list(has_left_pocket, has_right_pocket, has_valid_hand) if the item can be given, null otherwise. +/datum/bt_node/ai_behavior/give/proc/can_give_item(mob/living/target, obj/item/held_item) + if(QDELETED(held_item) || QDELETED(target)) + return null + + var/has_left_pocket = target.can_equip(held_item, ITEM_SLOT_LPOCKET) + var/has_right_pocket = target.can_equip(held_item, ITEM_SLOT_RPOCKET) + var/has_valid_hand + + for(var/hand_index in target.get_empty_held_indexes()) + if(target.can_put_in_hand(held_item, hand_index)) + has_valid_hand = TRUE + break + + if(!has_left_pocket && !has_right_pocket && !has_valid_hand) + return null + + return list(has_left_pocket, has_right_pocket, has_valid_hand) + +/datum/bt_node/ai_behavior/give/proc/try_to_give_item(mob/living/target, obj/item/held_item) + var/list/give_slots = can_give_item(target, held_item) + if(!give_slots) + return AI_BEHAVIOR_FAILED + + var/has_left_pocket = give_slots[1] + var/has_valid_hand = give_slots[3] + + if(!has_valid_hand || prob(50)) + target.equip_to_slot_if_possible(held_item, (!has_left_pocket ? ITEM_SLOT_RPOCKET : (prob(50) ? ITEM_SLOT_LPOCKET : ITEM_SLOT_RPOCKET))) + else + INVOKE_ASYNC(target, TYPE_PROC_REF(/mob, put_in_hands), held_item) + return AI_BEHAVIOR_SUCCEEDED + +/datum/bt_node/ai_behavior/give/finish_action(datum/ai_controller/controller, succeeded) + . = ..() + give_target = null + give_held_item = null + controller.clear_blackboard_key(target_key) diff --git a/code/datums/ai/generic_behaviors/grab_target.dm b/code/datums/ai/generic_behaviors/grab_target.dm new file mode 100644 index 00000000000..8698199de1d --- /dev/null +++ b/code/datums/ai/generic_behaviors/grab_target.dm @@ -0,0 +1,33 @@ +/** + * Grabs (starts pulling) the atom at the given blackboard key. + * Succeeds immediately if already pulling the target. + * Fails if the target is anchored + */ +/datum/bt_node/ai_behavior/grab_target + /// Blackboard key holding the atom to grab. + var/target_key + +/datum/bt_node/ai_behavior/grab_target/perform(seconds_per_tick, datum/ai_controller/controller) + var/async_flags = handle_async() + if(async_flags) + return async_flags + + var/atom/movable/target = controller.blackboard[target_key] + if(QDELETED(target) || target.anchored) + EVLOG_TEXT(controller, EVLOG_CATEGORY_AI_BEHAVIORS, "[controller.pawn] grab_target: can't grab [target] (deleted=[QDELETED(target)], anchored=[target?.anchored])") + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + var/mob/living/our_mob = controller.pawn + if(our_mob.pulling == target) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + + return start_async() + +/datum/bt_node/ai_behavior/grab_target/perform_async(datum/ai_controller/controller) + var/mob/living/our_mob = controller.pawn + var/atom/movable/target = controller.blackboard[target_key] + var/result = our_mob.start_pulling(target) + if(!async_still_valid()) + return + if(result) + EVLOG_MAPTEXT(controller, EVLOG_CATEGORY_AI_BEHAVIORS, "[our_mob] grabbing [target]", get_turf(target), "Grab") + finish_async(result ? AI_BEHAVIOR_SUCCEEDED : AI_BEHAVIOR_FAILED) diff --git a/code/datums/ai/generic_behaviors/heal_eye_damage.dm b/code/datums/ai/generic_behaviors/heal_eye_damage.dm new file mode 100644 index 00000000000..61f61c75bf5 --- /dev/null +++ b/code/datums/ai/generic_behaviors/heal_eye_damage.dm @@ -0,0 +1,22 @@ +/// Heals the eye damage of the keyed target. Movement to the target is handled externally. +/datum/bt_node/ai_behavior/heal_eye_damage + /// Blackboard key holding the target whose eyes we heal. + var/target_key + +/datum/bt_node/ai_behavior/heal_eye_damage/setup(datum/ai_controller/controller) + var/mob/living/carbon/target = controller.blackboard[target_key] + return !QDELETED(target) + +/datum/bt_node/ai_behavior/heal_eye_damage/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/carbon/target = controller.blackboard[target_key] + if(QDELETED(target)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + var/mob/living/basic/eyeball/eye = controller.pawn + var/obj/item/organ/eyes/eyes = target.get_organ_slot(ORGAN_SLOT_EYES) + eye.heal_eye_damage(target, eyes) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + +/datum/bt_node/ai_behavior/heal_eye_damage/finish_action(datum/ai_controller/controller, succeeded) + . = ..() + controller.clear_blackboard_key(target_key) diff --git a/code/datums/ai/generic_behaviors/hunt_target.dm b/code/datums/ai/generic_behaviors/hunt_target.dm new file mode 100644 index 00000000000..5eb684e79db --- /dev/null +++ b/code/datums/ai/generic_behaviors/hunt_target.dm @@ -0,0 +1,184 @@ +/// Performs an action on a blackboard-keyed hunt target once in range. +/// Movement to the target is handled externally via a move_to_target leaf. +/datum/bt_node/ai_behavior/hunt_target + /// Blackboard key holding the atom to hunt + var/target_key + /// Blackboard key to record the cooldown timestamp on success; if null, no cooldown is set + var/cooldown_key + /// Duration of the post-hunt cooldown + var/hunt_cooldown = 5 SECONDS + /// If TRUE, clears target_key after every hunt regardless of success + var/always_reset_target = FALSE + /// Target snapshotted in perform() for async subtypes, for perform_async() to read. + VAR_PRIVATE/atom/hunt_async_target + +/datum/bt_node/ai_behavior/hunt_target/setup(datum/ai_controller/controller) + var/atom/hunted = controller.blackboard[target_key] + return !QDELETED(hunted) + +/datum/bt_node/ai_behavior/hunt_target/perform(seconds_per_tick, datum/ai_controller/controller) + var/atom/hunted = controller.blackboard[target_key] + if(QDELETED(hunted)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + target_caught(controller.pawn, hunted) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + +/datum/bt_node/ai_behavior/hunt_target/proc/target_caught(mob/living/hunter, atom/hunted) + if(isliving(hunted)) + var/mob/living/living_target = hunted + hunter.manual_emote("chomps [living_target]!") + living_target.investigate_log("has been killed by [key_name(hunter)].", INVESTIGATE_DEATHS) + living_target.death() + else if(IS_EDIBLE(hunted)) + hunted.attack_animal(hunter) + else + hunter.manual_emote("chomps [hunted]!") + qdel(hunted) + +/datum/bt_node/ai_behavior/hunt_target/finish_action(datum/ai_controller/controller, succeeded) + . = ..() + hunt_async_target = null + if(succeeded && cooldown_key) + controller.set_blackboard_key(cooldown_key, world.time + hunt_cooldown) + else if(target_key) + controller.clear_blackboard_key(target_key) + if(always_reset_target && target_key) + controller.clear_blackboard_key(target_key) + +/// Uses ai_interact() on the target instead of default kill/eat/qdel. +/// behavior_combat_mode, always_reset_target, and hunt_cooldown are all configurable +/datum/bt_node/ai_behavior/hunt_target/interact_with_target + /// Combat mode to use when interacting with the target + var/behavior_combat_mode = TRUE + +/datum/bt_node/ai_behavior/hunt_target/interact_with_target/perform(seconds_per_tick, datum/ai_controller/controller) + var/async_flags = handle_async() + if(async_flags) + return async_flags + + var/atom/hunted = controller.blackboard[target_key] + if(QDELETED(hunted)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + hunt_async_target = hunted + return start_async() + +/datum/bt_node/ai_behavior/hunt_target/interact_with_target/perform_async(datum/ai_controller/controller) + target_caught(controller.pawn, hunt_async_target) + if(!async_still_valid()) + return + finish_async(AI_BEHAVIOR_SUCCEEDED) + +/datum/bt_node/ai_behavior/hunt_target/interact_with_target/target_caught(mob/living/hunter, atom/hunted) + hunter.ai_controller.ai_interact(target = hunted, combat_mode = behavior_combat_mode) + +/// Uses a cooldown ability from ability_key on the target. +/datum/bt_node/ai_behavior/hunt_target/use_ability_on_target + always_reset_target = TRUE + /// Blackboard key holding the /datum/action/cooldown ability to use + var/ability_key + /// Ability snapshotted in perform(), for perform_async() to read. + VAR_PRIVATE/datum/action/cooldown/hunt_async_ability + +/datum/bt_node/ai_behavior/hunt_target/use_ability_on_target/perform(seconds_per_tick, datum/ai_controller/controller) + var/async_flags = handle_async() + if(async_flags) + return async_flags + + var/datum/action/cooldown/ability = controller.blackboard[ability_key] + if(!ability?.IsAvailable()) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + var/atom/hunted = controller.blackboard[target_key] + if(QDELETED(hunted)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + hunt_async_target = hunted + hunt_async_ability = ability + return start_async() + +/datum/bt_node/ai_behavior/hunt_target/use_ability_on_target/perform_async(datum/ai_controller/controller) + target_caught(controller.pawn, hunt_async_target) + if(!async_still_valid()) + return + finish_async(AI_BEHAVIOR_SUCCEEDED) + +/datum/bt_node/ai_behavior/hunt_target/use_ability_on_target/finish_action(datum/ai_controller/controller, succeeded) + . = ..() + hunt_async_ability = null + +/datum/bt_node/ai_behavior/hunt_target/use_ability_on_target/target_caught(mob/living/hunter, atom/hunted) + hunt_async_ability.InterceptClickOn(hunter, null, hunted) + +/// Celebrates around the target with a spin animation. +/datum/bt_node/ai_behavior/hunt_target/snail_people + always_reset_target = TRUE + +/datum/bt_node/ai_behavior/hunt_target/snail_people/target_caught(mob/living/hunter, atom/hunted) + hunter.manual_emote("Celebrates around [hunted]!") + hunter.SpinAnimation(speed = 1, loops = 3) + +/// Starts pulling the target item toward the hunter. +/datum/bt_node/ai_behavior/hunt_target/pull_target + always_reset_target = TRUE + +/datum/bt_node/ai_behavior/hunt_target/pull_target/perform(seconds_per_tick, datum/ai_controller/controller) + var/async_flags = handle_async() + if(async_flags) + return async_flags + + var/obj/item/hunted = controller.blackboard[target_key] + if(QDELETED(hunted)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + hunt_async_target = hunted + return start_async() + +/datum/bt_node/ai_behavior/hunt_target/pull_target/perform_async(datum/ai_controller/controller) + target_caught(controller.pawn, hunt_async_target) + if(!async_still_valid()) + return + finish_async(AI_BEHAVIOR_SUCCEEDED) + +/datum/bt_node/ai_behavior/hunt_target/pull_target/target_caught(mob/living/hunter, obj/item/hunted) + hunter.start_pulling(hunted) + +/// Emotes enjoyment of the target's scent. +/datum/bt_node/ai_behavior/hunt_target/sniff_flora + always_reset_target = TRUE + +/datum/bt_node/ai_behavior/hunt_target/sniff_flora/target_caught(mob/living/hunter, atom/hunted) + hunter.manual_emote("Enjoys the sweet scent eminating from [hunted::name]!") + +/// Playfully headbutts the target's legs. +/datum/bt_node/ai_behavior/hunt_target/headbutt_leg + always_reset_target = TRUE + +/datum/bt_node/ai_behavior/hunt_target/headbutt_leg/target_caught(mob/living/hunter, atom/hunted) + hunter.manual_emote("playfully headbutts [hunted]'s legs!") + +/// Attempts to buckle (latch onto) the target mob. +/datum/bt_node/ai_behavior/hunt_target/latch_onto + +/datum/bt_node/ai_behavior/hunt_target/latch_onto/setup(datum/ai_controller/controller) + if(!..()) + return FALSE + var/mob/living/living_pawn = controller.pawn + return !living_pawn.buckled + +/datum/bt_node/ai_behavior/hunt_target/latch_onto/target_caught(mob/living/hunter, obj/hunted) + if(hunter.buckled) + return FALSE + if(!hunted.buckle_mob(hunter, force = TRUE)) + return FALSE + hunted.visible_message(span_notice("[hunted] has been latched onto by [hunter]!")) + return TRUE + + +/datum/bt_node/ai_behavior/hunt_target/play_with_owner + always_reset_target = TRUE + +/datum/bt_node/ai_behavior/hunt_target/play_with_owner/target_caught(mob/living/hunter, atom/hunted) + var/list/interactions_list = hunter.ai_controller.blackboard[BB_INTERACTIONS_WITH_OWNER] + var/interaction_message = length(interactions_list) ? pick(interactions_list) : "Plays with" + hunter.manual_emote("[interaction_message] [hunted]!") diff --git a/code/datums/ai/generic_behaviors/issue_pet_command.dm b/code/datums/ai/generic_behaviors/issue_pet_command.dm new file mode 100644 index 00000000000..92aaea16e22 --- /dev/null +++ b/code/datums/ai/generic_behaviors/issue_pet_command.dm @@ -0,0 +1,41 @@ +/** + * Issues a spoken pet command and points at the target. + * Requires a blackboard key holding a list of /datum/pet_command instances. + * Use a cooldown decorator in the tree time_between_perform has no effect on one-shot behaviors. + */ +/datum/bt_node/ai_behavior/issue_pet_command + /// Blackboard key holding a list of /datum/pet_command instances to search. + var/command_list_key + /// Typepath of the /datum/pet_command to locate in the list. + var/command_type + /// Blackboard key holding the target atom to point at. + var/target_key + /// If set, setup() requires at least one mob of this type within command_distance. + var/commandable_mob_type + /// Range to search for commandable mobs. + var/command_distance = 5 + +/datum/bt_node/ai_behavior/issue_pet_command/setup(datum/ai_controller/controller) + . = ..() + if(!.) + return FALSE + if(commandable_mob_type) + if(!locate(commandable_mob_type) in oview(command_distance, controller.pawn)) + return FALSE + var/atom/target = controller.blackboard[target_key] + return !QDELETED(target) + +/datum/bt_node/ai_behavior/issue_pet_command/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/basic/living_pawn = controller.pawn + var/atom/target = controller.blackboard[target_key] + if(QDELETED(target)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + var/list/commands = controller.blackboard[command_list_key] + if(!length(commands)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + var/datum/pet_command/cmd = locate(command_type) in commands + if(isnull(cmd)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + INVOKE_ASYNC(living_pawn, TYPE_PROC_REF(/atom/movable, say), pick(cmd.speech_commands), forced = "controller") + INVOKE_ASYNC(living_pawn, TYPE_PROC_REF(/mob, _pointed), target) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED diff --git a/code/datums/ai/generic_behaviors/keep_playing_instrument.dm b/code/datums/ai/generic_behaviors/keep_playing_instrument.dm new file mode 100644 index 00000000000..e0d9c28f27a --- /dev/null +++ b/code/datums/ai/generic_behaviors/keep_playing_instrument.dm @@ -0,0 +1,19 @@ +/// Checks that a song instrument's song is still properly configured and playing. +/// Returns SUCCEEDED while the song is playing correctly, FAILED when it needs setup or restart. +/datum/bt_node/ai_behavior/keep_playing_instrument + time_between_perform = 1 SECONDS + /// Blackboard key holding the instrument being played. + var/song_instrument_key + +/datum/bt_node/ai_behavior/keep_playing_instrument/perform(seconds_per_tick, datum/ai_controller/controller) + var/obj/item/instrument/song_player = controller.blackboard[song_instrument_key] + if(QDELETED(song_player)) + controller.clear_blackboard_key(song_instrument_key) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + var/list/parsed_song_lines = splittext(controller.blackboard[BB_SONG_LINES], "\n") + popleft(parsed_song_lines) // remove BPM as it is parsed out + if(!compare_list(song_player.song.lines, parsed_song_lines) || !song_player.song.repeat) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + if(!song_player.song.playing) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED diff --git a/code/datums/ai/generic_behaviors/maintain_distance.dm b/code/datums/ai/generic_behaviors/maintain_distance.dm new file mode 100644 index 00000000000..0f48cc359cb --- /dev/null +++ b/code/datums/ai/generic_behaviors/maintain_distance.dm @@ -0,0 +1,94 @@ +/// Moves away from a target if too close, or toward it if too far, to stay within the blackboard-keyed distance band. +/datum/bt_node/ai_behavior/maintain_distance + /// Set by on_movement_failed() when the movement system gives up pathing. + var/movement_failed = FALSE + /// Blackboard key holding the atom to keep distance from. + var/target_key + /// Blackboard key holding the minimum desired distance. + var/min_dist_key = BB_RANGED_SKIRMISH_MIN_DISTANCE + /// Blackboard key holding the maximum desired distance. + var/max_dist_key = BB_RANGED_SKIRMISH_MAX_DISTANCE + /// Movement type to use while approaching a target that's too far. Null falls back to the controller's default movement type. Reset on finish. + var/approach_movement_type = null + +/datum/bt_node/ai_behavior/maintain_distance/setup(datum/ai_controller/controller) + var/atom/target = controller.blackboard[target_key] + if(!isliving(target) || !can_see(controller.pawn, target, 10)) + return FALSE + RegisterSignal(controller.pawn, COMSIG_MOB_AI_MOVEMENT_FAILED, PROC_REF(on_movement_failed)) + return TRUE + +/datum/bt_node/ai_behavior/maintain_distance/proc/on_movement_failed(atom/source) + SIGNAL_HANDLER + movement_failed = TRUE + +/datum/bt_node/ai_behavior/maintain_distance/perform(seconds_per_tick, datum/ai_controller/controller) + if(movement_failed) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + var/atom/target = controller.blackboard[target_key] + if(QDELETED(target)) + return AI_BEHAVIOR_FAILED + + + var/minimum_distance = controller.blackboard[min_dist_key] || 4 + var/maximum_distance = controller.blackboard[max_dist_key] || 6 + var/range = get_dist(controller.pawn, target) + + if(range >= minimum_distance && range <= maximum_distance) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED + + if(range < minimum_distance) + if(!retreat(controller, target, minimum_distance)) + return AI_BEHAVIOR_FAILED + else + controller.change_ai_movement_type(approach_movement_type || initial(controller.ai_movement)) + controller.ai_movement.start_moving_towards(controller, target, maximum_distance) + + return AI_BEHAVIOR_INSTANT + +/datum/bt_node/ai_behavior/maintain_distance/finish_action(datum/ai_controller/controller, succeeded) + UnregisterSignal(controller.pawn, COMSIG_MOB_AI_MOVEMENT_FAILED) + movement_failed = FALSE + controller.ai_movement.stop_moving_towards(controller) + controller.change_ai_movement_type(initial(controller.ai_movement)) + return ..() + +/// Steps one tile away from target using backstep avoidance, falling back to shuffled directions if blocked. +/datum/bt_node/ai_behavior/maintain_distance/proc/retreat(datum/ai_controller/controller, atom/target, minimum_distance) + controller.change_ai_movement_type(/datum/ai_movement/basic_avoidance/backstep) + var/mob/pawn = controller.pawn + pawn.face_atom(target) + var/turf/next_step = get_step_away(pawn, target) + if(!isnull(next_step) && !next_step.is_blocked_turf(exclude_mobs = TRUE)) + controller.ai_movement.start_moving_towards(controller, next_step, 0, controller.movement_delay * 2) + return TRUE + var/list/all_dirs = GLOB.alldirs.Copy() + all_dirs -= get_dir(pawn, next_step) + all_dirs -= get_dir(pawn, target) + shuffle_inplace(all_dirs) + for(var/dir in all_dirs) + next_step = get_step(pawn, dir) + if(!isnull(next_step) && !next_step.is_blocked_turf(exclude_mobs = TRUE)) + controller.ai_movement.start_moving_towards(controller, next_step, 0, controller.movement_delay * 2) + return TRUE + return FALSE + +/// Retreats to the furthest available open turf within reach rather than a single step. +/datum/bt_node/ai_behavior/maintain_distance/cover_minimum_distance + +/datum/bt_node/ai_behavior/maintain_distance/cover_minimum_distance/retreat(datum/ai_controller/controller, atom/target, minimum_distance) + var/atom/movable/pawn = controller.pawn + var/required_distance = minimum_distance - get_dist(pawn, target) + var/best_distance = 0 + var/turf/chosen_turf + for(var/turf/open/potential_turf in oview(required_distance, pawn)) + if(potential_turf.is_blocked_turf()) + continue + var/new_distance = get_dist(potential_turf, target) + if(new_distance > best_distance) + chosen_turf = potential_turf + best_distance = new_distance + if(isnull(chosen_turf)) + return FALSE + controller.ai_movement.start_moving_towards(controller, chosen_turf, 0) + return TRUE diff --git a/code/datums/ai/generic_behaviors/mine_walls.dm b/code/datums/ai/generic_behaviors/mine_walls.dm new file mode 100644 index 00000000000..aa6f95061b2 --- /dev/null +++ b/code/datums/ai/generic_behaviors/mine_walls.dm @@ -0,0 +1,53 @@ +/// Searches for a nearby mineral wall the pawn can mine and sets the target key. +/datum/bt_node/ai_behavior/find_mineral_wall + time_between_perform = 2 SECONDS + /// Blackboard key to store the found mineral wall in. + var/target_key + +/datum/bt_node/ai_behavior/find_mineral_wall/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living_pawn = controller.pawn + for(var/turf/closed/mineral/potential_wall in oview(9, living_pawn)) + if(!check_if_mineable(controller, potential_wall)) + continue + controller.set_blackboard_key(target_key, potential_wall) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + +/// Returns TRUE if the given wall can be approached and mined. +/datum/bt_node/ai_behavior/find_mineral_wall/proc/check_if_mineable(datum/ai_controller/controller, turf/target_wall) + var/mob/living/source = controller.pawn + var/direction_to_turf = get_dir(target_wall, source) + if(!ISDIAGONALDIR(direction_to_turf)) + return TRUE + for(var/direction_check in GLOB.cardinals) + if(!(direction_check & direction_to_turf)) + continue + var/turf/test_turf = get_step(target_wall, direction_check) + if(isnull(test_turf)) + continue + if(!test_turf.is_blocked_turf(ignore_atoms = list(source))) + return TRUE + return FALSE + +/// Mines the mineral wall at target_key when adjacent. Clears the target key on finish. +/datum/bt_node/ai_behavior/mine_wall + /// Blackboard key holding the mineral wall to mine. + var/target_key + +/datum/bt_node/ai_behavior/mine_wall/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/basic/living_pawn = controller.pawn + var/turf/closed/mineral/target = controller.blackboard[target_key] + if(QDELETED(target)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + if(!living_pawn.Adjacent(target)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + if(!controller.ai_can_interact(target)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + INVOKE_ASYNC(controller, TYPE_PROC_REF(/datum/ai_controller, ai_interact), target) + if(istype(target, /turf/closed/mineral/gibtonite)) + living_pawn.manual_emote("sighs...") + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + +/datum/bt_node/ai_behavior/mine_wall/finish_action(datum/ai_controller/controller, succeeded) + . = ..() + controller.clear_blackboard_key(target_key) diff --git a/code/datums/ai/generic_behaviors/move_to_target.dm b/code/datums/ai/generic_behaviors/move_to_target.dm new file mode 100644 index 00000000000..076012a5917 --- /dev/null +++ b/code/datums/ai/generic_behaviors/move_to_target.dm @@ -0,0 +1,55 @@ +/// Moves toward a blackboard-keyed target each tick. Succeeds when within required_dist by default. +/// Pass finish_on_arrival = FALSE to keep running indefinitely (used in combat parallels). +/// Pass movement_type to temporarily override the controller's ai_movement; resets to the initial type on finish. +/datum/bt_node/ai_behavior/move_to_target + /// Set by on_movement_failed() when the movement system gives up pathing. + VAR_FINAL/movement_failed = FALSE + /// Blackboard key holding the atom to move toward. + var/target_key + /// Distance at which arrival is considered reached. + var/required_dist = 1 + /// Whether to succeed once within required_dist; FALSE keeps running indefinitely. + var/finish_on_arrival = TRUE + /// Optional ai_movement type override; resets to the initial type on finish. + var/movement_type = null + /// Tracks the last atom we started moving toward so we can retarget when the key changes. + VAR_PRIVATE/atom/tracked_target + +/datum/bt_node/ai_behavior/move_to_target/setup(datum/ai_controller/controller) + var/atom/target = controller.blackboard[target_key] + if(QDELETED(target)) + return FALSE + if(movement_type) + controller.change_ai_movement_type(movement_type) + RegisterSignal(controller.pawn, COMSIG_MOB_AI_MOVEMENT_FAILED, PROC_REF(on_movement_failed)) + controller.ai_movement.start_moving_towards(controller, target, required_dist) + tracked_target = target + return TRUE + +/datum/bt_node/ai_behavior/move_to_target/proc/on_movement_failed(atom/source) + SIGNAL_HANDLER + movement_failed = TRUE + +/datum/bt_node/ai_behavior/move_to_target/perform(seconds_per_tick, datum/ai_controller/controller) + if(movement_failed) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + var/atom/target = controller.blackboard[target_key] + if(QDELETED(target)) + return AI_BEHAVIOR_FAILED + if(target != tracked_target) + controller.ai_movement.start_moving_towards(controller, target, required_dist) + tracked_target = target + else if(!controller.ai_movement.moving_controllers[controller]) + controller.ai_movement.start_moving_towards(controller, target, required_dist) + if(finish_on_arrival && get_dist(controller.pawn, target) <= required_dist) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED + return AI_BEHAVIOR_INSTANT + +/datum/bt_node/ai_behavior/move_to_target/finish_action(datum/ai_controller/controller, succeeded) + UnregisterSignal(controller.pawn, COMSIG_MOB_AI_MOVEMENT_FAILED) + movement_failed = FALSE + tracked_target = null + controller.ai_movement.stop_moving_towards(controller) + if(movement_type) + controller.change_ai_movement_type(initial(controller.ai_movement)) + return ..() diff --git a/code/datums/ai/generic_behaviors/perform_emote.dm b/code/datums/ai/generic_behaviors/perform_emote.dm new file mode 100644 index 00000000000..c0f49f05066 --- /dev/null +++ b/code/datums/ai/generic_behaviors/perform_emote.dm @@ -0,0 +1,11 @@ +/// Performs a named emote on the pawn (BT-native). Use for standard emote types like "flip", "wave", etc. +/datum/bt_node/ai_behavior/perform_emote + /// Name of the emote to perform. + var/emote + +/datum/bt_node/ai_behavior/perform_emote/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/living_pawn = controller.pawn + if(!istype(living_pawn)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + INVOKE_ASYNC(living_pawn, TYPE_PROC_REF(/mob, emote), emote) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED diff --git a/code/datums/ai/generic_behaviors/pick_random_ability.dm b/code/datums/ai/generic_behaviors/pick_random_ability.dm new file mode 100644 index 00000000000..3f81dbfe096 --- /dev/null +++ b/code/datums/ai/generic_behaviors/pick_random_ability.dm @@ -0,0 +1,27 @@ +/// Picks a random available ability from a pool of blackboard keys, excluding the last-used one. +/// Writes the selected action object to result_key for a targeted_mob_ability leaf to fire. +/// Returns INSTANT FAILURE if no ability in the pool is currently available. +/datum/bt_node/ai_behavior/pick_random_ability + /// List of blackboard key name strings to pick from. + var/list/ability_keys = null + /// Blackboard key storing the last-picked key name string (anti-repeat). Can be null. + var/last_used_key = null + /// Blackboard key to write the selected action object into. + var/result_key = BB_GENERIC_ACTION + +/datum/bt_node/ai_behavior/pick_random_ability/perform(seconds_per_tick, datum/ai_controller/controller) + var/list/possible = ability_keys.Copy() + var/last_used = last_used_key ? controller.blackboard[last_used_key] : null + if(last_used) + possible -= last_used + for(var/bb_key in possible) + var/datum/action/ability = controller.blackboard[bb_key] + if(QDELETED(ability) || !ability.IsAvailable()) + possible -= bb_key + if(!length(possible)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + var/picked_key = pick(possible) + if(last_used_key) + controller.set_blackboard_key(last_used_key, picked_key) + controller.set_blackboard_key(result_key, controller.blackboard[picked_key]) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED diff --git a/code/datums/ai/generic_behaviors/pick_up.dm b/code/datums/ai/generic_behaviors/pick_up.dm new file mode 100644 index 00000000000..fba4ffbacd1 --- /dev/null +++ b/code/datums/ai/generic_behaviors/pick_up.dm @@ -0,0 +1,28 @@ +/// Picks up a blackboard-keyed item. Pair with move_to_target in the BT tree for navigation. +/datum/bt_node/ai_behavior/pick_up + time_between_perform = 2 SECONDS + /// Blackboard key holding the item to pick up. + var/target_key + /// Whether to drop a currently-held item to free a hand. + var/drop_held = TRUE + +/datum/bt_node/ai_behavior/pick_up/setup(datum/ai_controller/controller) + var/atom/target = controller.blackboard[target_key] + return !QDELETED(target) + +/datum/bt_node/ai_behavior/pick_up/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/living_pawn = controller.pawn + var/obj/item/target = controller.blackboard[target_key] + if(QDELETED(target)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + if(living_pawn.is_holding(target)) // already in hands + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + if(!target.IsReachableBy(living_pawn)) // cant reach! + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + if(living_pawn.get_active_held_item()) // something is in our hands already + if(!drop_held || !living_pawn.dropItemToGround(living_pawn.get_active_held_item())) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + INVOKE_ASYNC(controller, TYPE_PROC_REF(/datum/ai_controller, ai_interact), target, FALSE) + return AI_BEHAVIOR_DELAY | (target.loc == living_pawn ? AI_BEHAVIOR_SUCCEEDED : AI_BEHAVIOR_FAILED) diff --git a/code/datums/ai/generic_behaviors/play_instrument.dm b/code/datums/ai/generic_behaviors/play_instrument.dm new file mode 100644 index 00000000000..a2ba4a08fde --- /dev/null +++ b/code/datums/ai/generic_behaviors/play_instrument.dm @@ -0,0 +1,13 @@ +/// Starts playing the song loaded into a blackboard-keyed instrument. +/datum/bt_node/ai_behavior/play_instrument + var/volume = 50 + /// Blackboard key holding the instrument to play. + var/song_instrument_key + +/datum/bt_node/ai_behavior/play_instrument/perform(seconds_per_tick, datum/ai_controller/controller) + var/obj/item/instrument/song_instrument = controller.blackboard[song_instrument_key] + var/datum/song/song = song_instrument.song + song.volume = volume + + song.start_playing(controller.pawn) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED diff --git a/code/datums/ai/generic_behaviors/random_walk.dm b/code/datums/ai/generic_behaviors/random_walk.dm new file mode 100644 index 00000000000..4cbda837ba2 --- /dev/null +++ b/code/datums/ai/generic_behaviors/random_walk.dm @@ -0,0 +1,111 @@ +// BT-native idle wander leaves. +/datum/bt_node/ai_behavior/idle_random_walk + /// Chance that the mob random walks per second. + var/walk_chance = 25 + +/datum/bt_node/ai_behavior/idle_random_walk/proc/can_move(mob/living/living_pawn) + return living_pawn && (living_pawn.mobility_flags & MOBILITY_MOVE) && isturf(living_pawn.loc) && !living_pawn.pulledby + +/datum/bt_node/ai_behavior/idle_random_walk/proc/try_random_step(mob/living/living_pawn, seconds_per_tick, step_walk_chance) + if(LAZYLEN(living_pawn.do_afters)) + return FALSE + + if(!SPT_PROB(step_walk_chance, seconds_per_tick) || !can_move(living_pawn)) + return FALSE + + var/move_dir = pick(GLOB.alldirs) + var/turf/destination_turf = get_step(living_pawn, move_dir) + if(!destination_turf?.can_cross_safely(living_pawn)) + return FALSE + + living_pawn.Move(destination_turf, move_dir) + return TRUE + +/datum/bt_node/ai_behavior/idle_random_walk/proc/try_near_target_step(mob/living/living_pawn, atom/target, seconds_per_tick, step_walk_chance, minimum_distance) + if(isnull(target) || !can_move(living_pawn) || LAZYLEN(living_pawn.do_afters)) + return FALSE + + if(!SPT_PROB(step_walk_chance, seconds_per_tick)) + return FALSE + + var/distance = get_dist(target, living_pawn) + if(distance > minimum_distance) + return try_random_step(living_pawn, seconds_per_tick, step_walk_chance) + + var/list/possible_turfs = list() + for(var/direction in GLOB.alldirs) + var/turf/possible_step = get_step(living_pawn, direction) + if(get_dist(possible_step, target) > minimum_distance) + continue + if(possible_step.is_blocked_turf() || !possible_step.can_cross_safely(living_pawn)) + continue + possible_turfs += possible_step + + if(!length(possible_turfs)) + return FALSE + + var/turf/picked_turf = pick(possible_turfs) + living_pawn.Move(picked_turf, get_dir(living_pawn, picked_turf)) + return TRUE + +/datum/bt_node/ai_behavior/idle_random_walk/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/living_pawn = controller.pawn + try_random_step(living_pawn, seconds_per_tick, walk_chance) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED + + +// Only walk if we don't have a target. +/datum/bt_node/ai_behavior/idle_random_walk/no_target + /// Where do we look for a target? + var/target_key = BB_CURRENT_TARGET + +/datum/bt_node/ai_behavior/idle_random_walk/no_target/perform(seconds_per_tick, datum/ai_controller/controller) + if(controller.blackboard_key_exists(target_key)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + return ..() + +// Only walk if we are not on the target's location. +/datum/bt_node/ai_behavior/idle_random_walk/not_while_on_target + /// What is the spot we have to stand on? + var/target_key + +/datum/bt_node/ai_behavior/idle_random_walk/not_while_on_target/perform(seconds_per_tick, datum/ai_controller/controller) + var/atom/target = controller.blackboard[target_key] + if(!QDELETED(target) && ((isturf(target) && controller.pawn.loc == target) || (target.loc == controller.pawn.loc))) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + return ..() + +// Walk more when healthy and on rust (explore for more), or damaged and off rust (seek healing rust). +/datum/bt_node/ai_behavior/idle_random_walk/rust + +/datum/bt_node/ai_behavior/idle_random_walk/rust/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/living_pawn = controller.pawn + var/turf/my_turf = get_turf(living_pawn) + var/on_rust = HAS_TRAIT(my_turf, TRAIT_RUSTY) + var/damaged = living_pawn.health < living_pawn.maxHealth + try_random_step(living_pawn, seconds_per_tick, (on_rust == damaged) ? 10 : 50) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED + +// Walk randomly, but stay near a target when possible. +/datum/bt_node/ai_behavior/idle_random_walk/walk_near_target + /// Distance we are allowed to stay from the target. + var/minimum_distance = 20 + /// Key that holds target. + var/target_key + +/datum/bt_node/ai_behavior/idle_random_walk/walk_near_target/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/living_pawn = controller.pawn + var/atom/target = controller.blackboard[target_key] + if(QDELETED(target)) + target = null + + if(!can_move(living_pawn) || LAZYLEN(living_pawn.do_afters) || !SPT_PROB(walk_chance, seconds_per_tick)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED + + if(isnull(target) || get_dist(target, living_pawn) > minimum_distance) + try_random_step(living_pawn, seconds_per_tick, walk_chance) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED + + if(!try_near_target_step(living_pawn, target, seconds_per_tick, walk_chance, minimum_distance)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED diff --git a/code/datums/ai/generic_behaviors/resist.dm b/code/datums/ai/generic_behaviors/resist.dm new file mode 100644 index 00000000000..aa5a4311198 --- /dev/null +++ b/code/datums/ai/generic_behaviors/resist.dm @@ -0,0 +1,14 @@ +/datum/bt_node/ai_behavior/resist/perform(seconds_per_tick, datum/ai_controller/controller) + var/async_flags = handle_async() + if(async_flags) + return async_flags + + return start_async() + +/datum/bt_node/ai_behavior/resist/perform_async(datum/ai_controller/controller) + + if(!async_still_valid()) + return + var/mob/living/living_pawn = controller.pawn + living_pawn.execute_resist() + finish_async(AI_BEHAVIOR_SUCCEEDED) diff --git a/code/datums/ai/generic_behaviors/run_emote.dm b/code/datums/ai/generic_behaviors/run_emote.dm new file mode 100644 index 00000000000..985cb18a3b0 --- /dev/null +++ b/code/datums/ai/generic_behaviors/run_emote.dm @@ -0,0 +1,21 @@ +/datum/bt_node/ai_behavior/run_emote + /// Blackboard key holding the emote (or list of emotes) to perform. + var/emote_key = BB_EMOTE_KEY + +/datum/bt_node/ai_behavior/run_emote/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/living_pawn = controller.pawn + if(!isliving(living_pawn)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + + var/list/emote_list = controller.blackboard[emote_key] + var/emote + if(islist(emote_list)) + emote = length(emote_list) ? pick(emote_list) : null + else + emote = emote_list + + if(isnull(emote)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + + INVOKE_ASYNC(living_pawn, TYPE_PROC_REF(/mob, emote), emote) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED diff --git a/code/datums/ai/generic_behaviors/set_bb_cooldown.dm b/code/datums/ai/generic_behaviors/set_bb_cooldown.dm new file mode 100644 index 00000000000..36e03e266e4 --- /dev/null +++ b/code/datums/ai/generic_behaviors/set_bb_cooldown.dm @@ -0,0 +1,10 @@ +/// Sets the given blackboard key to a future timestamp, blocking key_off_cooldown until that time. +/datum/bt_node/ai_behavior/set_bb_cooldown + /// Blackboard key to write the cooldown timestamp into. + var/cooldown_key + /// Cooldown duration, in seconds. + var/cooldown_duration + +/datum/bt_node/ai_behavior/set_bb_cooldown/perform(seconds_per_tick, datum/ai_controller/controller) + controller.set_blackboard_key(cooldown_key, world.time + cooldown_duration) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED diff --git a/code/datums/ai/generic_behaviors/set_bb_key.dm b/code/datums/ai/generic_behaviors/set_bb_key.dm new file mode 100644 index 00000000000..0470bf11622 --- /dev/null +++ b/code/datums/ai/generic_behaviors/set_bb_key.dm @@ -0,0 +1,10 @@ +/// Sets the given blackboard key to a value. +/datum/bt_node/ai_behavior/set_bb_key + /// Blackboard key to set. + var/target_key + /// Value to store in the key. + var/value + +/datum/bt_node/ai_behavior/set_bb_key/perform(seconds_per_tick, datum/ai_controller/controller) + controller.set_blackboard_key(target_key, value) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED diff --git a/code/datums/ai/generic_behaviors/setup_instrument.dm b/code/datums/ai/generic_behaviors/setup_instrument.dm new file mode 100644 index 00000000000..e26bea9a5ca --- /dev/null +++ b/code/datums/ai/generic_behaviors/setup_instrument.dm @@ -0,0 +1,18 @@ +/// Parses and loads a song into a blackboard-keyed instrument, preparing it for playback. +/datum/bt_node/ai_behavior/setup_instrument + /// Blackboard key holding the instrument to load the song into. + var/song_instrument_key + /// Blackboard key holding the song lines to parse. + var/song_lines_key + +/datum/bt_node/ai_behavior/setup_instrument/perform(seconds_per_tick, datum/ai_controller/controller) + var/obj/item/instrument/song_instrument = controller.blackboard[song_instrument_key] + var/datum/song/song = song_instrument.song + var/song_lines = controller.blackboard[song_lines_key] + + //just in case- it won't do anything if the instrument isn't playing + song.stop_playing() + song.ParseSong(new_song = song_lines) + song.repeat = 10 + song.volume = song.max_volume - 10 + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED diff --git a/code/datums/ai/generic_behaviors/speech.dm b/code/datums/ai/generic_behaviors/speech.dm new file mode 100644 index 00000000000..6a5acfc02d4 --- /dev/null +++ b/code/datums/ai/generic_behaviors/speech.dm @@ -0,0 +1,184 @@ +///Random speech behavior, for speech thats random +/datum/bt_node/ai_behavior/random_speech + time_between_perform = 1 SECONDS + /// Chance that the mob will speak. + var/speech_chance = 1 + /// Hearable emotes (e.g. "barks.") played with sound if sound list is populated. + var/list/emote_hear + /// Visible-only emotes (e.g. "wags tail.") no sound. + var/list/emote_see + /// Spoken lines. + var/list/speak + /// Sound files to play alongside emote_hear or speak lines. + var/list/sound + +/datum/bt_node/ai_behavior/random_speech/perform(seconds_per_tick, datum/ai_controller/controller) + if(!prob(speech_chance)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + var/audible = length(emote_hear) + var/visible = length(emote_see) + var/spoken = length(speak) + var/total = audible + visible + spoken + if(!total) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + var/mob/living/pawn = controller.pawn + if(!istype(pawn)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + var/roll = rand(1, total) + + if(roll <= audible) + pawn.manual_emote(pick(emote_hear)) + if(length(sound)) + playsound(pawn, pick(sound), 80, vary = TRUE, pressure_affected = TRUE, ignore_walls = FALSE) + else if(roll <= audible + visible) + pawn.manual_emote(pick(emote_see)) + else + INVOKE_ASYNC(src, PROC_REF(speak), pawn, controller) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + +/datum/bt_node/ai_behavior/random_speech/proc/speak(mob/living/pawn, datum/ai_controller/controller) + pawn.say(pick(speak), forced = "AI Controller") + if(length(sound)) + playsound(pawn, pick(sound), 80, vary = TRUE) + +/datum/bt_node/ai_behavior/random_speech/mothroach + speech_chance = 15 + emote_hear = list("flutters.") + +/datum/bt_node/ai_behavior/random_speech/mouse + speech_chance = 1 + speak = list("Squeak!", "SQUEAK!", "Squeak?") + sound = list('sound/mobs/non-humanoids/mouse/mousesqueek.ogg') + emote_hear = list("squeaks.") + emote_see = list("runs in a circle.", "shakes.") + +/datum/bt_node/ai_behavior/random_speech/frog + speech_chance = 3 + emote_see = list("jumps in a circle.", "shakes.") + +/datum/bt_node/ai_behavior/random_speech/lizard + speech_chance = 3 + emote_hear = list("stamps around some.", "hisses a bit.") + emote_see = list("blehs the tongue.", "tilts the head.", "does a spin.") + +/datum/bt_node/ai_behavior/random_speech/faithless + speech_chance = 1 + emote_see = list("wails.") + +/datum/bt_node/ai_behavior/random_speech/garden_gnome + speech_chance = 5 + speak = list("Gnot a gnelf!", "Gnot a gnoblin!", "Howdy chum!") + emote_hear = list("snores.", "burps.") + emote_see = list("blinks.") + +/datum/bt_node/ai_behavior/random_speech/killer_tomato + speech_chance = 3 + emote_hear = list("gnashes.", "growls lowly.", "snarls.") + emote_see = list("salivates.") + +/datum/bt_node/ai_behavior/random_speech/ant + speech_chance = 1 + speak = list("BZZZZT!", "CHTCHTCHT!", "Bzzz", "ChtChtCht") + sound = list('sound/mobs/non-humanoids/insect/chitter.ogg') + emote_hear = list("buzzes.", "clacks.") + emote_see = list("shakes their head.", "twitches their antennae.") + +/datum/bt_node/ai_behavior/random_speech/fox + speech_chance = 1 + speak = list("Ack-Ack", "Ack-Ack-Ack-Ackawoooo", "Geckers", "Awoo", "Tchoff") + emote_hear = list("howls.", "barks.", "screams.") + emote_see = list("shakes their head.", "shivers.") + +/datum/bt_node/ai_behavior/random_speech/crab + speech_chance = 1 + sound = list('sound/mobs/non-humanoids/crab/claw_click.ogg') + emote_hear = list("clicks.") + emote_see = list("clacks.") + +/datum/bt_node/ai_behavior/random_speech/penguin + speech_chance = 5 + speak = list("Gah Gah!", "NOOT NOOT!", "NOOT!", "Noot", "noot", "Prah!", "Grah!") + emote_hear = list("squawks", "gakkers") + +/datum/bt_node/ai_behavior/random_speech/bear + speech_chance = 5 + emote_hear = list("rawrs.", "grumbles.", "grawls.", "stomps!") + emote_see = list("stares ferociously.") + +/datum/bt_node/ai_behavior/random_speech/cats + speech_chance = 10 + sound = list(SFX_CAT_MEOW) + emote_hear = list("meows.") + emote_see = list("meows.") + +/// Make spooky sounds, if we have a corpse inside then impersonate them +/datum/bt_node/ai_behavior/random_speech/legion + speech_chance = 1 + speak = list("Come...", "Legion...", "Why...?") + emote_hear = list("groans.", "wails.", "whimpers.") + emote_see = list("twitches.", "shudders.") + /// Stuff to specifically say into a radio + var/list/radio_speech = list("Come...", "Why...?") + +/datum/bt_node/ai_behavior/random_speech/legion/speak(mob/living/pawn, datum/ai_controller/controller) + var/mob/living/carbon/human/victim = controller.blackboard[BB_LEGION_CORPSE] + if (QDELETED(victim) || prob(30)) + return ..() + + if (HAS_MIND_TRAIT(victim, TRAIT_MIMING)) // mimes cant talk + return + + var/list/remembered_speech = controller.blackboard[BB_LEGION_RECENT_LINES] || list() + + if (length(remembered_speech) && prob(50)) // Don't spam the radio + pawn.say(pick(remembered_speech), forced = "AI Controller") + return + + var/obj/item/radio/mob_radio = locate() in victim + if (QDELETED(mob_radio)) + return ..() // No radio, just talk funny + mob_radio.talk_into(pawn, pick(radio_speech + remembered_speech), pick(RADIO_CHANNEL_SUPPLY, RADIO_CHANNEL_COMMON)) + +///Speech behavior that reads from a blackboard to pick what to say. Useful for things with dynamic speech behaviors +/datum/bt_node/ai_behavior/random_speech_blackboard + +/datum/bt_node/ai_behavior/random_speech_blackboard/perform(seconds_per_tick, datum/ai_controller/controller) + var/list/speech_lines = controller.blackboard[BB_BASIC_MOB_SPEAK_LINES] + if(isnull(speech_lines)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + var/speech_chance = speech_lines[BB_SPEAK_CHANCE] || 1 + if(!prob(speech_chance)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + var/list/emote_hear = speech_lines[BB_EMOTE_HEAR] || list() + var/list/emote_see = speech_lines[BB_EMOTE_SEE] || list() + var/list/speak = speech_lines[BB_EMOTE_SAY] || list() + var/list/sounds = speech_lines[BB_EMOTE_SOUND] || list() + + var/total = length(emote_hear) + length(emote_see) + length(speak) + if(!total) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + var/mob/living/pawn = controller.pawn + if(!istype(pawn)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + var/sound_to_play = length(sounds) ? pick(sounds) : null + var/roll = rand(1, total) + + if(roll <= length(emote_hear)) + pawn.manual_emote(pick(emote_hear)) + if(sound_to_play) + playsound(pawn, sound_to_play, 80, vary = TRUE, pressure_affected = TRUE, ignore_walls = FALSE) + else if(roll <= length(emote_hear) + length(emote_see)) + pawn.manual_emote(pick(emote_see)) + else + INVOKE_ASYNC(pawn, TYPE_PROC_REF(/atom/movable, say), pick(speak), forced = "AI Controller") + if(sound_to_play) + playsound(pawn, sound_to_play, 80, vary = TRUE) + + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED diff --git a/code/datums/ai/generic_behaviors/spin_web.dm b/code/datums/ai/generic_behaviors/spin_web.dm new file mode 100644 index 00000000000..91f7b684ca8 --- /dev/null +++ b/code/datums/ai/generic_behaviors/spin_web.dm @@ -0,0 +1,35 @@ +/// Trigger a web-spinning action at the current web target turf. +/// Expects move_to_target to have positioned the spider first. +/// Clears BB_SPIDER_WEB_TARGET on finish. +/datum/bt_node/ai_behavior/spin_web + time_between_perform = 15 SECONDS + /// Blackboard key holding the web-spinning action. + var/action_key + /// Blackboard key holding the target turf to web. + var/target_key + +/datum/bt_node/ai_behavior/spin_web/setup(datum/ai_controller/controller) + if(!controller.blackboard_key_exists(action_key) || !controller.blackboard_key_exists(target_key)) + return FALSE + return ..() + +/datum/bt_node/ai_behavior/spin_web/perform(seconds_per_tick, datum/ai_controller/controller) + var/async_flags = handle_async() + if(async_flags) + return async_flags + + var/datum/action/cooldown/web_action = controller.blackboard[action_key] + if(!web_action) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + return start_async() + +/datum/bt_node/ai_behavior/spin_web/perform_async(datum/ai_controller/controller) + var/datum/action/cooldown/web_action = controller.blackboard[action_key] + var/result = web_action.Trigger() + if(!async_still_valid()) + return + finish_async(result ? AI_BEHAVIOR_SUCCEEDED : AI_BEHAVIOR_FAILED) + +/datum/bt_node/ai_behavior/spin_web/finish_action(datum/ai_controller/controller, succeeded) + . = ..() + controller.clear_blackboard_key(target_key) diff --git a/code/datums/ai/generic_behaviors/stop_dragging.dm b/code/datums/ai/generic_behaviors/stop_dragging.dm new file mode 100644 index 00000000000..24c28fea42d --- /dev/null +++ b/code/datums/ai/generic_behaviors/stop_dragging.dm @@ -0,0 +1,7 @@ +/// Stops the pawn from pulling whatever it is currently dragging. +/datum/bt_node/ai_behavior/stop_dragging + +/datum/bt_node/ai_behavior/stop_dragging/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/living_pawn = controller.pawn + living_pawn.stop_pulling() + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED diff --git a/code/datums/ai/generic_behaviors/stuff_in_disposal.dm b/code/datums/ai/generic_behaviors/stuff_in_disposal.dm new file mode 100644 index 00000000000..07b85ad95d9 --- /dev/null +++ b/code/datums/ai/generic_behaviors/stuff_in_disposal.dm @@ -0,0 +1,37 @@ +/// Grabs a downed target mob and stuffs them into a nearby disposal unit. +/datum/bt_node/ai_behavior/stuff_in_disposal + time_between_perform = 2 SECONDS + /// Blackboard key holding the mob to stuff. + var/attack_target_key + /// Blackboard key holding the disposal unit. + var/disposal_target_key + +/datum/bt_node/ai_behavior/stuff_in_disposal/perform(seconds_per_tick, datum/ai_controller/controller) + var/async_flags = handle_async() + if(async_flags) + return async_flags + + var/mob/living/target = controller.blackboard[attack_target_key] + var/obj/machinery/disposal/disposal = controller.blackboard[disposal_target_key] + var/mob/living/living_pawn = controller.pawn + if(QDELETED(target) || QDELETED(disposal)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + if(!living_pawn.Adjacent(disposal)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + return start_async() + +/datum/bt_node/ai_behavior/stuff_in_disposal/perform_async(datum/ai_controller/controller) + var/mob/living/target = controller.blackboard[attack_target_key] + var/obj/machinery/disposal/disposal = controller.blackboard[disposal_target_key] + var/mob/living/living_pawn = controller.pawn + var/stuffed = disposal.stuff_mob_in(target, living_pawn) + if(!async_still_valid()) + return + if(stuffed && !QDELETED(disposal)) + disposal.flush() + finish_async(AI_BEHAVIOR_SUCCEEDED) + +/datum/bt_node/ai_behavior/stuff_in_disposal/finish_action(datum/ai_controller/controller, succeeded) + . = ..() + controller.clear_blackboard_key(attack_target_key) + controller.clear_blackboard_key(disposal_target_key) diff --git a/code/datums/ai/generic_behaviors/succeed.dm b/code/datums/ai/generic_behaviors/succeed.dm new file mode 100644 index 00000000000..19d31d8d406 --- /dev/null +++ b/code/datums/ai/generic_behaviors/succeed.dm @@ -0,0 +1,4 @@ +/datum/bt_node/ai_behavior/succeed + +/datum/bt_node/ai_behavior/succeed/perform(seconds_per_tick, datum/ai_controller/controller) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED diff --git a/code/datums/ai/generic_behaviors/target_retaliate.dm b/code/datums/ai/generic_behaviors/target_retaliate.dm new file mode 100644 index 00000000000..dff7da1d5ba --- /dev/null +++ b/code/datums/ai/generic_behaviors/target_retaliate.dm @@ -0,0 +1,53 @@ + +///Pick a target from our retaliate list +/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list + target_source = /datum/target_source/from_bb_list/retaliate_list + revalidation_mode = TARGET_REVALIDATE + time_between_perform = 2 SECONDS + vision_range = 9 + /// Blackboard key in which to store the target's hiding location. + var/hiding_location_key + /// If FALSE, temporarily ignores faction during the search. + var/check_faction = FALSE + +/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list/perform(seconds_per_tick, datum/ai_controller/controller) + if(!check_faction) // This is lame, but comeon man the polar bears kept killing each other + controller.set_blackboard_key(BB_TEMPORARILY_IGNORE_FACTION, TRUE) + . = ..() + var/usually_ignores_faction = controller.blackboard[BB_ALWAYS_IGNORE_FACTION] || FALSE + controller.set_blackboard_key(BB_TEMPORARILY_IGNORE_FACTION, usually_ignores_faction) + +/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list/filter_candidates(datum/ai_controller/controller, list/candidates, datum/targeting_strategy/strategy, atom/current_target) + var/mob/living/pawn = controller.pawn + var/datum/target_priority_strategy/priority_strategy = GET_TARGET_PRIORITY_STRATEGY(controller.blackboard[BB_TARGET_PRIORITY_STRATEGY]) + var/current_priority = priority_strategy ? priority_strategy.get_target_priority(controller, current_target) : 0 + var/list/filtered = list() + for(var/atom/candidate as anything in candidates) + if(!strategy.is_valid_target(pawn, candidate, vision_range, controller)) + continue + if(priority_strategy && priority_strategy.get_target_priority(controller, candidate) < current_priority) + continue + filtered += candidate + return filtered + +/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list/on_target_found(datum/ai_controller/controller, atom/target, datum/targeting_strategy/strategy) + var/atom/hiding = strategy.find_hidden_mobs(controller.pawn, target) + if(hiding) + controller.set_blackboard_key(hiding_location_key, hiding) + +/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list/on_no_valid_candidates(datum/ai_controller/controller, atom/current_target) + if(current_target) + controller.clear_blackboard_key(target_key) + +/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list/pick_final_target(datum/ai_controller/controller, list/filtered_targets) + var/datum/target_priority_strategy/priority_strategy = GET_TARGET_PRIORITY_STRATEGY(controller.blackboard[BB_TARGET_PRIORITY_STRATEGY]) + if(!priority_strategy) + return pick(filtered_targets) + return priority_strategy.select_target(controller, filtered_targets) + +/// Nearest-attacker variant +/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list/nearest + +/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list/nearest/pick_final_target(datum/ai_controller/controller, list/filtered_targets) + var/turf/our_position = get_turf(controller.pawn) + return get_closest_atom(/atom/, filtered_targets, our_position) diff --git a/code/datums/ai/generic_behaviors/use_in_hand.dm b/code/datums/ai/generic_behaviors/use_in_hand.dm new file mode 100644 index 00000000000..01fc28d5a4c --- /dev/null +++ b/code/datums/ai/generic_behaviors/use_in_hand.dm @@ -0,0 +1,10 @@ +/// Uses the pawn's currently active held item in-hand. +/datum/bt_node/ai_behavior/use_in_hand + +/datum/bt_node/ai_behavior/use_in_hand/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/pawn = controller.pawn + var/obj/item/held = pawn.get_active_held_item() + if(!held) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + INVOKE_ASYNC(pawn, TYPE_PROC_REF(/mob, activate_hand)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED diff --git a/code/datums/ai/generic_behaviors/use_on_object.dm b/code/datums/ai/generic_behaviors/use_on_object.dm new file mode 100644 index 00000000000..5661221406c --- /dev/null +++ b/code/datums/ai/generic_behaviors/use_on_object.dm @@ -0,0 +1,12 @@ +/// Uses the pawn's held item (or unarmed) on a blackboard-keyed target. +/datum/bt_node/ai_behavior/use_on_object + /// Blackboard key holding the atom to use the held item on. + var/target_key + +/datum/bt_node/ai_behavior/use_on_object/perform(seconds_per_tick, datum/ai_controller/controller) + var/atom/target = controller.blackboard[target_key] + if(QDELETED(target)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + INVOKE_ASYNC(controller, TYPE_PROC_REF(/datum/ai_controller, ai_interact), target, FALSE) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED diff --git a/code/datums/ai/generic_behaviors/virtual_pick_up_item.dm b/code/datums/ai/generic_behaviors/virtual_pick_up_item.dm new file mode 100644 index 00000000000..b22367d0b63 --- /dev/null +++ b/code/datums/ai/generic_behaviors/virtual_pick_up_item.dm @@ -0,0 +1,59 @@ +/// Moves the item at target_key onto the pawn and records it in storage_key. Does not use hands storage_key is the virtual carry slot. Clears target_key on finish. +/datum/bt_node/ai_behavior/pick_up_item_virtual + /// Blackboard key holding the item to pick up. + var/target_key + /// Blackboard key acting as the virtual carry slot. + var/storage_key + +/datum/bt_node/ai_behavior/pick_up_item_virtual/setup(datum/ai_controller/controller) + var/obj/item/target = controller.blackboard[target_key] + return isitem(target) && isturf(target.loc) && !target.anchored + +/datum/bt_node/ai_behavior/pick_up_item_virtual/perform(seconds_per_tick, datum/ai_controller/controller) + var/obj/item/target = controller.blackboard[target_key] + if(QDELETED(target) || !isturf(target.loc)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + if(!controller.pawn.Adjacent(target)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + _pickup(controller, target, storage_key) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + +/datum/bt_node/ai_behavior/pick_up_item_virtual/finish_action(datum/ai_controller/controller, succeeded) + . = ..() + controller.clear_blackboard_key(target_key) + +/datum/bt_node/ai_behavior/pick_up_item_virtual/proc/_pickup(datum/ai_controller/controller, obj/item/target, storage_key) + var/atom/pawn = controller.pawn + var/obj/item/held = controller.blackboard[storage_key] + if(held?.loc == pawn) + pawn.visible_message(span_notice("[pawn] drops [held].")) + held.forceMove(get_turf(pawn)) + controller.clear_blackboard_key(storage_key) + pawn.visible_message(span_notice("[pawn] picks up [target].")) + target.forceMove(pawn) + controller.set_blackboard_key(storage_key, target) + +/// Passes the item at storage_key to whoever is at delivery_key (must already be adjacent). +/datum/bt_node/ai_behavior/pass_item_virtual + /// Blackboard key holding the recipient to deliver to. + var/delivery_key + /// Blackboard key holding the virtually-carried item. + var/storage_key + +/datum/bt_node/ai_behavior/pass_item_virtual/setup(datum/ai_controller/controller) + var/atom/target = controller.blackboard[delivery_key] + return !QDELETED(target) + +/datum/bt_node/ai_behavior/pass_item_virtual/perform(seconds_per_tick, datum/ai_controller/controller) + var/atom/target = controller.blackboard[delivery_key] + if(QDELETED(target)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + var/mob/pawn = controller.pawn + var/obj/item/item = controller.blackboard[storage_key] + if(QDELETED(item) || item.loc != pawn) + pawn.visible_message(span_notice("[pawn] looks around as if [pawn.p_they()] [pawn.p_have()] lost something.")) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + pawn.visible_message(span_notice("[pawn] delivers [item] to [target].")) + item.forceMove(get_turf(target)) + controller.clear_blackboard_key(storage_key) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED diff --git a/code/datums/ai/generic_behaviors/wait.dm b/code/datums/ai/generic_behaviors/wait.dm new file mode 100644 index 00000000000..b609803c89f --- /dev/null +++ b/code/datums/ai/generic_behaviors/wait.dm @@ -0,0 +1,19 @@ +/// Does nothing for a given duration (deciseconds, so use the SECONDS define pls), then succeeds. duration = 0 waits forever. +/datum/bt_node/ai_behavior/wait + /// world.time when the wait ends. 0 when waiting forever + VAR_PRIVATE/end_time = 0 + /// How long to wait, in deciseconds. 0 waits forever. + var/duration = 0 + +/datum/bt_node/ai_behavior/wait/setup(datum/ai_controller/controller) + end_time = duration > 0 ? world.time + duration : 0 + return TRUE + +/datum/bt_node/ai_behavior/wait/perform(seconds_per_tick, datum/ai_controller/controller) + if(!end_time || world.time < end_time) + return AI_BEHAVIOR_INSTANT + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED + +/datum/bt_node/ai_behavior/wait/reset_tick_state() + end_time = 0 + ..() diff --git a/code/datums/ai/generic_decorators/ability_available.dm b/code/datums/ai/generic_decorators/ability_available.dm new file mode 100644 index 00000000000..99050ba4449 --- /dev/null +++ b/code/datums/ai/generic_decorators/ability_available.dm @@ -0,0 +1,8 @@ +/// Gates on whether a mob ability stored in a blackboard key is currently available. Needs to poll since we dont have nice signals to register too : ( +/datum/bt_node/decorator/ability_available + /// Blackboard key holding the ability datum + var/ability_key = BB_GENERIC_ACTION + +/datum/bt_node/decorator/ability_available/check_condition(datum/ai_controller/controller) + var/datum/action/action = controller.blackboard[ability_key] + return !QDELETED(action) && action.IsAvailable() diff --git a/code/datums/ai/generic_decorators/bb_key_at_least.dm b/code/datums/ai/generic_decorators/bb_key_at_least.dm new file mode 100644 index 00000000000..b7e9c5e3c60 --- /dev/null +++ b/code/datums/ai/generic_decorators/bb_key_at_least.dm @@ -0,0 +1,8 @@ +/// Passes if the given blackboard key holds a number >= minimum. Returns FAILURE if the key is unset or below minimum. +/datum/bt_node/decorator/bb_key_at_least + var/key + var/minimum + +/datum/bt_node/decorator/bb_key_at_least/check_condition(datum/ai_controller/controller) + var/value = controller.blackboard[key] + return !isnull(value) && value >= minimum diff --git a/code/datums/ai/generic_decorators/bb_key_equals.dm b/code/datums/ai/generic_decorators/bb_key_equals.dm new file mode 100644 index 00000000000..dfabc05ffa9 --- /dev/null +++ b/code/datums/ai/generic_decorators/bb_key_equals.dm @@ -0,0 +1,7 @@ +/// Passes if the given blackboard key equals the expected value. +/datum/bt_node/decorator/bb_key_equals + var/key + var/value + +/datum/bt_node/decorator/bb_key_equals/check_condition(datum/ai_controller/controller) + return bb_key_equals(controller, key, value) diff --git a/code/datums/ai/generic_decorators/bb_key_list_min_count.dm b/code/datums/ai/generic_decorators/bb_key_list_min_count.dm new file mode 100644 index 00000000000..27792bc4af6 --- /dev/null +++ b/code/datums/ai/generic_decorators/bb_key_list_min_count.dm @@ -0,0 +1,14 @@ +/// Passes if the blackboard key holds a list with at least min_count entries. +/datum/bt_node/decorator/bb_key_list_min_count + var/key + var/min_count = 1 + +/datum/bt_node/decorator/bb_key_list_min_count/register_observe_signals(atom/pawn) + RegisterSignals(pawn, list(COMSIG_AI_BLACKBOARD_KEY_SET(key), COMSIG_AI_BLACKBOARD_KEY_CLEARED(key)), PROC_REF(on_signal_changed)) + return TRUE + +/datum/bt_node/decorator/bb_key_list_min_count/unregister_observe_signals(atom/pawn) + UnregisterSignal(pawn, list(COMSIG_AI_BLACKBOARD_KEY_SET(key), COMSIG_AI_BLACKBOARD_KEY_CLEARED(key))) + +/datum/bt_node/decorator/bb_key_list_min_count/check_condition(datum/ai_controller/controller) + return LAZYLEN(controller.blackboard[key]) >= min_count diff --git a/code/datums/ai/generic_decorators/bb_key_set.dm b/code/datums/ai/generic_decorators/bb_key_set.dm new file mode 100644 index 00000000000..68edc597137 --- /dev/null +++ b/code/datums/ai/generic_decorators/bb_key_set.dm @@ -0,0 +1,13 @@ +/// Is the key set to a non-null value +/datum/bt_node/decorator/bb_key_set + var/key = null + +/datum/bt_node/decorator/bb_key_set/register_observe_signals(atom/pawn) + RegisterSignals(pawn, list(COMSIG_AI_BLACKBOARD_KEY_SET(key), COMSIG_AI_BLACKBOARD_KEY_CLEARED(key)), PROC_REF(on_signal_changed)) + return TRUE + +/datum/bt_node/decorator/bb_key_set/unregister_observe_signals(atom/pawn) + UnregisterSignal(pawn, list(COMSIG_AI_BLACKBOARD_KEY_SET(key), COMSIG_AI_BLACKBOARD_KEY_CLEARED(key))) + +/datum/bt_node/decorator/bb_key_set/check_condition(datum/ai_controller/controller) + return controller.blackboard_key_exists(key) diff --git a/code/datums/ai/generic_decorators/bb_key_true.dm b/code/datums/ai/generic_decorators/bb_key_true.dm new file mode 100644 index 00000000000..680ac0afb00 --- /dev/null +++ b/code/datums/ai/generic_decorators/bb_key_true.dm @@ -0,0 +1,7 @@ +/// Passes if the given blackboard key equals a true-ey value +/datum/bt_node/decorator/bb_key_true + var/key + +/datum/bt_node/decorator/bb_key_true/check_condition(datum/ai_controller/controller) + var/result = controller.blackboard[key] + return !!result diff --git a/code/datums/ai/generic_decorators/buckle_target_dangerous.dm b/code/datums/ai/generic_decorators/buckle_target_dangerous.dm new file mode 100644 index 00000000000..115f415236a --- /dev/null +++ b/code/datums/ai/generic_decorators/buckle_target_dangerous.dm @@ -0,0 +1,10 @@ +/// Gates on the buckle target having TRAIT_DANGEROUS_BUCKLE; fails if pacifist = TRUE. +/datum/bt_node/decorator/buckle_target_dangerous + var/pacifist = FALSE + var/target_key = BB_BASIC_MOB_ESCAPE_TARGET + +/datum/bt_node/decorator/buckle_target_dangerous/check_condition(datum/ai_controller/controller) + if(pacifist) + return FALSE + var/atom/target = controller.blackboard[target_key] + return !isnull(target) && HAS_TRAIT(target, TRAIT_DANGEROUS_BUCKLE) diff --git a/code/datums/ai/generic_decorators/can_see_target.dm b/code/datums/ai/generic_decorators/can_see_target.dm new file mode 100644 index 00000000000..9a2ea5b7fe2 --- /dev/null +++ b/code/datums/ai/generic_decorators/can_see_target.dm @@ -0,0 +1,17 @@ +/** + * Gates child on a blackboard key holding a visible, non-deleted atom. + * If the target is null, deleted, or not visible to the pawn, clears the key and returns BT_FAILURE. + */ +/datum/bt_node/decorator/can_see_target + /// The blackboard key whose value is the atom to validate. + var/key + /// Visibility range passed to can_see(). Default matches typical bot search radius. + var/range = 7 + +/datum/bt_node/decorator/can_see_target/check_condition(datum/ai_controller/controller) + var/atom/target = controller.blackboard[key] + if(QDELETED(target) || !can_see(controller.pawn, target, range)) + EVLOG_TEXT(controller, EVLOG_CATEGORY_AI_DECISIONMAKING, "[controller.pawn] can_see_target([key]): target lost") + controller.clear_blackboard_key(key) + return FALSE + return TRUE diff --git a/code/datums/ai/generic_decorators/check_cooldown.dm b/code/datums/ai/generic_decorators/check_cooldown.dm new file mode 100644 index 00000000000..721179dd9e1 --- /dev/null +++ b/code/datums/ai/generic_decorators/check_cooldown.dm @@ -0,0 +1,28 @@ +/// Check if the specified blackboard key is off cooldown. +/datum/bt_node/decorator/key_off_cooldown + var/cooldown_key + +/datum/bt_node/decorator/key_off_cooldown/check_condition(datum/ai_controller/controller) + var/cooldown_time = controller.blackboard[cooldown_key] + return isnull(cooldown_time) || cooldown_time <= world.time + +/** + * Checks if a blackboard cooldown key is off cooldown before ticking the child, + * then sets it when the child finishes replacing the key_off_cooldown + * + * cooldown_duration is in deciseconds (e.g. 30 SECONDS). + * lock_on_succeed = TRUE (default): lock after child SUCCESS only. + * lock_on_succeed = FALSE: lock after any completion (SUCCESS or FAILURE). + */ +/datum/bt_node/decorator/cooldown + var/cooldown_key + var/cooldown_duration + var/lock_on_succeed = TRUE + +/datum/bt_node/decorator/cooldown/check_condition(datum/ai_controller/controller) + var/cooldown_time = controller.blackboard[cooldown_key] + return isnull(cooldown_time) || cooldown_time <= world.time + +/datum/bt_node/decorator/cooldown/on_child_complete(datum/ai_controller/controller, result) + if(!lock_on_succeed || result == BT_SUCCESS) + controller.set_blackboard_key(cooldown_key, world.time + cooldown_duration) diff --git a/code/datums/ai/generic_decorators/check_rider_stat.dm b/code/datums/ai/generic_decorators/check_rider_stat.dm new file mode 100644 index 00000000000..7f082d11ed4 --- /dev/null +++ b/code/datums/ai/generic_decorators/check_rider_stat.dm @@ -0,0 +1,11 @@ +///Checks the health status of our rider, if any. returns false if we dont have a rider to begin with +/datum/bt_node/decorator/check_rider_stat + ///stat we're interested in + var/target_stat = UNCONSCIOUS + +/datum/bt_node/decorator/check_rider_stat/check_condition(datum/ai_controller/controller) + var/mob/living/living_pawn = controller.pawn + if(!length(living_pawn.buckled_mobs)) + return FALSE + var/mob/living/buckled_to = living_pawn.buckled_mobs[1] + return buckled_to.stat == target_stat diff --git a/code/datums/ai/generic_decorators/container_attackable.dm b/code/datums/ai/generic_decorators/container_attackable.dm new file mode 100644 index 00000000000..8e4303e74e4 --- /dev/null +++ b/code/datums/ai/generic_decorators/container_attackable.dm @@ -0,0 +1,15 @@ +/// Gates on the container being worth attacking (pawn.obj_damage > damage_deflection); fails if pacifist = TRUE. +/datum/bt_node/decorator/container_attackable + var/pacifist = FALSE + var/target_key = BB_BASIC_MOB_ESCAPE_TARGET + +/datum/bt_node/decorator/container_attackable/check_condition(datum/ai_controller/controller) + if(pacifist) + return FALSE + if(!isbasicmob(controller.pawn)) + return FALSE + var/atom/target = controller.blackboard[target_key] + if(isnull(target)) + return FALSE + var/mob/living/basic/basic_pawn = controller.pawn + return basic_pawn.obj_damage > target.damage_deflection diff --git a/code/datums/ai/generic_decorators/is_at_distance.dm b/code/datums/ai/generic_decorators/is_at_distance.dm new file mode 100644 index 00000000000..d8852e1b895 --- /dev/null +++ b/code/datums/ai/generic_decorators/is_at_distance.dm @@ -0,0 +1,21 @@ +/// Decorator that requires the controller's pawn to be within range of a blackboard target. +/datum/bt_node/decorator/is_at_distance + /// Blackboard key holding the atom to approach. Must be set on the subtype or via configure(). + var/target_key = null + /// Minimum distance (inclusive) from target. 0 means no lower bound. + var/min_distance = -1 + /// Maximum distance (inclusive) from target + var/maximum_distance = -1 + /// If TRUE, also verifies target.IsReachableBy(pawn) before passing to child. + var/require_reach = FALSE + +/datum/bt_node/decorator/is_at_distance/check_condition(datum/ai_controller/controller) + var/atom/target = controller.blackboard[target_key] + if(QDELETED(target)) + return FALSE + + var/atom/movable/pawn = controller.pawn + var/dist = get_dist(pawn, target) + var/reachable = !require_reach || target.IsReachableBy(pawn) + + return (maximum_distance == -1 || dist <= maximum_distance) && dist >= min_distance && reachable diff --git a/code/datums/ai/generic_decorators/is_dragging.dm b/code/datums/ai/generic_decorators/is_dragging.dm new file mode 100644 index 00000000000..6abe04896af --- /dev/null +++ b/code/datums/ai/generic_decorators/is_dragging.dm @@ -0,0 +1,6 @@ +/// Gates child on pawn currently pulling something. Use invert = TRUE for the opposite. Checked each tick. +/datum/bt_node/decorator/is_dragging + +/datum/bt_node/decorator/is_dragging/check_condition(datum/ai_controller/controller) + var/mob/living/living_pawn = controller.pawn + return !!living_pawn.pulling diff --git a/code/datums/ai/generic_decorators/is_grabbing_target.dm b/code/datums/ai/generic_decorators/is_grabbing_target.dm new file mode 100644 index 00000000000..9e9dd8fae31 --- /dev/null +++ b/code/datums/ai/generic_decorators/is_grabbing_target.dm @@ -0,0 +1,20 @@ +///Are we grabbing the specified target key? +/datum/bt_node/decorator/is_grabbing_target + /// The blackboard key whose value is the grabbed atom. + var/key + +/datum/bt_node/decorator/is_grabbing_target/check_condition(datum/ai_controller/controller) + var/atom/movable/target = controller.blackboard[key] + var/mob/living/our_mob = controller.pawn + if(QDELETED(target) || our_mob.pulling != target) + controller.clear_blackboard_key(key) + return FALSE + return TRUE + +/datum/bt_node/decorator/is_grabbing_target/register_observe_signals(atom/pawn) + RegisterSignals(pawn, list(COMSIG_LIVING_START_PULL, COMSIG_ATOM_NO_LONGER_PULLING, COMSIG_AI_BLACKBOARD_KEY_SET(key), COMSIG_AI_BLACKBOARD_KEY_CLEARED(key)), PROC_REF(on_signal_changed)) + return TRUE + +/datum/bt_node/decorator/is_grabbing_target/unregister_observe_signals(atom/pawn) + UnregisterSignal(pawn, list(COMSIG_LIVING_START_PULL, COMSIG_ATOM_NO_LONGER_PULLING, COMSIG_AI_BLACKBOARD_KEY_SET(key), COMSIG_AI_BLACKBOARD_KEY_CLEARED(key))) + diff --git a/code/datums/ai/generic_decorators/is_holding_target.dm b/code/datums/ai/generic_decorators/is_holding_target.dm new file mode 100644 index 00000000000..dd433a7a085 --- /dev/null +++ b/code/datums/ai/generic_decorators/is_holding_target.dm @@ -0,0 +1,24 @@ +/** + * Gates child on the pawn currently holding the item at the given blackboard key in its hands. + * Clears the key and returns BT_FAILURE if the item has been deleted. + * Returns BT_FAILURE without clearing the key if the item exists but is not held the item + * may be in a bag or on the floor and other branches may still need to locate it. + */ +/datum/bt_node/decorator/is_holding_target + /// The blackboard key whose value is the item to check. + var/key + +/datum/bt_node/decorator/is_holding_target/register_observe_signals(atom/pawn) + RegisterSignals(pawn, list(COMSIG_MOB_EQUIPPED_ITEM, COMSIG_MOB_UNEQUIPPED_ITEM, COMSIG_AI_BLACKBOARD_KEY_SET(key), COMSIG_AI_BLACKBOARD_KEY_CLEARED(key)), PROC_REF(on_signal_changed)) + return TRUE + +/datum/bt_node/decorator/is_holding_target/unregister_observe_signals(atom/pawn) + UnregisterSignal(pawn, list(COMSIG_MOB_EQUIPPED_ITEM, COMSIG_MOB_UNEQUIPPED_ITEM, COMSIG_AI_BLACKBOARD_KEY_SET(key), COMSIG_AI_BLACKBOARD_KEY_CLEARED(key))) + +/datum/bt_node/decorator/is_holding_target/check_condition(datum/ai_controller/controller) + var/obj/item/target = controller.blackboard[key] + var/mob/mob_pawn = controller.pawn + if(QDELETED(target)) + controller.clear_blackboard_key(key) + return FALSE + return mob_pawn.is_holding(target) diff --git a/code/datums/ai/generic_decorators/is_in_vent.dm b/code/datums/ai/generic_decorators/is_in_vent.dm new file mode 100644 index 00000000000..de4e08f500c --- /dev/null +++ b/code/datums/ai/generic_decorators/is_in_vent.dm @@ -0,0 +1,13 @@ +/// Gates on the pawn currently being inside a vent (has TRAIT_MOVE_VENTCRAWLING). +/datum/bt_node/decorator/is_in_vent + observer_abort = BT_ABORT_LOWER_PRIORITY + +/datum/bt_node/decorator/is_in_vent/register_observe_signals(atom/pawn) + RegisterSignals(pawn, list(SIGNAL_ADDTRAIT(TRAIT_MOVE_VENTCRAWLING), SIGNAL_REMOVETRAIT(TRAIT_MOVE_VENTCRAWLING)), PROC_REF(on_signal_changed)) + return TRUE + +/datum/bt_node/decorator/is_in_vent/unregister_observe_signals(atom/pawn) + UnregisterSignal(pawn, list(SIGNAL_ADDTRAIT(TRAIT_MOVE_VENTCRAWLING), SIGNAL_REMOVETRAIT(TRAIT_MOVE_VENTCRAWLING))) + +/datum/bt_node/decorator/is_in_vent/check_condition(datum/ai_controller/controller) + return HAS_TRAIT(controller.pawn, TRAIT_MOVE_VENTCRAWLING) diff --git a/code/datums/ai/generic_decorators/is_target_stunned.dm b/code/datums/ai/generic_decorators/is_target_stunned.dm new file mode 100644 index 00000000000..13f11c799d8 --- /dev/null +++ b/code/datums/ai/generic_decorators/is_target_stunned.dm @@ -0,0 +1,11 @@ +/// Gates child on the mob held in a blackboard key being paralyzed (stunned and on the ground). +/// Use "invert": true to gate on the target NOT being stunned. Used by arrest bots like ED209 to close in once the target is incapacitated. +/datum/bt_node/decorator/is_target_stunned + /// Blackboard key holding the mob to check. + var/key = BB_CURRENT_TARGET + +/datum/bt_node/decorator/is_target_stunned/check_condition(datum/ai_controller/controller) + var/mob/living/target = controller.blackboard[key] + if(QDELETED(target) || !isliving(target)) + return FALSE + return !!target.IsParalyzed() diff --git a/code/datums/ai/generic_decorators/item_inside_pawn.dm b/code/datums/ai/generic_decorators/item_inside_pawn.dm new file mode 100644 index 00000000000..d4c3d31013d --- /dev/null +++ b/code/datums/ai/generic_decorators/item_inside_pawn.dm @@ -0,0 +1,46 @@ +/** + * Gates child on the item at the given blackboard key being located anywhere inside the pawn + * (recursive contents check via locate()). Clears the key and returns FALSE if the item is deleted. + * Does NOT clear the key if the item simply isn't inside the pawn other branches may still use it. + */ +/datum/bt_node/decorator/item_inside_pawn + /// Blackboard key holding the item to check. + var/key = null + /// The item currently being observed for location changes. + var/obj/item/observed_item = null + +/datum/bt_node/decorator/item_inside_pawn/register_observe_signals(atom/pawn) + var/obj/item/target = owning_controller?.blackboard[key] + if(target) + observed_item = target + RegisterSignal(target, COMSIG_MOVABLE_MOVED, PROC_REF(on_signal_changed), override = TRUE) + RegisterSignals(pawn, list(COMSIG_AI_BLACKBOARD_KEY_SET(key), COMSIG_AI_BLACKBOARD_KEY_CLEARED(key)), PROC_REF(on_item_key_changed)) + return TRUE + +/datum/bt_node/decorator/item_inside_pawn/unregister_observe_signals(atom/pawn) + if(observed_item) + UnregisterSignal(observed_item, COMSIG_MOVABLE_MOVED) + observed_item = null + UnregisterSignal(pawn, list(COMSIG_AI_BLACKBOARD_KEY_SET(key), COMSIG_AI_BLACKBOARD_KEY_CLEARED(key))) + +/// Fires when the blackboard key changes. Rebinds the move observer to the new item and re-evaluates. +/datum/bt_node/decorator/item_inside_pawn/proc/on_item_key_changed(atom/source, ...) + SIGNAL_HANDLER + var/obj/item/target = owning_controller?.blackboard[key] + if(target == observed_item) + return + if(observed_item) + UnregisterSignal(observed_item, COMSIG_MOVABLE_MOVED) + observed_item = null + if(target) + observed_item = target + RegisterSignal(target, COMSIG_MOVABLE_MOVED, PROC_REF(on_signal_changed), override = TRUE) + if(owning_controller) + on_observed_change(owning_controller, null) + +/datum/bt_node/decorator/item_inside_pawn/check_condition(datum/ai_controller/controller) + var/obj/item/target = controller.blackboard[key] + if(QDELETED(target)) + controller.clear_blackboard_key(key) + return FALSE + return !isnull(locate(target) in controller.pawn) diff --git a/code/datums/ai/generic_decorators/key_in_typelist.dm b/code/datums/ai/generic_decorators/key_in_typelist.dm new file mode 100644 index 00000000000..0bb11ecc9d2 --- /dev/null +++ b/code/datums/ai/generic_decorators/key_in_typelist.dm @@ -0,0 +1,30 @@ +/// Gates child on the atom in `key` being an instance of a type in the typelist held at `typelist_key`. Use "invert": true for the opposite. +/datum/bt_node/decorator/key_in_typelist + /// Blackboard key holding the atom to type-check. + var/key = BB_CURRENT_TARGET + /// Blackboard key holding the list of typepaths to check against. + var/typelist_key + +/datum/bt_node/decorator/key_in_typelist/register_observe_signals(atom/pawn) + RegisterSignals(pawn, list( + COMSIG_AI_BLACKBOARD_KEY_SET(key), + COMSIG_AI_BLACKBOARD_KEY_CLEARED(key), + COMSIG_AI_BLACKBOARD_KEY_SET(typelist_key), + COMSIG_AI_BLACKBOARD_KEY_CLEARED(typelist_key), + ), PROC_REF(on_signal_changed)) + return TRUE + +/datum/bt_node/decorator/key_in_typelist/unregister_observe_signals(atom/pawn) + UnregisterSignal(pawn, list( + COMSIG_AI_BLACKBOARD_KEY_SET(key), + COMSIG_AI_BLACKBOARD_KEY_CLEARED(key), + COMSIG_AI_BLACKBOARD_KEY_SET(typelist_key), + COMSIG_AI_BLACKBOARD_KEY_CLEARED(typelist_key), + )) + +/datum/bt_node/decorator/key_in_typelist/check_condition(datum/ai_controller/controller) + var/atom/target = controller.blackboard[key] + var/list/types = controller.blackboard[typelist_key] + if(QDELETED(target) || isnull(types)) + return FALSE + return is_type_in_list(target, types) diff --git a/code/datums/ai/generic_decorators/keys_different_gender.dm b/code/datums/ai/generic_decorators/keys_different_gender.dm new file mode 100644 index 00000000000..fa5335cb205 --- /dev/null +++ b/code/datums/ai/generic_decorators/keys_different_gender.dm @@ -0,0 +1,30 @@ +///Checks for gender difference, I know, not very 2026 of me but until we add m-preg this'll have to do. +/datum/bt_node/decorator/keys_different_gender + /// Blackboard key holding the first mob. + var/key_a = BB_CURRENT_TARGET + /// Blackboard key holding the second mob. + var/key_b + +/datum/bt_node/decorator/keys_different_gender/register_observe_signals(atom/pawn) + RegisterSignals(pawn, list( + COMSIG_AI_BLACKBOARD_KEY_SET(key_a), + COMSIG_AI_BLACKBOARD_KEY_CLEARED(key_a), + COMSIG_AI_BLACKBOARD_KEY_SET(key_b), + COMSIG_AI_BLACKBOARD_KEY_CLEARED(key_b), + ), PROC_REF(on_signal_changed)) + return TRUE + +/datum/bt_node/decorator/keys_different_gender/unregister_observe_signals(atom/pawn) + UnregisterSignal(pawn, list( + COMSIG_AI_BLACKBOARD_KEY_SET(key_a), + COMSIG_AI_BLACKBOARD_KEY_CLEARED(key_a), + COMSIG_AI_BLACKBOARD_KEY_SET(key_b), + COMSIG_AI_BLACKBOARD_KEY_CLEARED(key_b), + )) + +/datum/bt_node/decorator/keys_different_gender/check_condition(datum/ai_controller/controller) + var/mob/mob_a = controller.blackboard[key_a] + var/mob/mob_b = controller.blackboard[key_b] + if(isnull(mob_a) || isnull(mob_b)) + return FALSE + return mob_a.gender != mob_b.gender diff --git a/code/datums/ai/generic_decorators/mob_stat_at_least.dm b/code/datums/ai/generic_decorators/mob_stat_at_least.dm new file mode 100644 index 00000000000..ae8f8d8e413 --- /dev/null +++ b/code/datums/ai/generic_decorators/mob_stat_at_least.dm @@ -0,0 +1,45 @@ +/// Passes when the mob held in a blackboard key has a stat value at that is at least X. Higher is more dead. +/datum/bt_node/decorator/mob_stat_at_least + /// Blackboard key holding the mob to check. + var/key = null + /// Minimum stat value (inclusive) for the condition to pass. Default: CONSCIOUS. + var/min_stat = CONSCIOUS + /// The mob currently being observed. Tracked so we can unregister when the key changes or teardown runs. + var/mob/observed_mob = null + +/datum/bt_node/decorator/mob_stat_at_least/register_observe_signals(atom/pawn) + var/mob/target = owning_controller?.blackboard[key] + if(target) + observed_mob = target + RegisterSignal(target, COMSIG_MOB_STATCHANGE, PROC_REF(on_signal_changed), override = TRUE) + RegisterSignals(pawn, list(COMSIG_AI_BLACKBOARD_KEY_SET(key), COMSIG_AI_BLACKBOARD_KEY_CLEARED(key)), PROC_REF(on_mob_key_changed)) + return TRUE + +/datum/bt_node/decorator/mob_stat_at_least/unregister_observe_signals(atom/pawn) + if(observed_mob) + UnregisterSignal(observed_mob, COMSIG_MOB_STATCHANGE) + observed_mob = null + UnregisterSignal(pawn, list(COMSIG_AI_BLACKBOARD_KEY_SET(key), COMSIG_AI_BLACKBOARD_KEY_CLEARED(key))) + + +/// Fires when the blackboard key changes. Rebinds the stat observer to the new mob and re-evaluates. +/datum/bt_node/decorator/mob_stat_at_least/proc/on_mob_key_changed(atom/source, ...) + SIGNAL_HANDLER + var/mob/target = owning_controller?.blackboard[key] + if(target == observed_mob) + return + if(observed_mob) + UnregisterSignal(observed_mob, COMSIG_MOB_STATCHANGE) + observed_mob = null + if(target) + observed_mob = target + RegisterSignal(target, COMSIG_MOB_STATCHANGE, PROC_REF(on_signal_changed), override = TRUE) + if(owning_controller) + on_observed_change(owning_controller, null) + + +/datum/bt_node/decorator/mob_stat_at_least/check_condition(datum/ai_controller/controller) + var/mob/target = controller.blackboard[key] + if(!ismob(target)) + return FALSE + return target.stat >= min_stat diff --git a/code/datums/ai/generic_decorators/no_humans_watching.dm b/code/datums/ai/generic_decorators/no_humans_watching.dm new file mode 100644 index 00000000000..6ffadadbc66 --- /dev/null +++ b/code/datums/ai/generic_decorators/no_humans_watching.dm @@ -0,0 +1,11 @@ +/// Passes only when no living, conscious human can see or hear the pawn within range. +/// Used by cowardly mobs that only act when nobody is looking. +/datum/bt_node/decorator/no_humans_watching + /// How far to look for witnesses. + var/range = 7 + +/datum/bt_node/decorator/no_humans_watching/check_condition(datum/ai_controller/controller) + for(var/mob/living/carbon/human/watcher in hearers(range, controller.pawn)) + if(watcher.stat != DEAD) + return FALSE + return TRUE diff --git a/code/datums/ai/generic_decorators/pawn_buckled_to_obj.dm b/code/datums/ai/generic_decorators/pawn_buckled_to_obj.dm new file mode 100644 index 00000000000..04514588249 --- /dev/null +++ b/code/datums/ai/generic_decorators/pawn_buckled_to_obj.dm @@ -0,0 +1,18 @@ +/// Gates on the pawn being buckled to an obj; writes pawn.buckled to the escape target key as a side effect. +/datum/bt_node/decorator/pawn_buckled_to_obj + var/target_key = BB_BASIC_MOB_ESCAPE_TARGET + observer_abort = BT_ABORT_LOWER_PRIORITY + +/datum/bt_node/decorator/pawn_buckled_to_obj/register_observe_signals(atom/pawn) + RegisterSignals(pawn, list(COMSIG_MOB_BUCKLED, COMSIG_MOB_UNBUCKLED), PROC_REF(on_signal_changed)) + return TRUE + +/datum/bt_node/decorator/pawn_buckled_to_obj/unregister_observe_signals(atom/pawn) + UnregisterSignal(pawn, list(COMSIG_MOB_BUCKLED, COMSIG_MOB_UNBUCKLED)) + +/datum/bt_node/decorator/pawn_buckled_to_obj/check_condition(datum/ai_controller/controller) + var/mob/living/pawn = controller.pawn + if(!isobj(pawn.buckled)) + return FALSE + controller.blackboard[target_key] = pawn.buckled + return TRUE diff --git a/code/datums/ai/generic_decorators/pawn_contained_in_obj.dm b/code/datums/ai/generic_decorators/pawn_contained_in_obj.dm new file mode 100644 index 00000000000..a49659df49d --- /dev/null +++ b/code/datums/ai/generic_decorators/pawn_contained_in_obj.dm @@ -0,0 +1,18 @@ +/// Gates on the pawn being inside an obj (not a turf, mob, or mob_holder); writes pawn.loc to the escape target key. +/datum/bt_node/decorator/pawn_contained_in_obj + var/target_key = BB_BASIC_MOB_ESCAPE_TARGET + observer_abort = BT_ABORT_LOWER_PRIORITY + +/datum/bt_node/decorator/pawn_contained_in_obj/register_observe_signals(atom/pawn) + RegisterSignals(pawn, list(COMSIG_MOVABLE_MOVED), PROC_REF(on_signal_changed)) + return TRUE + +/datum/bt_node/decorator/pawn_contained_in_obj/unregister_observe_signals(atom/pawn) + UnregisterSignal(pawn, list(COMSIG_MOVABLE_MOVED)) + +/datum/bt_node/decorator/pawn_contained_in_obj/check_condition(datum/ai_controller/controller) + var/mob/living/pawn = controller.pawn + if(isturf(pawn.loc) || ismob(pawn.loc) || istype(pawn.loc, /obj/item/mob_holder) || HAS_TRAIT(controller.pawn, TRAIT_MOVE_VENTCRAWLING)) + return FALSE + controller.blackboard[target_key] = pawn.loc + return TRUE diff --git a/code/datums/ai/generic_decorators/pawn_farther_than_from_key.dm b/code/datums/ai/generic_decorators/pawn_farther_than_from_key.dm new file mode 100644 index 00000000000..997f6f0abcd --- /dev/null +++ b/code/datums/ai/generic_decorators/pawn_farther_than_from_key.dm @@ -0,0 +1,13 @@ +/// Passes if the pawn is farther than the distance stored in distance_key from the atom stored in anchor_key. +/datum/bt_node/decorator/pawn_farther_than_from_key + /// Blackboard key holding the anchor atom. + var/anchor_key + /// Blackboard key whose integer value is the minimum distance threshold (exclusive). + var/distance_key + +/datum/bt_node/decorator/pawn_farther_than_from_key/check_condition(datum/ai_controller/controller) + var/atom/anchor = controller.blackboard[anchor_key] + if(QDELETED(anchor)) + return FALSE + var/min_dist = controller.blackboard[distance_key] + return get_dist(controller.pawn, anchor) > min_dist diff --git a/code/datums/ai/generic_decorators/pawn_grabbed_by_enemy.dm b/code/datums/ai/generic_decorators/pawn_grabbed_by_enemy.dm new file mode 100644 index 00000000000..64e7a0df473 --- /dev/null +++ b/code/datums/ai/generic_decorators/pawn_grabbed_by_enemy.dm @@ -0,0 +1,22 @@ +/// Gates on the pawn being grabbed above GRAB_PASSIVE by a mob the targeting strategy considers an enemy. +/datum/bt_node/decorator/pawn_grabbed_by_enemy + var/targeting_strategy_key = BB_TARGETING_STRATEGY + child_typepath = /datum/bt_node/ai_behavior/resist + +/datum/bt_node/decorator/pawn_grabbed_by_enemy/register_observe_signals(atom/pawn) + RegisterSignals(pawn, list(COMSIG_LIVING_GET_PULLED, COMSIG_ATOM_NO_LONGER_PULLED, COMSIG_MOVABLE_SET_GRAB_STATE), PROC_REF(on_signal_changed)) + return TRUE + +/datum/bt_node/decorator/pawn_grabbed_by_enemy/unregister_observe_signals(atom/pawn) + UnregisterSignal(pawn, list(COMSIG_LIVING_GET_PULLED, COMSIG_ATOM_NO_LONGER_PULLED, COMSIG_MOVABLE_SET_GRAB_STATE)) + +/datum/bt_node/decorator/pawn_grabbed_by_enemy/check_condition(datum/ai_controller/controller) + var/mob/living/pawn = controller.pawn + var/mob/puller = pawn.pulledby + if(isnull(puller) || puller.grab_state <= GRAB_PASSIVE) + return FALSE + var/datum/targeting_strategy/strategy = GET_TARGETING_STRATEGY(controller.blackboard[targeting_strategy_key]) + if(!strategy?.is_valid_target(pawn, puller)) + return FALSE + var/list/friends = controller.blackboard[BB_FRIENDS_LIST] || list() + return !(puller in friends) diff --git a/code/datums/ai/generic_decorators/pawn_has_gravity.dm b/code/datums/ai/generic_decorators/pawn_has_gravity.dm new file mode 100644 index 00000000000..78de74825b4 --- /dev/null +++ b/code/datums/ai/generic_decorators/pawn_has_gravity.dm @@ -0,0 +1,13 @@ +/// Gates child on the pawn having gravity. Use invert = TRUE to gate on weightlessness instead. +/datum/bt_node/decorator/pawn_has_gravity + +/datum/bt_node/decorator/pawn_has_gravity/register_observe_signals(atom/pawn) + RegisterSignal(pawn, COMSIG_LIVING_GRAVITY_CHANGED, PROC_REF(on_signal_changed)) + return TRUE + +/datum/bt_node/decorator/pawn_has_gravity/unregister_observe_signals(atom/pawn) + UnregisterSignal(pawn, COMSIG_LIVING_GRAVITY_CHANGED) + +/datum/bt_node/decorator/pawn_has_gravity/check_condition(datum/ai_controller/controller) + var/mob/living/living_pawn = controller.pawn + return istype(living_pawn) && living_pawn.has_gravity() diff --git a/code/datums/ai/generic_decorators/pawn_has_trait_from.dm b/code/datums/ai/generic_decorators/pawn_has_trait_from.dm new file mode 100644 index 00000000000..b422f828717 --- /dev/null +++ b/code/datums/ai/generic_decorators/pawn_has_trait_from.dm @@ -0,0 +1,16 @@ +/// Gates child on the pawn having a given trait from a specific source. Use "invert": true to gate on the trait being absent. +/datum/bt_node/decorator/pawn_has_trait_from + /// The trait to check for. + var/trait = null + /// The source the trait must originate from. + var/source = null + +/datum/bt_node/decorator/pawn_has_trait_from/register_observe_signals(atom/pawn) + RegisterSignals(pawn, list(SIGNAL_ADDTRAIT(trait), SIGNAL_REMOVETRAIT(trait)), PROC_REF(on_signal_changed)) + return TRUE + +/datum/bt_node/decorator/pawn_has_trait_from/unregister_observe_signals(atom/pawn) + UnregisterSignal(pawn, list(SIGNAL_ADDTRAIT(trait), SIGNAL_REMOVETRAIT(trait))) + +/datum/bt_node/decorator/pawn_has_trait_from/check_condition(datum/ai_controller/controller) + return HAS_TRAIT_FROM(controller.pawn, trait, source) diff --git a/code/datums/ai/generic_decorators/pawn_health_below.dm b/code/datums/ai/generic_decorators/pawn_health_below.dm new file mode 100644 index 00000000000..9a7390251bc --- /dev/null +++ b/code/datums/ai/generic_decorators/pawn_health_below.dm @@ -0,0 +1,20 @@ +/// Passes when the pawn's current health is below the configured threshold. +/datum/bt_node/decorator/pawn_health_below + /// Health value that the pawn must be below for this decorator to pass + var/health_threshold = 0 + /// blackboard value holding our threshold, if this is null, health_threshold will be used instead + var/health_blackboard_key + +/datum/bt_node/decorator/pawn_health_below/register_observe_signals(atom/pawn) + RegisterSignal(pawn, COMSIG_LIVING_HEALTH_UPDATE, PROC_REF(on_signal_changed)) + return TRUE + +/datum/bt_node/decorator/pawn_health_below/unregister_observe_signals(atom/pawn) + UnregisterSignal(pawn, COMSIG_LIVING_HEALTH_UPDATE) + +/datum/bt_node/decorator/pawn_health_below/check_condition(datum/ai_controller/controller) + var/mob/living/living_pawn = controller.pawn + if(!isliving(living_pawn)) + return FALSE + var/final_threshold = controller.blackboard[health_blackboard_key] || health_threshold + return living_pawn.health < final_threshold diff --git a/code/datums/ai/generic_decorators/pawn_inside_mob.dm b/code/datums/ai/generic_decorators/pawn_inside_mob.dm new file mode 100644 index 00000000000..cef895c380a --- /dev/null +++ b/code/datums/ai/generic_decorators/pawn_inside_mob.dm @@ -0,0 +1,13 @@ +/// Gates on the pawn being located inside another mob (e.g. absorbed or shapeshifted). +/datum/bt_node/decorator/pawn_inside_mob + observer_abort = BT_ABORT_LOWER_PRIORITY + +/datum/bt_node/decorator/pawn_inside_mob/register_observe_signals(atom/pawn) + RegisterSignal(pawn, COMSIG_MOVABLE_MOVED, PROC_REF(on_signal_changed)) + return TRUE + +/datum/bt_node/decorator/pawn_inside_mob/unregister_observe_signals(atom/pawn) + UnregisterSignal(pawn, COMSIG_MOVABLE_MOVED) + +/datum/bt_node/decorator/pawn_inside_mob/check_condition(datum/ai_controller/controller) + return ismob(controller.pawn.loc) diff --git a/code/datums/ai/generic_decorators/pawn_is_restrained.dm b/code/datums/ai/generic_decorators/pawn_is_restrained.dm new file mode 100644 index 00000000000..2a9b7c96f76 --- /dev/null +++ b/code/datums/ai/generic_decorators/pawn_is_restrained.dm @@ -0,0 +1,14 @@ +/// Gates on the pawn having TRAIT_RESTRAINED; reacts to trait add/remove signals. +/datum/bt_node/decorator/pawn_is_restrained + observer_abort = BT_ABORT_LOWER_PRIORITY + child_typepath = /datum/bt_node/ai_behavior/resist + +/datum/bt_node/decorator/pawn_is_restrained/register_observe_signals(atom/pawn) + RegisterSignals(pawn, list(SIGNAL_ADDTRAIT(TRAIT_RESTRAINED), SIGNAL_REMOVETRAIT(TRAIT_RESTRAINED)), PROC_REF(on_signal_changed)) + return TRUE + +/datum/bt_node/decorator/pawn_is_restrained/unregister_observe_signals(atom/pawn) + UnregisterSignal(pawn, list(SIGNAL_ADDTRAIT(TRAIT_RESTRAINED), SIGNAL_REMOVETRAIT(TRAIT_RESTRAINED))) + +/datum/bt_node/decorator/pawn_is_restrained/check_condition(datum/ai_controller/controller) + return HAS_TRAIT(controller.pawn, TRAIT_RESTRAINED) diff --git a/code/datums/ai/generic_decorators/pawn_loc_is_type.dm b/code/datums/ai/generic_decorators/pawn_loc_is_type.dm new file mode 100644 index 00000000000..339078ad49c --- /dev/null +++ b/code/datums/ai/generic_decorators/pawn_loc_is_type.dm @@ -0,0 +1,15 @@ +/// Passes when the pawn's loc is of the given type (e.g. the pawn is inside a specific structure). +/datum/bt_node/decorator/pawn_loc_is_type + observer_abort = BT_ABORT_LOWER_PRIORITY + /// Typepath the pawn's loc must match. + var/loc_type + +/datum/bt_node/decorator/pawn_loc_is_type/register_observe_signals(atom/pawn) + RegisterSignal(pawn, COMSIG_MOVABLE_MOVED, PROC_REF(on_signal_changed)) + return TRUE + +/datum/bt_node/decorator/pawn_loc_is_type/unregister_observe_signals(atom/pawn) + UnregisterSignal(pawn, COMSIG_MOVABLE_MOVED) + +/datum/bt_node/decorator/pawn_loc_is_type/check_condition(datum/ai_controller/controller) + return istype(controller.pawn.loc, loc_type) diff --git a/code/datums/ai/generic_decorators/pawn_nutrition_below.dm b/code/datums/ai/generic_decorators/pawn_nutrition_below.dm new file mode 100644 index 00000000000..ea559a106a7 --- /dev/null +++ b/code/datums/ai/generic_decorators/pawn_nutrition_below.dm @@ -0,0 +1,10 @@ +/// Passes if the pawn's nutrition is below the given threshold. +/datum/bt_node/decorator/pawn_nutrition_below + /// Nutrition threshold to check against. + var/nutrition_threshold = NUTRITION_LEVEL_HUNGRY + +/datum/bt_node/decorator/pawn_nutrition_below/check_condition(datum/ai_controller/controller) + var/mob/living/pawn = controller.pawn + if(!isliving(pawn)) + return FALSE + return pawn.nutrition < nutrition_threshold diff --git a/code/datums/ai/generic_decorators/pawn_same_z_as_key.dm b/code/datums/ai/generic_decorators/pawn_same_z_as_key.dm new file mode 100644 index 00000000000..eadaef06968 --- /dev/null +++ b/code/datums/ai/generic_decorators/pawn_same_z_as_key.dm @@ -0,0 +1,9 @@ +/// Passes if the pawn is on the same z-level as the atom stored in the given blackboard key. +/datum/bt_node/decorator/pawn_same_z_as_key + var/key + +/datum/bt_node/decorator/pawn_same_z_as_key/check_condition(datum/ai_controller/controller) + var/atom/target = controller.blackboard[key] + if(QDELETED(target)) + return FALSE + return controller.pawn.z == target.z diff --git a/code/datums/ai/generic_decorators/pawn_turf_has_trait.dm b/code/datums/ai/generic_decorators/pawn_turf_has_trait.dm new file mode 100644 index 00000000000..e5febf59704 --- /dev/null +++ b/code/datums/ai/generic_decorators/pawn_turf_has_trait.dm @@ -0,0 +1,7 @@ +/// Gates child on the pawn's current turf having a given trait. Use invert = TRUE to gate on the trait being absent. +/datum/bt_node/decorator/pawn_turf_has_trait + var/trait = null + +/datum/bt_node/decorator/pawn_turf_has_trait/check_condition(datum/ai_controller/controller) + var/turf/my_turf_in_the_middle_of_my_turf = get_turf(controller.pawn) + return HAS_TRAIT(my_turf_in_the_middle_of_my_turf, trait) diff --git a/code/datums/ai/generic_decorators/random_chance.dm b/code/datums/ai/generic_decorators/random_chance.dm new file mode 100644 index 00000000000..f33392904a3 --- /dev/null +++ b/code/datums/ai/generic_decorators/random_chance.dm @@ -0,0 +1,7 @@ +///Decorator with a probability to pass, useful for things that sometimes happen. Slay queen +/datum/bt_node/decorator/random_chance + /// 0.0–1.0 float; converted to percentage for prob(). Configure via BT_DECORATOR. + var/chance = 0.5 + +/datum/bt_node/decorator/random_chance/check_condition(datum/ai_controller/controller) + return prob(chance * 100) diff --git a/code/datums/ai/generic_decorators/random_chance_from_key.dm b/code/datums/ai/generic_decorators/random_chance_from_key.dm new file mode 100644 index 00000000000..9c7d778cd60 --- /dev/null +++ b/code/datums/ai/generic_decorators/random_chance_from_key.dm @@ -0,0 +1,10 @@ +/// Passes with a probability taken from a blackboard key (0-100 integer). +/datum/bt_node/decorator/random_chance_from_key + /// Blackboard key holding the integer probability (0-100) + var/chance_key = null + +/datum/bt_node/decorator/random_chance_from_key/check_condition(datum/ai_controller/controller) + var/chance = controller.blackboard[chance_key] + if(!chance) + return FALSE + return prob(chance) diff --git a/code/datums/ai/generic_decorators/target_has_reagent.dm b/code/datums/ai/generic_decorators/target_has_reagent.dm new file mode 100644 index 00000000000..85458169bf5 --- /dev/null +++ b/code/datums/ai/generic_decorators/target_has_reagent.dm @@ -0,0 +1,44 @@ +/// Gates child on the atom held in a blackboard key having a specific reagent in its reagent list. Use "invert": true to gate on the reagent being absent. +/datum/bt_node/decorator/target_has_reagent + /// Blackboard key holding the atom to check. + var/key = null + /// Typepath of the reagent to look for. + var/reagent_type = null + /// Tracked so we can rebind signals when the key or its reagents change. + var/datum/reagents/observed_holder = null + +/datum/bt_node/decorator/target_has_reagent/register_observe_signals(atom/pawn) + var/atom/target = owning_controller?.blackboard[key] + if(target?.reagents) + observed_holder = target.reagents + RegisterSignal(observed_holder, COMSIG_REAGENTS_HOLDER_UPDATED, PROC_REF(on_signal_changed), override = TRUE) + RegisterSignals(pawn, list(COMSIG_AI_BLACKBOARD_KEY_SET(key), COMSIG_AI_BLACKBOARD_KEY_CLEARED(key)), PROC_REF(on_target_key_changed)) + return TRUE + +/datum/bt_node/decorator/target_has_reagent/unregister_observe_signals(atom/pawn) + if(observed_holder) + UnregisterSignal(observed_holder, COMSIG_REAGENTS_HOLDER_UPDATED) + observed_holder = null + UnregisterSignal(pawn, list(COMSIG_AI_BLACKBOARD_KEY_SET(key), COMSIG_AI_BLACKBOARD_KEY_CLEARED(key))) + +/// Fires when the blackboard key changes. Rebinds the reagent holder observer to the new target and re-evaluates. +/datum/bt_node/decorator/target_has_reagent/proc/on_target_key_changed(atom/source, ...) + SIGNAL_HANDLER + var/atom/target = owning_controller?.blackboard[key] + var/datum/reagents/new_holder = target?.reagents + if(new_holder == observed_holder) + return + if(observed_holder) + UnregisterSignal(observed_holder, COMSIG_REAGENTS_HOLDER_UPDATED) + observed_holder = null + if(new_holder) + observed_holder = new_holder + RegisterSignal(observed_holder, COMSIG_REAGENTS_HOLDER_UPDATED, PROC_REF(on_signal_changed), override = TRUE) + if(owning_controller) + on_observed_change(owning_controller, null) + +/datum/bt_node/decorator/target_has_reagent/check_condition(datum/ai_controller/controller) + var/atom/target = controller.blackboard[key] + if(QDELETED(target) || !target.reagents) + return FALSE + return !!target.reagents.has_reagent(reagent_type) diff --git a/code/datums/ai/generic_decorators/target_has_trait.dm b/code/datums/ai/generic_decorators/target_has_trait.dm new file mode 100644 index 00000000000..8dbd0310004 --- /dev/null +++ b/code/datums/ai/generic_decorators/target_has_trait.dm @@ -0,0 +1,43 @@ +/// Gates child on the atom held in a blackboard key having a given trait. Use "invert": true to gate on the trait being absent. +/datum/bt_node/decorator/target_has_trait + /// Blackboard key holding the atom to check. + var/key = null + /// The trait the target must have for the child to run. + var/trait = null + /// Tracked so we can rebind signals when the key changes. + var/atom/observed_target = null + +/datum/bt_node/decorator/target_has_trait/register_observe_signals(atom/pawn) + var/atom/target = owning_controller?.blackboard[key] + if(target) + observed_target = target + RegisterSignals(target, list(SIGNAL_ADDTRAIT(trait), SIGNAL_REMOVETRAIT(trait)), PROC_REF(on_signal_changed), override = TRUE) + RegisterSignals(pawn, list(COMSIG_AI_BLACKBOARD_KEY_SET(key), COMSIG_AI_BLACKBOARD_KEY_CLEARED(key)), PROC_REF(on_target_key_changed)) + return TRUE + +/datum/bt_node/decorator/target_has_trait/unregister_observe_signals(atom/pawn) + if(observed_target) + UnregisterSignal(observed_target, list(SIGNAL_ADDTRAIT(trait), SIGNAL_REMOVETRAIT(trait))) + observed_target = null + UnregisterSignal(pawn, list(COMSIG_AI_BLACKBOARD_KEY_SET(key), COMSIG_AI_BLACKBOARD_KEY_CLEARED(key))) + +/// Fires when the blackboard key changes. Rebinds trait signals to the new target and re-evaluates. +/datum/bt_node/decorator/target_has_trait/proc/on_target_key_changed(atom/source, ...) + SIGNAL_HANDLER + var/atom/target = owning_controller?.blackboard[key] + if(target == observed_target) + return + if(observed_target) + UnregisterSignal(observed_target, list(SIGNAL_ADDTRAIT(trait), SIGNAL_REMOVETRAIT(trait))) + observed_target = null + if(target) + observed_target = target + RegisterSignals(target, list(SIGNAL_ADDTRAIT(trait), SIGNAL_REMOVETRAIT(trait)), PROC_REF(on_signal_changed), override = TRUE) + if(owning_controller) + on_observed_change(owning_controller, null) + +/datum/bt_node/decorator/target_has_trait/check_condition(datum/ai_controller/controller) + var/atom/target = controller.blackboard[key] + if(QDELETED(target)) + return FALSE + return HAS_TRAIT(target, trait) diff --git a/code/datums/ai/generic_decorators/target_health_below_fraction.dm b/code/datums/ai/generic_decorators/target_health_below_fraction.dm new file mode 100644 index 00000000000..0f2ebb0d594 --- /dev/null +++ b/code/datums/ai/generic_decorators/target_health_below_fraction.dm @@ -0,0 +1,42 @@ +/// Passes when the living mob held in a blackboard key has health below a fraction of its max health. +/datum/bt_node/decorator/target_health_below_fraction + /// Blackboard key holding the mob to check. + var/key = null + /// Health fraction threshold (0.0–1.0). Passes when health < maxHealth * fraction. + var/fraction = 0.75 + /// The mob currently being observed. + VAR_FINAL/mob/living/observed_mob = null + +/datum/bt_node/decorator/target_health_below_fraction/register_observe_signals(atom/pawn) + var/mob/living/target = owning_controller?.blackboard[key] + if(isliving(target)) + observed_mob = target + RegisterSignal(target, COMSIG_LIVING_HEALTH_UPDATE, PROC_REF(on_signal_changed), override = TRUE) + RegisterSignals(pawn, list(COMSIG_AI_BLACKBOARD_KEY_SET(key), COMSIG_AI_BLACKBOARD_KEY_CLEARED(key)), PROC_REF(on_mob_key_changed)) + return TRUE + +/datum/bt_node/decorator/target_health_below_fraction/unregister_observe_signals(atom/pawn) + if(observed_mob) + UnregisterSignal(observed_mob, COMSIG_LIVING_HEALTH_UPDATE) + observed_mob = null + UnregisterSignal(pawn, list(COMSIG_AI_BLACKBOARD_KEY_SET(key), COMSIG_AI_BLACKBOARD_KEY_CLEARED(key))) + +/datum/bt_node/decorator/target_health_below_fraction/proc/on_mob_key_changed(atom/source, ...) + SIGNAL_HANDLER + var/mob/living/target = owning_controller?.blackboard[key] + if(target == observed_mob) + return + if(observed_mob) + UnregisterSignal(observed_mob, COMSIG_LIVING_HEALTH_UPDATE) + observed_mob = null + if(isliving(target)) + observed_mob = target + RegisterSignal(target, COMSIG_LIVING_HEALTH_UPDATE, PROC_REF(on_signal_changed), override = TRUE) + if(owning_controller) + on_observed_change(owning_controller, null) + +/datum/bt_node/decorator/target_health_below_fraction/check_condition(datum/ai_controller/controller) + var/mob/living/target = controller.blackboard[key] + if(!isliving(target)) + return FALSE + return target.health < target.maxHealth * fraction diff --git a/code/datums/ai/generic_decorators/target_holding_lit_item.dm b/code/datums/ai/generic_decorators/target_holding_lit_item.dm new file mode 100644 index 00000000000..1ef20f50cf5 --- /dev/null +++ b/code/datums/ai/generic_decorators/target_holding_lit_item.dm @@ -0,0 +1,21 @@ +/// Passes when the keyed target is a carbon holding a lit item whose type is in a blackboard-keyed type list. +/// Use "invert": true for the opposite. +/datum/bt_node/decorator/target_holding_lit_item + /// Blackboard key holding the atom to check. + var/key = BB_CURRENT_TARGET + /// Blackboard key holding the list of item typepaths we care about. + var/item_types_key + +/datum/bt_node/decorator/target_holding_lit_item/check_condition(datum/ai_controller/controller) + var/mob/living/carbon/target = controller.blackboard[key] + if(!iscarbon(target)) + return FALSE + var/list/item_types = controller.blackboard[item_types_key] + if(!length(item_types)) + return FALSE + for(var/obj/item/held_item in target.held_items) + if(!is_type_in_list(held_item, item_types)) + continue + if(held_item.light_on) + return TRUE + return FALSE diff --git a/code/datums/ai/generic_decorators/target_is_holding_item.dm b/code/datums/ai/generic_decorators/target_is_holding_item.dm new file mode 100644 index 00000000000..888a327ea37 --- /dev/null +++ b/code/datums/ai/generic_decorators/target_is_holding_item.dm @@ -0,0 +1,42 @@ +/// Gates child on the mob held in a blackboard key currently holding any item in their hands. +/// Use "invert": true to gate on the target holding nothing. +/datum/bt_node/decorator/target_is_holding_item + /// Blackboard key holding the mob to check. + var/key = BB_CURRENT_TARGET + /// Tracked so we can rebind signals when the key changes. + var/mob/observed_target = null + +/datum/bt_node/decorator/target_is_holding_item/register_observe_signals(atom/pawn) + var/mob/target = owning_controller?.blackboard[key] + if(ismob(target)) + observed_target = target + RegisterSignals(target, list(COMSIG_MOB_EQUIPPED_ITEM, COMSIG_MOB_UNEQUIPPED_ITEM), PROC_REF(on_signal_changed), override = TRUE) + RegisterSignals(pawn, list(COMSIG_AI_BLACKBOARD_KEY_SET(key), COMSIG_AI_BLACKBOARD_KEY_CLEARED(key)), PROC_REF(on_target_key_changed)) + return TRUE + +/datum/bt_node/decorator/target_is_holding_item/unregister_observe_signals(atom/pawn) + if(observed_target) + UnregisterSignal(observed_target, list(COMSIG_MOB_EQUIPPED_ITEM, COMSIG_MOB_UNEQUIPPED_ITEM)) + observed_target = null + UnregisterSignal(pawn, list(COMSIG_AI_BLACKBOARD_KEY_SET(key), COMSIG_AI_BLACKBOARD_KEY_CLEARED(key))) + +/// Fires when the blackboard key changes. Rebinds equip signals to the new target and re-evaluates. +/datum/bt_node/decorator/target_is_holding_item/proc/on_target_key_changed(atom/source, ...) + SIGNAL_HANDLER + var/mob/target = owning_controller?.blackboard[key] + if(target == observed_target) + return + if(observed_target) + UnregisterSignal(observed_target, list(COMSIG_MOB_EQUIPPED_ITEM, COMSIG_MOB_UNEQUIPPED_ITEM)) + observed_target = null + if(ismob(target)) + observed_target = target + RegisterSignals(target, list(COMSIG_MOB_EQUIPPED_ITEM, COMSIG_MOB_UNEQUIPPED_ITEM), PROC_REF(on_signal_changed), override = TRUE) + if(owning_controller) + on_observed_change(owning_controller, null) + +/datum/bt_node/decorator/target_is_holding_item/check_condition(datum/ai_controller/controller) + var/mob/target = controller.blackboard[key] + if(QDELETED(target)) + return FALSE + return target.get_num_held_items() > 0 diff --git a/code/datums/ai/generic_decorators/target_is_type.dm b/code/datums/ai/generic_decorators/target_is_type.dm new file mode 100644 index 00000000000..dba7d6a2618 --- /dev/null +++ b/code/datums/ai/generic_decorators/target_is_type.dm @@ -0,0 +1,12 @@ +/// Gates child on the atom held in a blackboard key being an instance of a given typepath. Use "invert": true for the opposite. +/datum/bt_node/decorator/target_is_type + /// Blackboard key holding the atom to check. + var/key = BB_CURRENT_TARGET + /// Typepath the target must be an instance of. + var/target_type + +/datum/bt_node/decorator/target_is_type/check_condition(datum/ai_controller/controller) + var/atom/target = controller.blackboard[key] + if(QDELETED(target) || isnull(target_type)) + return FALSE + return istype(target, target_type) diff --git a/code/datums/ai/generic_decorators/target_legcuffed.dm b/code/datums/ai/generic_decorators/target_legcuffed.dm new file mode 100644 index 00000000000..7eabdb699b3 --- /dev/null +++ b/code/datums/ai/generic_decorators/target_legcuffed.dm @@ -0,0 +1,10 @@ +/// Gates child on the carbon held in a blackboard key being legcuffed. Use "invert": true to gate on not being legcuffed. +/datum/bt_node/decorator/target_legcuffed + /// Blackboard key holding the mob to check. + var/key = BB_CURRENT_TARGET + +/datum/bt_node/decorator/target_legcuffed/check_condition(datum/ai_controller/controller) + var/mob/living/carbon/target = controller.blackboard[key] + if(!iscarbon(target)) + return FALSE + return !isnull(target.legcuffed) diff --git a/code/datums/ai/generic_decorators/target_on_ground.dm b/code/datums/ai/generic_decorators/target_on_ground.dm new file mode 100644 index 00000000000..bba87d5d531 --- /dev/null +++ b/code/datums/ai/generic_decorators/target_on_ground.dm @@ -0,0 +1,45 @@ +///Check fi target is still on a turf +/datum/bt_node/decorator/validate_target_on_turf + var/key + + var/atom/observed_target = null + +/datum/bt_node/decorator/validate_target_on_turf/register_observe_signals(atom/pawn) + var/atom/target = owning_controller?.blackboard[key] + if(target) + observed_target = target + RegisterSignal(target, COMSIG_MOVABLE_MOVED, PROC_REF(on_signal_changed), override = TRUE) + RegisterSignals(pawn, list(COMSIG_AI_BLACKBOARD_KEY_SET(key), COMSIG_AI_BLACKBOARD_KEY_CLEARED(key)), PROC_REF(on_key_changed)) + return TRUE + +/datum/bt_node/decorator/validate_target_on_turf/unregister_observe_signals(atom/pawn) + if(observed_target) + UnregisterSignal(observed_target, COMSIG_MOVABLE_MOVED) + observed_target = null + UnregisterSignal(pawn, list(COMSIG_AI_BLACKBOARD_KEY_SET(key), COMSIG_AI_BLACKBOARD_KEY_CLEARED(key))) + +/datum/bt_node/decorator/validate_target_on_turf/proc/on_key_changed(atom/pawn, ...) + SIGNAL_HANDLER + var/atom/target = owning_controller?.blackboard[key] + if(target == observed_target) + return + if(observed_target) + UnregisterSignal(observed_target, COMSIG_MOVABLE_MOVED) + observed_target = null + if(target) + observed_target = target + RegisterSignal(target, COMSIG_MOVABLE_MOVED, PROC_REF(on_signal_changed), override = TRUE) + if(owning_controller) + on_observed_change(owning_controller, null) + +/datum/bt_node/decorator/validate_target_on_turf/check_condition(datum/ai_controller/controller) + var/atom/target = controller.blackboard[key] + if(QDELETED(target) || !isturf(target.loc)) + controller.clear_blackboard_key(key) + return FALSE + return TRUE + +/// Check without side effects for observer path. +/datum/bt_node/decorator/validate_target_on_turf/evaluate_for_observer(datum/ai_controller/controller) + var/atom/target = controller.blackboard[key] + return !QDELETED(target) && isturf(target.loc) diff --git a/code/datums/ai/generic_decorators/true_for_time.dm b/code/datums/ai/generic_decorators/true_for_time.dm new file mode 100644 index 00000000000..c04383a1835 --- /dev/null +++ b/code/datums/ai/generic_decorators/true_for_time.dm @@ -0,0 +1,31 @@ +/** + * Passes while its duration has not elapsed, then fails reactively via observer abort. + * Starts timing on the first tick; resets when the tree resets. + * + * duration: how long to pass, in deciseconds (e.g. "10 SECONDS"). + * Set observer_abort to BT_ABORT_BOTH (or BT_ABORT_SELF) to abort the child when time expires. + */ +/datum/bt_node/decorator/true_for_time + var/duration = 0 + var/timer_id = null + var/timed_out = FALSE + +/datum/bt_node/decorator/true_for_time/register_observe_signals(atom/pawn) + timer_id = addtimer(CALLBACK(src, PROC_REF(on_timeout)), duration, TIMER_STOPPABLE|TIMER_DELETE_ME) + return TRUE + +/datum/bt_node/decorator/true_for_time/unregister_observe_signals(atom/pawn) + if(timer_id) + deltimer(timer_id) + timer_id = null + timed_out = FALSE + +/datum/bt_node/decorator/true_for_time/proc/on_timeout() + SIGNAL_HANDLER + timer_id = null + timed_out = TRUE + if(owning_controller) + on_observed_change(owning_controller, null) + +/datum/bt_node/decorator/true_for_time/check_condition(datum/ai_controller/controller) + return !timed_out diff --git a/code/datums/ai/generic_hunger.bt.json b/code/datums/ai/generic_hunger.bt.json new file mode 100644 index 00000000000..905de0d57b3 --- /dev/null +++ b/code/datums/ai/generic_hunger.bt.json @@ -0,0 +1,43 @@ +{ + "dm_type": "/datum/bt_node/subtree/generic_hunger", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "bb_food_target" + }, + "child": { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/consume", + "vars": { + "target_key": "bb_food_target", + "hunger_timer_key": "BB_NEXT_HUNGRY" + } + }, + { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "bb_food_target", + "required_dist": 1, + "finish_on_arrival": true + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/pick_up", + "vars": { + "target_key": "bb_food_target", + "drop_held": true + } + } + ] + } + ] + } +} diff --git a/code/datums/ai/generic_play_instrument.bt.json b/code/datums/ai/generic_play_instrument.bt.json new file mode 100644 index 00000000000..5da0a146839 --- /dev/null +++ b/code/datums/ai/generic_play_instrument.bt.json @@ -0,0 +1,83 @@ +{ + "dm_type": "/datum/bt_node/subtree/generic_play_instrument", + "bindings": { + "bulu13xf": { + "label": "volume", + "default": "50" + } + }, + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_SONG_INSTRUMENT" + }, + "child": { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_true", + "vars": { + "invert": true, + "key": "BB_INSTRUMENT_UP_ASS" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/is_holding_target", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "invert": true, + "key": "BB_SONG_INSTRUMENT" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_SONG_INSTRUMENT" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/pick_up", + "vars": { + "target_key": "BB_SONG_INSTRUMENT" + } + } + ] + } + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/keep_playing_instrument", + "vars": { + "song_instrument_key": "BB_SONG_INSTRUMENT" + } + }, + { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/setup_instrument", + "vars": { + "song_instrument_key": "BB_SONG_INSTRUMENT", + "song_lines_key": "song_lines" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/play_instrument", + "vars": { + "song_instrument_key": "BB_SONG_INSTRUMENT", + "volume": "$bulu13xf" + } + } + ] + } + ] + } +} diff --git a/code/datums/ai/generic_subtrees/basic_find_target.bt.json b/code/datums/ai/generic_subtrees/basic_find_target.bt.json new file mode 100644 index 00000000000..e6dfedebcbb --- /dev/null +++ b/code/datums/ai/generic_subtrees/basic_find_target.bt.json @@ -0,0 +1,10 @@ +{ + "dm_type": "/datum/bt_node/subtree/basic_find_target", + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } +} diff --git a/code/datums/ai/generic_subtrees/basic_find_target.dm b/code/datums/ai/generic_subtrees/basic_find_target.dm new file mode 100644 index 00000000000..989688e5a06 --- /dev/null +++ b/code/datums/ai/generic_subtrees/basic_find_target.dm @@ -0,0 +1,2 @@ +/datum/bt_node/subtree/basic_find_target + behavior_tree_json = "code/datums/ai/generic_subtrees/basic_find_target.bt.json" diff --git a/code/datums/ai/generic_subtrees/capricious_pick_target.bt.json b/code/datums/ai/generic_subtrees/capricious_pick_target.bt.json new file mode 100644 index 00000000000..33549460fbb --- /dev/null +++ b/code/datums/ai/generic_subtrees/capricious_pick_target.bt.json @@ -0,0 +1,18 @@ +{ + "dm_type": "/datum/bt_node/subtree/capricious_pick_target", + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/capricious_retaliate", + "vars": { + "targeting_strategy": "BB_TARGETING_STRATEGY", + "ignore_faction": true + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/pick_retaliate_target" + } + ] +} diff --git a/code/datums/ai/generic_subtrees/capricious_pick_target.dm b/code/datums/ai/generic_subtrees/capricious_pick_target.dm new file mode 100644 index 00000000000..91cd88264b6 --- /dev/null +++ b/code/datums/ai/generic_subtrees/capricious_pick_target.dm @@ -0,0 +1,4 @@ +/// Like [/datum/bt_node/subtree/pick_retaliate_target], but first rolls to randomly aggro on or +/// forgive a nearby mob. Used by animals that pick fights for no reason. +/datum/bt_node/subtree/capricious_pick_target + behavior_tree_json = "code/datums/ai/generic_subtrees/capricious_pick_target.bt.json" diff --git a/code/datums/ai/generic_subtrees/climb_tree.bt.json b/code/datums/ai/generic_subtrees/climb_tree.bt.json new file mode 100644 index 00000000000..0f11ef37f35 --- /dev/null +++ b/code/datums/ai/generic_subtrees/climb_tree.bt.json @@ -0,0 +1,50 @@ +{ + "dm_type": "/datum/bt_node/subtree/climb_tree", + "bindings": { + "bb0lcfol": { + "label": "tree_climbing_cooldown", + "default": "30 SECONDS" + } + }, + "type": "decorator", + "decorator": "/datum/bt_node/decorator/cooldown", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "cooldown_key": "BB_TREE_CLIMBING_COOLDOWN", + "cooldown_duration": "$bb0lcfol" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CLIMBED_TREE" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CLIMBED_TREE" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/ai_interact", + "vars": { + "target_key": "BB_CLIMBED_TREE" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/clear_key", + "vars": { + "key": "BB_CLIMBED_TREE" + } + } + ] + } + } +} diff --git a/code/datums/ai/generic_subtrees/climb_tree.dm b/code/datums/ai/generic_subtrees/climb_tree.dm new file mode 100644 index 00000000000..7160d455982 --- /dev/null +++ b/code/datums/ai/generic_subtrees/climb_tree.dm @@ -0,0 +1,2 @@ +/datum/bt_node/subtree/climb_tree + behavior_tree_json = "code/datums/ai/generic_subtrees/climb_tree.bt.json" diff --git a/code/datums/ai/generic_subtrees/find_food.bt.json b/code/datums/ai/generic_subtrees/find_food.bt.json new file mode 100644 index 00000000000..a67032a285f --- /dev/null +++ b/code/datums/ai/generic_subtrees/find_food.bt.json @@ -0,0 +1,19 @@ +{ + "dm_type": "/datum/bt_node/subtree/find_food", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/key_off_cooldown", + "vars": { + "observer_abort": "BT_ABORT_SELF", + "cooldown_key": "BB_NEXT_FOOD_EAT" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target", + "vars": { + "target_key": "BB_TARGET_FOOD", + "targeting_strategy": "/datum/targeting_strategy/pickup_item", + "target_source": "/datum/target_source/oview_typed/from_bb_key/basic_foods", + "revalidation_mode": "TARGET_REVALIDATE" + } + } +} diff --git a/code/datums/ai/generic_subtrees/find_food.dm b/code/datums/ai/generic_subtrees/find_food.dm new file mode 100644 index 00000000000..74b2d386f77 --- /dev/null +++ b/code/datums/ai/generic_subtrees/find_food.dm @@ -0,0 +1,2 @@ +/datum/bt_node/subtree/find_food + behavior_tree_json = "code/datums/ai/generic_subtrees/find_food.bt.json" diff --git a/code/datums/ai/generic_subtrees/find_partner.bt.json b/code/datums/ai/generic_subtrees/find_partner.bt.json new file mode 100644 index 00000000000..d48387851e1 --- /dev/null +++ b/code/datums/ai/generic_subtrees/find_partner.bt.json @@ -0,0 +1,32 @@ +{ + "dm_type": "/datum/bt_node/subtree/find_partner", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "key": "BB_FUCKS" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_BREED_READY" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/key_off_cooldown", + "vars": { + "cooldown_key": "BB_PARTNER_SEARCH_TIMEOUT" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/find_partner", + "vars": { + "target_key": "BB_BABIES_TARGET", + "partner_types_key": "BB_BABIES_PARTNER_TYPES", + "child_types_key": "BB_BABIES_CHILD_TYPES" + } + } + } + } +} diff --git a/code/datums/ai/generic_subtrees/find_partner.dm b/code/datums/ai/generic_subtrees/find_partner.dm new file mode 100644 index 00000000000..a808c199fde --- /dev/null +++ b/code/datums/ai/generic_subtrees/find_partner.dm @@ -0,0 +1,2 @@ +/datum/bt_node/subtree/find_partner + behavior_tree_json = "code/datums/ai/generic_subtrees/find_partner.bt.json" diff --git a/code/datums/ai/generic_subtrees/find_stealable_object.bt.json b/code/datums/ai/generic_subtrees/find_stealable_object.bt.json new file mode 100644 index 00000000000..0176eedb0eb --- /dev/null +++ b/code/datums/ai/generic_subtrees/find_stealable_object.bt.json @@ -0,0 +1,88 @@ +{ + "dm_type": "/datum/bt_node/subtree/find_stealable_object", + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/is_dragging", + "child": { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/random_chance_from_key", + "vars": { + "chance_key": "BB_GUILTY_CONSCIOUS_CHANCE" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/stop_dragging" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/succeed" + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_true", + "vars": { + "invert": true, + "key": "BB_WANTS_TO_COMMIT_THEFT" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/random_chance_from_key", + "vars": { + "chance_key": "BB_STEAL_CHANCE" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/set_bb_key", + "vars": { + "target_key": "BB_WANTS_TO_COMMIT_THEFT", + "value": true + } + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_true", + "vars": { + "invert": true, + "key": "BB_WANTS_TO_COMMIT_THEFT" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/succeed" + } + }, + { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_ITEM_TO_STEAL", + "targeting_strategy": "/datum/targeting_strategy/pickup_item/stealable_item", + "target_source": "/datum/target_source/oview", + "must_be_reachable": true + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/set_bb_key", + "vars": { + "target_key": "BB_WANTS_TO_COMMIT_THEFT", + "value": false + } + } + ] + } + ] +} diff --git a/code/datums/ai/generic_subtrees/find_stealable_object.dm b/code/datums/ai/generic_subtrees/find_stealable_object.dm new file mode 100644 index 00000000000..4811a5207df --- /dev/null +++ b/code/datums/ai/generic_subtrees/find_stealable_object.dm @@ -0,0 +1,2 @@ +/datum/bt_node/subtree/find_stealable_object + behavior_tree_json = "code/datums/ai/generic_subtrees/find_stealable_object.bt.json" diff --git a/code/datums/ai/generic_subtrees/forage_and_retaliate.bt.json b/code/datums/ai/generic_subtrees/forage_and_retaliate.bt.json new file mode 100644 index 00000000000..17a52c4d35d --- /dev/null +++ b/code/datums/ai/generic_subtrees/forage_and_retaliate.bt.json @@ -0,0 +1,92 @@ +{ + "dm_type": "/datum/bt_node/subtree/forage_and_retaliate", + "bindings": { + "q8w3rtv1": { + "label": "Food Finder", + "default": "/datum/bt_node/subtree/find_food" + }, + "z4n9bk7p": { + "label": "Retaliate Manager", + "default": "/datum/bt_node/subtree/capricious_pick_target" + } + }, + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_BASIC_MOB_RETALIATE_LIST", + "observer_abort": "BT_ABORT_SELF" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/move_to_and_eat" + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_DISABLE_IDLE", + "invert": true + }, + "child": { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + } + ] + }, + { + "type": "subtree", + "subtype": "$z4n9bk7p" + }, + { + "type": "subtree", + "subtype": "$q8w3rtv1" + } + ] +} diff --git a/code/datums/ai/generic_subtrees/forage_and_retaliate.dm b/code/datums/ai/generic_subtrees/forage_and_retaliate.dm new file mode 100644 index 00000000000..291fa033090 --- /dev/null +++ b/code/datums/ai/generic_subtrees/forage_and_retaliate.dm @@ -0,0 +1,2 @@ +/datum/bt_node/subtree/forage_and_retaliate + behavior_tree_json = "code/datums/ai/generic_subtrees/forage_and_retaliate.bt.json" diff --git a/code/datums/ai/generic_subtrees/make_babies.dm b/code/datums/ai/generic_subtrees/make_babies.dm new file mode 100644 index 00000000000..9ed043fb297 --- /dev/null +++ b/code/datums/ai/generic_subtrees/make_babies.dm @@ -0,0 +1,2 @@ +/datum/bt_node/subtree/make_babies + behavior_tree_json = "code/datums/ai/babies/make_babies.bt.json" diff --git a/code/datums/ai/generic_subtrees/move_to_and_eat.bt.json b/code/datums/ai/generic_subtrees/move_to_and_eat.bt.json new file mode 100644 index 00000000000..652b9f8d376 --- /dev/null +++ b/code/datums/ai/generic_subtrees/move_to_and_eat.bt.json @@ -0,0 +1,36 @@ +{ + "dm_type": "/datum/bt_node/subtree/move_to_and_eat", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_TARGET_FOOD" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_TARGET_FOOD", + "required_dist": 1 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/perform_emote", + "vars": { + "emote": "eats up happily!" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/ai_interact", + "vars": { + "target_key": "BB_TARGET_FOOD" + } + } + ] + } +} diff --git a/code/datums/ai/generic_subtrees/move_to_and_eat.dm b/code/datums/ai/generic_subtrees/move_to_and_eat.dm new file mode 100644 index 00000000000..d3b051bd1e0 --- /dev/null +++ b/code/datums/ai/generic_subtrees/move_to_and_eat.dm @@ -0,0 +1,2 @@ +/datum/bt_node/subtree/move_to_and_eat + behavior_tree_json = "code/datums/ai/generic_subtrees/move_to_and_eat.bt.json" diff --git a/code/datums/ai/generic_subtrees/move_to_and_hunt.bt.json b/code/datums/ai/generic_subtrees/move_to_and_hunt.bt.json new file mode 100644 index 00000000000..b1e027d0dee --- /dev/null +++ b/code/datums/ai/generic_subtrees/move_to_and_hunt.bt.json @@ -0,0 +1,63 @@ +{ + "dm_type": "/datum/bt_node/subtree/move_to_and_hunt", + "bindings": { + "bvtz06kb": { + "label": "hunting_target", + "default": "BB_LOW_PRIORITY_HUNTING_TARGET" + }, + "b3y599q4": { + "label": "hunting_target", + "default": "BB_LOW_PRIORITY_HUNTING_TARGET" + }, + "brrasnah": { + "label": "hunting_target", + "default": "BB_LOW_PRIORITY_HUNTING_TARGET" + }, + "bqwjf4id": { + "label": "hunt_cooldown", + "default": "5 SECONDS" + }, + "bm3y5m55": { + "label": "cooldown_key", + "default": "" + }, + "bd1towgc": { + "label": "behavior_combat_mode", + "default": "TRUE" + }, + "b3cnse9r": { + "label": "always_reset_target", + "default": "TRUE" + } + }, + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "$bvtz06kb" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "$b3y599q4", + "required_dist": 1 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/hunt_target/interact_with_target", + "vars": { + "target_key": "$brrasnah", + "always_reset_target": "$b3cnse9r", + "hunt_cooldown": "$bqwjf4id", + "cooldown_key": "$bm3y5m55", + "behavior_combat_mode": "$bd1towgc" + } + } + ] + } +} diff --git a/code/datums/ai/generic_subtrees/move_to_and_hunt.dm b/code/datums/ai/generic_subtrees/move_to_and_hunt.dm new file mode 100644 index 00000000000..428ca9db235 --- /dev/null +++ b/code/datums/ai/generic_subtrees/move_to_and_hunt.dm @@ -0,0 +1,2 @@ +/datum/bt_node/subtree/move_to_and_hunt + behavior_tree_json = "code/datums/ai/generic_subtrees/move_to_and_hunt.bt.json" diff --git a/code/datums/ai/generic_subtrees/move_to_reinforce.bt.json b/code/datums/ai/generic_subtrees/move_to_reinforce.bt.json new file mode 100644 index 00000000000..08bad36b3f4 --- /dev/null +++ b/code/datums/ai/generic_subtrees/move_to_reinforce.bt.json @@ -0,0 +1,30 @@ +{ + "dm_type": "/datum/bt_node/subtree/move_to_reinforce", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "key": "BB_BASIC_MOB_REINFORCEMENT_TARGET" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_BASIC_MOB_REINFORCEMENT_TARGET", + "required_dist": 0, + "finish_on_arrival": false + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/clear_key", + "vars": { + "key": "BB_BASIC_MOB_REINFORCEMENT_TARGET" + } + } + ] + } +} diff --git a/code/datums/ai/generic_subtrees/move_to_reinforce.dm b/code/datums/ai/generic_subtrees/move_to_reinforce.dm new file mode 100644 index 00000000000..3a3a95c47ef --- /dev/null +++ b/code/datums/ai/generic_subtrees/move_to_reinforce.dm @@ -0,0 +1,2 @@ +/datum/bt_node/subtree/move_to_reinforce + behavior_tree_json = "code/datums/ai/generic_subtrees/move_to_reinforce.bt.json" diff --git a/code/datums/ai/generic_subtrees/pick_retaliate_target.bt.json b/code/datums/ai/generic_subtrees/pick_retaliate_target.bt.json new file mode 100644 index 00000000000..c1bef09b791 --- /dev/null +++ b/code/datums/ai/generic_subtrees/pick_retaliate_target.bt.json @@ -0,0 +1,10 @@ +{ + "dm_type": "/datum/bt_node/subtree/pick_retaliate_target", + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } +} diff --git a/code/datums/ai/generic_subtrees/pick_retaliate_target.dm b/code/datums/ai/generic_subtrees/pick_retaliate_target.dm new file mode 100644 index 00000000000..e4e52f3d10c --- /dev/null +++ b/code/datums/ai/generic_subtrees/pick_retaliate_target.dm @@ -0,0 +1,3 @@ +/// Picks a target from our retaliate list (real attackers only). +/datum/bt_node/subtree/pick_retaliate_target + behavior_tree_json = "code/datums/ai/generic_subtrees/pick_retaliate_target.bt.json" diff --git a/code/datums/ai/generic_subtrees/random_walk.bt.json b/code/datums/ai/generic_subtrees/random_walk.bt.json new file mode 100644 index 00000000000..fb0446306c3 --- /dev/null +++ b/code/datums/ai/generic_subtrees/random_walk.bt.json @@ -0,0 +1,29 @@ +{ + "dm_type": "/datum/bt_node/subtree/random_walk", + "bindings": { + "bf07i8ep": { + "label": "walk_chance", + "default": "25" + } + }, + "type": "decorator", + "decorator": "/datum/bt_node/decorator/true_for_time", + "vars": { + "duration": "5 SECONDS" + }, + "child": { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_FAIL_ON_FAILURE", + "loop_delay": "IDLE_BEHAVIOR_RATE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/idle_random_walk", + "vars": { + "walk_chance": "$bf07i8ep" + } + } + ] + } +} diff --git a/code/datums/ai/generic_subtrees/random_walk.dm b/code/datums/ai/generic_subtrees/random_walk.dm new file mode 100644 index 00000000000..f4160f87ed0 --- /dev/null +++ b/code/datums/ai/generic_subtrees/random_walk.dm @@ -0,0 +1,2 @@ +/datum/bt_node/subtree/random_walk + behavior_tree_json = "code/datums/ai/generic_subtrees/random_walk.bt.json" diff --git a/code/datums/ai/generic_subtrees/skittish_and_speak.bt.json b/code/datums/ai/generic_subtrees/skittish_and_speak.bt.json new file mode 100644 index 00000000000..9899a48b230 --- /dev/null +++ b/code/datums/ai/generic_subtrees/skittish_and_speak.bt.json @@ -0,0 +1,32 @@ +{ + "dm_type": "/datum/bt_node/subtree/skittish_and_speak", + "bindings": { + "f17iiafz": { + "label": "Speech Behavior", + "default": "/datum/bt_node/ai_behavior/random_speech_blackboard" + } + }, + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "1 SECONDS", + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_skittish_combat" + } + ] + }, + { + "type": "leaf", + "behavior": "$f17iiafz" + } + ] +} diff --git a/code/datums/ai/generic_subtrees/skittish_and_speak.dm b/code/datums/ai/generic_subtrees/skittish_and_speak.dm new file mode 100644 index 00000000000..6bef54057e6 --- /dev/null +++ b/code/datums/ai/generic_subtrees/skittish_and_speak.dm @@ -0,0 +1,2 @@ +/datum/bt_node/subtree/skittish_and_speak + behavior_tree_json = "code/datums/ai/generic_subtrees/skittish_and_speak.bt.json" diff --git a/code/datums/ai/generic_subtrees/steal_and_flee.bt.json b/code/datums/ai/generic_subtrees/steal_and_flee.bt.json new file mode 100644 index 00000000000..922f09d1d09 --- /dev/null +++ b/code/datums/ai/generic_subtrees/steal_and_flee.bt.json @@ -0,0 +1,43 @@ +{ + "dm_type": "/datum/bt_node/subtree/steal_and_flee", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_ITEM_TO_STEAL" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_ITEM_TO_STEAL" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/drag_target", + "vars": { + "target_key": "BB_ITEM_TO_STEAL" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/copy_bb_key", + "vars": { + "dest_key": "BB_LAST_STOLEN_ITEM", + "source_key": "BB_ITEM_TO_STEAL" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/clear_key", + "vars": { + "key": "BB_ITEM_TO_STEAL" + } + } + ] + } +} diff --git a/code/datums/ai/generic_subtrees/steal_and_flee.dm b/code/datums/ai/generic_subtrees/steal_and_flee.dm new file mode 100644 index 00000000000..e20201f1fdd --- /dev/null +++ b/code/datums/ai/generic_subtrees/steal_and_flee.dm @@ -0,0 +1,2 @@ +/datum/bt_node/subtree/steal_and_flee + behavior_tree_json = "code/datums/ai/generic_subtrees/steal_and_flee.bt.json" diff --git a/code/datums/ai/hauntium/haunted.bt.json b/code/datums/ai/hauntium/haunted.bt.json new file mode 100644 index 00000000000..88cad7cc215 --- /dev/null +++ b/code/datums/ai/hauntium/haunted.bt.json @@ -0,0 +1,107 @@ +{ + "dm_type": "/datum/ai_controller/haunted", + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/item_being_held", + "vars": { + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_LIKES_EQUIPPER", + "observer_abort": "BT_ABORT_SELF" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/wait", + "vars": { + "duration": 0 + } + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/item_escape_grasp" + } + ] + } + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_HAUNT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_FAIL_ON_FAILURE", + "loop_delay": "2 SECONDS", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/throw_attack/haunted", + "vars": { + "target_key": "BB_HAUNT_TARGET", + "throw_count_key": "BB_HAUNTED_THROW_ATTEMPT_COUNT", + "haunt_list_key": "BB_TO_HAUNT_LIST" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_HAUNT_TARGET", + "required_dist": 3, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/idle_ghost_item" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/pick_haunt_target", + "vars": { + "target_key": "BB_HAUNT_TARGET", + "haunt_list_key": "BB_TO_HAUNT_LIST" + } + } + ] + } + ] +} diff --git a/code/datums/ai/hauntium/haunted_bt_nodes.dm b/code/datums/ai/hauntium/haunted_bt_nodes.dm new file mode 100644 index 00000000000..576f37b8f38 --- /dev/null +++ b/code/datums/ai/hauntium/haunted_bt_nodes.dm @@ -0,0 +1,83 @@ +/// Gates on whether the haunted item pawn is currently inside a mob's inventory. +/datum/bt_node/decaorator/item_being_held + +/datum/bt_node/decorator/item_being_held/check_condition(datum/ai_controller/controller) + return ismob(controller.pawn.loc) + +/datum/bt_node/decorator/item_being_held/register_observe_signals(atom/pawn) + RegisterSignals(pawn, list(COMSIG_ITEM_ENTERED_HANDS, COMSIG_ITEM_DROPPED), PROC_REF(on_signal_changed)) + return TRUE + +/datum/bt_node/decorator/item_being_held/unregister_observe_signals(atom/pawn) + UnregisterSignal(pawn, list(COMSIG_ITEM_ENTERED_HANDS, COMSIG_ITEM_DROPPED)) + +/** + * Attempts to slip out of the holder's hands with a per-tick probability. + * Returns RUNNING while waiting for the chance to fire; SUCCEEDED after escaping. + */ +/datum/bt_node/ai_behavior/item_escape_grasp + +/datum/bt_node/ai_behavior/item_escape_grasp/perform(seconds_per_tick, datum/ai_controller/controller) + if(!SPT_PROB(HAUNTED_ITEM_ESCAPE_GRASP_CHANCE, seconds_per_tick)) + return AI_BEHAVIOR_INSTANT + var/obj/item/item_pawn = controller.pawn + var/mob/item_holder = item_pawn.loc + if(!ismob(item_holder)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + item_pawn.visible_message(span_warning("[item_pawn] slips out of the hands of [item_holder]!")) + item_holder.dropItemToGround(item_pawn, TRUE) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + +///Find someone to haunt +/datum/bt_node/ai_behavior/pick_haunt_target + var/target_key + var/haunt_list_key + +/datum/bt_node/ai_behavior/pick_haunt_target/perform(seconds_per_tick, datum/ai_controller/controller) + if(!prob(HAUNTED_ITEM_ATTACK_HAUNT_CHANCE)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + var/obj/item/item_pawn = controller.pawn + var/list/to_haunt = controller.blackboard[haunt_list_key] + for(var/mob/living/candidate as anything in to_haunt) + if(QDELETED(candidate) || to_haunt[candidate] <= 0) + controller.remove_thing_from_blackboard_key(haunt_list_key, candidate) + continue + if(get_dist(candidate, item_pawn) <= CURSED_VIEW_RANGE) + controller.set_blackboard_key(target_key, candidate) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + +/// Haunted variant: decrements haunt list aggro when throws are exhausted. +/// BT args: target_key, throw_count_key, haunt_list_key +/datum/bt_node/ai_behavior/throw_attack/haunted + var/haunt_list_key + max_attempts = HAUNTED_MAX_THROW_ATTEMPTS + /// Cached from perform args so on_throws_exhausted can access haunt_list_key. + var/active_haunt_list_key + +/datum/bt_node/ai_behavior/throw_attack/haunted/perform(seconds_per_tick, datum/ai_controller/controller) + active_haunt_list_key = haunt_list_key + return ..() + +/datum/bt_node/ai_behavior/throw_attack/haunted/on_throws_exhausted(datum/ai_controller/controller, atom/throw_target, target_key, throw_count_key) + controller.add_blackboard_key_assoc(active_haunt_list_key, throw_target, -1) + return ..() + + +///Teleport every now and then +/datum/bt_node/ai_behavior/idle_ghost_item + ///Chance for item to teleport somewhere else + var/teleport_chance = 4 + time_between_perform = 1 SECONDS + +/datum/bt_node/ai_behavior/idle_ghost_item/perform(seconds_per_tick, datum/ai_controller/controller) + . = ..() + var/obj/item/item_pawn = controller.pawn + if(ismob(item_pawn.loc)) //Being held. dont teleport + return AI_BEHAVIOR_INSTANT + if(SPT_PROB(teleport_chance, seconds_per_tick)) + playsound(item_pawn.loc, 'sound/items/haunted/ghostitemattack.ogg', 100, TRUE) + #ifndef UNIT_TESTS // hauntium teleports can cause mapping nearstation tests to fail if it teleports outside an area + do_teleport(item_pawn, get_turf(item_pawn), 4, channel = TELEPORT_CHANNEL_MAGIC) + #endif + return AI_BEHAVIOR_INSTANT diff --git a/code/datums/ai/hauntium/haunted_controller.dm b/code/datums/ai/hauntium/haunted_controller.dm index b2863a310c1..45388702fc7 100644 --- a/code/datums/ai/hauntium/haunted_controller.dm +++ b/code/datums/ai/hauntium/haunted_controller.dm @@ -1,5 +1,6 @@ /datum/ai_controller/haunted + behavior_tree_json = "code/datums/ai/hauntium/haunted.bt.json" movement_delay = 0.4 SECONDS blackboard = list( BB_TO_HAUNT_LIST = list(), @@ -7,8 +8,6 @@ BB_HAUNT_TARGET, BB_HAUNTED_THROW_ATTEMPT_COUNT, ) - planning_subtrees = list(/datum/ai_planning_subtree/haunted) - idle_behavior = /datum/idle_behavior/idle_ghost_item /datum/ai_controller/haunted/TryPossessPawn(atom/new_pawn) if(!isitem(new_pawn)) diff --git a/code/datums/ai/hauntium/hauntium_subtrees.dm b/code/datums/ai/hauntium/hauntium_subtrees.dm deleted file mode 100644 index 57b5d2ae7e0..00000000000 --- a/code/datums/ai/hauntium/hauntium_subtrees.dm +++ /dev/null @@ -1,24 +0,0 @@ -/datum/ai_planning_subtree/haunted/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/obj/item/item_pawn = controller.pawn - - if(ismob(item_pawn.loc)) //We're being held, maybe escape? - if(controller.blackboard[BB_LIKES_EQUIPPER])//don't unequip from people it's okay with - return - if(SPT_PROB(HAUNTED_ITEM_ESCAPE_GRASP_CHANCE, seconds_per_tick)) - controller.queue_behavior(/datum/ai_behavior/item_escape_grasp) - return SUBTREE_RETURN_FINISH_PLANNING - - if(!SPT_PROB(HAUNTED_ITEM_ATTACK_HAUNT_CHANCE, seconds_per_tick)) - return - - var/list/to_haunt_list = controller.blackboard[BB_TO_HAUNT_LIST] - - for(var/mob/living/haunt_target as anything in to_haunt_list) - if(to_haunt_list[haunt_target] <= 0) - controller.remove_thing_from_blackboard_key(BB_TO_HAUNT_LIST, haunt_target) - continue - - if(get_dist(haunt_target, item_pawn) <= 7) - controller.set_blackboard_key(BB_HAUNT_TARGET, haunt_target) - controller.queue_behavior(/datum/ai_behavior/item_move_close_and_attack/ghostly/haunted, BB_HAUNT_TARGET, BB_HAUNTED_THROW_ATTEMPT_COUNT) - return SUBTREE_RETURN_FINISH_PLANNING diff --git a/code/datums/ai/hunting_behavior/hunting_behaviors.dm b/code/datums/ai/hunting_behavior/hunting_behaviors.dm deleted file mode 100644 index 1095919c058..00000000000 --- a/code/datums/ai/hunting_behavior/hunting_behaviors.dm +++ /dev/null @@ -1,170 +0,0 @@ -/** - * Tells the AI to find a certain target nearby to hunt. - * If a target has been found, we will start to move towards it, and eventually attack it. - */ -/datum/ai_planning_subtree/find_and_hunt_target - /// What key in the blacbkboard do we store our hunting target? - /// If you want to have multiple hunting behaviors on a controller be sure that this is unique - var/target_key = BB_CURRENT_HUNTING_TARGET - /// What behavior to execute if we have no target - var/finding_behavior = /datum/ai_behavior/find_hunt_target - /// What behavior to execute if we do have a target - var/hunting_behavior = /datum/ai_behavior/hunt_target - /// What targets we're hunting for - var/list/hunt_targets - /// In what radius will we hunt - var/hunt_range = 2 - /// What are the chances we hunt something at any given moment - var/hunt_chance = 100 - ///do we finish planning subtree - var/finish_planning = TRUE - -/datum/ai_planning_subtree/find_and_hunt_target/New() - . = ..() - hunt_targets = typecacheof(hunt_targets) - -/datum/ai_planning_subtree/find_and_hunt_target/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(!SPT_PROB(hunt_chance, seconds_per_tick)) - return - - if(controller.blackboard[BB_HUNTING_COOLDOWN(type)] >= world.time) - return - - if(!controller.blackboard_key_exists(target_key)) - controller.queue_behavior(finding_behavior, target_key, hunt_targets, hunt_range) - return - - // We ARE hunting something, execute the hunt. - // Note that if our AI controller has multiple hunting subtrees set, - // we may accidentally be executing another tree's hunt - not ideal, - // try to set a unique target key if you have multiple - - controller.queue_behavior(hunting_behavior, target_key, BB_HUNTING_COOLDOWN(type)) - if(finish_planning) - return SUBTREE_RETURN_FINISH_PLANNING //If we're hunting we're too busy for anything else - -/// Finds a specific atom type to hunt. -/datum/ai_behavior/find_hunt_target - ///is this only meant to search for turf types? - var/search_turf_types = FALSE - -/datum/ai_behavior/find_hunt_target/perform(seconds_per_tick, datum/ai_controller/controller, hunting_target_key, types_to_hunt, hunt_range) - var/mob/living/living_mob = controller.pawn - var/list/interesting_objects = search_turf_types ? RANGE_TURFS(hunt_range, living_mob) : oview(hunt_range, living_mob) - for(var/atom/possible_dinner as anything in typecache_filter_list(interesting_objects, types_to_hunt)) - if(!valid_dinner(living_mob, possible_dinner, hunt_range, controller, seconds_per_tick)) - continue - controller.set_blackboard_key(hunting_target_key, possible_dinner) - EVLOG_MAPTEXT(controller, EVLOG_CATEGORY_AI_TARGETING, "[controller.pawn] has selected [possible_dinner] as a target for blackboard key [hunting_target_key]! Behavior: [src]", get_turf(possible_dinner), "Target: [possible_dinner]") - EVLOG_LINES(controller, EVLOG_CATEGORY_AI_TARGETING, "Line to target", get_turf(controller.pawn), get_turf(possible_dinner)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - -/datum/ai_behavior/find_hunt_target/proc/valid_dinner(mob/living/source, atom/dinner, radius, datum/ai_controller/controller, seconds_per_tick) - if(isliving(dinner)) - var/mob/living/living_target = dinner - if(living_target.stat == DEAD) //bitch is dead - return FALSE - - return can_see(source, dinner, radius) - -/datum/ai_behavior/find_hunt_target/search_turf_types - search_turf_types = TRUE - -/// Hunts down a specific atom type. -/datum/ai_behavior/hunt_target - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH - /// How long do we have to wait after a successful hunt? - var/hunt_cooldown = 5 SECONDS - /// Do we reset the target after attacking something, so we can check for status changes. - var/always_reset_target = FALSE - - -/datum/ai_behavior/hunt_target/setup(datum/ai_controller/controller, hunting_target_key, hunting_cooldown_key) - . = ..() - var/atom/hunt_target = controller.blackboard[hunting_target_key] - if (isnull(hunt_target)) - return FALSE - set_movement_target(controller, hunt_target) - -/datum/ai_behavior/hunt_target/perform(seconds_per_tick, datum/ai_controller/controller, hunting_target_key, hunting_cooldown_key) - var/mob/living/hunter = controller.pawn - var/atom/hunted = controller.blackboard[hunting_target_key] - - if(QDELETED(hunted)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - target_caught(hunter, hunted) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -/datum/ai_behavior/hunt_target/proc/target_caught(mob/living/hunter, atom/hunted) - if(isliving(hunted)) // Are we hunting a living mob? - var/mob/living/living_target = hunted - hunter.manual_emote("chomps [living_target]!") - living_target.investigate_log("has been killed by [key_name(hunter)].", INVESTIGATE_DEATHS) - living_target.death() - - else if(IS_EDIBLE(hunted)) - hunted.attack_animal(hunter) - - else // We're hunting an object, and should delete it instead of killing it. Mostly useful for decal bugs like ants or spider webs. - hunter.manual_emote("chomps [hunted]!") - qdel(hunted) - -/datum/ai_behavior/hunt_target/finish_action(datum/ai_controller/controller, succeeded, hunting_target_key, hunting_cooldown_key) - . = ..() - if(succeeded && hunting_cooldown_key) - controller.set_blackboard_key(hunting_cooldown_key, world.time + hunt_cooldown) - else if(hunting_target_key) - controller.clear_blackboard_key(hunting_target_key) - if(always_reset_target && hunting_target_key) - controller.clear_blackboard_key(hunting_target_key) - -/datum/ai_behavior/hunt_target/interact_with_target - ///what combat mode should we use to interact with - var/behavior_combat_mode = TRUE - -/datum/ai_behavior/hunt_target/interact_with_target/target_caught(mob/living/hunter, obj/structure/cable/hunted) - var/datum/ai_controller/controller = hunter.ai_controller - controller.ai_interact(target = hunted, combat_mode = behavior_combat_mode) - -/datum/ai_behavior/hunt_target/interact_with_target/combat_mode_off - behavior_combat_mode = FALSE - -/datum/ai_behavior/hunt_target/interact_with_target/reset_target - always_reset_target = TRUE - -/datum/ai_behavior/hunt_target/interact_with_target/reset_target_combat_mode_off - always_reset_target = TRUE - behavior_combat_mode = FALSE - -/datum/ai_behavior/hunt_target/use_ability_on_target - always_reset_target = TRUE - ///the ability we will use - var/ability_key - -/datum/ai_behavior/hunt_target/use_ability_on_target/perform(seconds_per_tick, datum/ai_controller/controller, hunting_target_key, hunting_cooldown_key) - var/datum/action/cooldown/ability = controller.blackboard[ability_key] - if(!ability?.IsAvailable()) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - return ..() - -/datum/ai_behavior/hunt_target/use_ability_on_target/target_caught(mob/living/hunter, atom/hunted) - var/datum/action/cooldown/ability = hunter.ai_controller.blackboard[ability_key] - ability.InterceptClickOn(hunter, null, hunted) - - -/datum/ai_behavior/hunt_target/latch_onto - -/datum/ai_behavior/hunt_target/latch_onto/setup(datum/ai_controller/controller, hunting_target_key, hunting_cooldown_key) - . = ..() - var/mob/living/living_pawn = controller.pawn - if(living_pawn.buckled) - return FALSE - -/datum/ai_behavior/hunt_target/latch_onto/target_caught(mob/living/hunter, obj/hunted) - if(hunter.buckled) - return FALSE - if(!hunted.buckle_mob(hunter, force = TRUE)) - return FALSE - hunted.visible_message(span_notice("[hunted] has been latched onto by [hunter]!")) - return TRUE diff --git a/code/datums/ai/hunting_behavior/hunting_cockroach.dm b/code/datums/ai/hunting_behavior/hunting_cockroach.dm deleted file mode 100644 index a786342bfa0..00000000000 --- a/code/datums/ai/hunting_behavior/hunting_cockroach.dm +++ /dev/null @@ -1,2 +0,0 @@ -/datum/ai_planning_subtree/find_and_hunt_target/roach - hunt_targets = list(/obj/effect/decal/cleanable/ants) diff --git a/code/datums/ai/hunting_behavior/hunting_corpses.dm b/code/datums/ai/hunting_behavior/hunting_corpses.dm deleted file mode 100644 index 89d100263fb..00000000000 --- a/code/datums/ai/hunting_behavior/hunting_corpses.dm +++ /dev/null @@ -1,17 +0,0 @@ -/// Find and attack corpses -/datum/ai_planning_subtree/find_and_hunt_target/corpses - finding_behavior = /datum/ai_behavior/find_hunt_target/corpses - hunting_behavior = /datum/ai_behavior/hunt_target/interact_with_target - hunt_targets = list(/mob/living) - -/// Find nearby dead mobs -/datum/ai_behavior/find_hunt_target/corpses - -/datum/ai_behavior/find_hunt_target/corpses/valid_dinner(mob/living/source, mob/living/dinner, radius) - if (!isliving(dinner) || dinner.stat != DEAD) - return FALSE - return can_see(source, dinner, radius) - -/// Find and attack specifically human corpses -/datum/ai_planning_subtree/find_and_hunt_target/corpses/human - hunt_targets = list(/mob/living/carbon/human) diff --git a/code/datums/ai/hunting_behavior/hunting_lights.dm b/code/datums/ai/hunting_behavior/hunting_lights.dm deleted file mode 100644 index 5062a8aaf92..00000000000 --- a/code/datums/ai/hunting_behavior/hunting_lights.dm +++ /dev/null @@ -1,18 +0,0 @@ -/datum/ai_planning_subtree/find_and_hunt_target/look_for_light_fixtures - target_key = BB_LOW_PRIORITY_HUNTING_TARGET - finding_behavior = /datum/ai_behavior/find_hunt_target/light_fixtures - hunting_behavior = /datum/ai_behavior/hunt_target/interact_with_target/light_fixtures - hunt_targets = list(/obj/machinery/light) - hunt_range = 7 - -/datum/ai_behavior/hunt_target/interact_with_target/light_fixtures - hunt_cooldown = 10 SECONDS - always_reset_target = TRUE - -/datum/ai_behavior/find_hunt_target/light_fixtures - -/datum/ai_behavior/find_hunt_target/light_fixtures/valid_dinner(mob/living/source, obj/machinery/light/dinner, radius) - if(dinner.status == LIGHT_BROKEN) //light is already broken - return FALSE - - return can_see(source, dinner, radius) diff --git a/code/datums/ai/hunting_behavior/hunting_mouse.dm b/code/datums/ai/hunting_behavior/hunting_mouse.dm deleted file mode 100644 index 3bcee20b79e..00000000000 --- a/code/datums/ai/hunting_behavior/hunting_mouse.dm +++ /dev/null @@ -1,55 +0,0 @@ -// Mouse subtree to hunt down delicious cheese. -/datum/ai_planning_subtree/find_and_hunt_target/look_for_cheese - hunting_behavior = /datum/ai_behavior/hunt_target/interact_with_target/mouse - hunt_targets = list(/obj/item/food/cheese) - hunt_range = 1 - -// Mouse subtree to hunt down ... delicious cabling? -/datum/ai_planning_subtree/find_and_hunt_target/look_for_cables - target_key = BB_LOW_PRIORITY_HUNTING_TARGET - hunting_behavior = /datum/ai_behavior/hunt_target/interact_with_target/mouse - finding_behavior = /datum/ai_behavior/find_hunt_target/mouse_cable - hunt_targets = list(/obj/structure/cable) - hunt_range = 0 // Only look below us - hunt_chance = 1 - -// When looking for a cable, we can only bite things we can reach. -/datum/ai_behavior/find_hunt_target/mouse_cable - -/datum/ai_behavior/find_hunt_target/mouse_cable/valid_dinner(mob/living/source, obj/structure/cable/dinner, radius) - . = ..() - if(!.) - return - - var/turf/open/floor/below_the_cable = get_turf(dinner) - if(!istype(below_the_cable)) - return FALSE - - return below_the_cable.underfloor_accessibility >= UNDERFLOOR_INTERACTABLE - -// Our hunts have a decent cooldown. -/datum/ai_behavior/hunt_target/interact_with_target/mouse - hunt_cooldown = 20 SECONDS - -/datum/ai_planning_subtree/approach_synthesizer - -/datum/ai_planning_subtree/approach_synthesizer/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/mob/living/pawn = controller.pawn - var/atom/instrument = controller.blackboard[BB_SONG_INSTRUMENT] - if(!isnull(instrument)) - if (!isturf(instrument.loc) || !can_see(pawn, instrument)) - controller.clear_blackboard_key(BB_SONG_INSTRUMENT) - return - if (instrument.IsReachableBy(pawn)) - return - controller.queue_behavior(/datum/ai_behavior/travel_towards/adjacent, BB_SONG_INSTRUMENT) - return SUBTREE_RETURN_FINISH_PLANNING - - controller.queue_behavior(/datum/ai_behavior/find_and_set/piano_synth, BB_SONG_INSTRUMENT, /obj/item/instrument/piano_synth) - -/datum/ai_behavior/find_and_set/piano_synth // Disinclude subtypes - -/datum/ai_behavior/find_and_set/piano_synth/search_tactic(datum/ai_controller/controller, locate_path, search_range = SEARCH_TACTIC_DEFAULT_RANGE) - for(var/obj/item/instrument/piano_synth/synth in oview(search_range, controller.pawn)) - if(synth.type == /obj/item/instrument/piano_synth) - return synth diff --git a/code/datums/ai/idle_behaviors/_idle_behavior.dm b/code/datums/ai/idle_behaviors/_idle_behavior.dm deleted file mode 100644 index bacb8e7cdf3..00000000000 --- a/code/datums/ai/idle_behaviors/_idle_behavior.dm +++ /dev/null @@ -1,5 +0,0 @@ -/datum/idle_behavior - -/datum/idle_behavior/proc/perform_idle_behavior(seconds_per_tick, datum/ai_controller/controller) - set waitfor = FALSE - SHOULD_CALL_PARENT(TRUE) diff --git a/code/datums/ai/idle_behaviors/idle_dog.dm b/code/datums/ai/idle_behaviors/idle_dog.dm deleted file mode 100644 index 4d036e9a7a5..00000000000 --- a/code/datums/ai/idle_behaviors/idle_dog.dm +++ /dev/null @@ -1,21 +0,0 @@ -///Dog specific idle behavior. -/datum/idle_behavior/idle_dog/perform_idle_behavior(seconds_per_tick, datum/ai_controller/basic_controller/dog/controller) - . = ..() - var/mob/living/living_pawn = controller.pawn - if(!isturf(living_pawn.loc) || living_pawn.pulledby) - return - - var/obj/item/carry_item = controller.blackboard[BB_SIMPLE_CARRY_ITEM] - // if we're just ditzing around carrying something, occasionally print a message so people know we have something - if(carry_item && SPT_PROB(5, seconds_per_tick)) - living_pawn.visible_message(span_notice("[living_pawn] gently teethes on \the [carry_item] in [living_pawn.p_their()] mouth."), vision_distance=COMBAT_MESSAGE_RANGE) - - // Custom movement rate, for old corgis, etc. - var/move_chance = controller.blackboard[BB_DOG_IS_SLOW] ? 2.5 : 5 - - if(SPT_PROB(move_chance, seconds_per_tick) && (living_pawn.mobility_flags & MOBILITY_MOVE)) - var/move_dir = pick(GLOB.alldirs) - living_pawn.Move(get_step(living_pawn, move_dir), move_dir) - else if(SPT_PROB(2, seconds_per_tick)) - living_pawn.manual_emote(pick("dances around.", "chases [living_pawn.p_their()] tail!")) - living_pawn.AddComponent(/datum/component/spinny) diff --git a/code/datums/ai/idle_behaviors/idle_haunted.dm b/code/datums/ai/idle_behaviors/idle_haunted.dm deleted file mode 100644 index 756adae9313..00000000000 --- a/code/datums/ai/idle_behaviors/idle_haunted.dm +++ /dev/null @@ -1,15 +0,0 @@ -///If not held, teleport somewhere else -/datum/idle_behavior/idle_ghost_item - ///Chance for item to teleport somewhere else - var/teleport_chance = 4 - -/datum/idle_behavior/idle_ghost_item/perform_idle_behavior(seconds_per_tick, datum/ai_controller/controller) - . = ..() - var/obj/item/item_pawn = controller.pawn - if(ismob(item_pawn.loc)) //Being held. dont teleport - return - if(SPT_PROB(teleport_chance, seconds_per_tick)) - playsound(item_pawn.loc, 'sound/items/haunted/ghostitemattack.ogg', 100, TRUE) - #ifndef UNIT_TESTS // hauntium teleports can cause mapping nearstation tests to fail if it teleports outside an area - do_teleport(item_pawn, get_turf(item_pawn), 4, channel = TELEPORT_CHANNEL_MAGIC) - #endif diff --git a/code/datums/ai/idle_behaviors/idle_monkey.dm b/code/datums/ai/idle_behaviors/idle_monkey.dm deleted file mode 100644 index c32534dce52..00000000000 --- a/code/datums/ai/idle_behaviors/idle_monkey.dm +++ /dev/null @@ -1,37 +0,0 @@ -/datum/idle_behavior/idle_monkey - ///Emotes that will be played commonly during idle behavior. - var/list/common_emotes = list( - "screech", - "roar", - ) - ///Emotes that will be played rarely during idle behavior. - var/list/rare_emotes = list( - "scratch", - "jump", - "roll", - "tail", - ) - -/datum/idle_behavior/idle_monkey/perform_idle_behavior(seconds_per_tick, datum/ai_controller/controller) - . = ..() - var/mob/living/living_pawn = controller.pawn - - if(SPT_PROB(25, seconds_per_tick) && (living_pawn.mobility_flags & MOBILITY_MOVE) && isturf(living_pawn.loc) && !living_pawn.pulledby) - var/move_dir = pick(GLOB.alldirs) - living_pawn.Move(get_step(living_pawn, move_dir), move_dir) - else if(SPT_PROB(5, seconds_per_tick)) - INVOKE_ASYNC(living_pawn, TYPE_PROC_REF(/mob, emote), pick(common_emotes)) - else if(SPT_PROB(1, seconds_per_tick)) - INVOKE_ASYNC(living_pawn, TYPE_PROC_REF(/mob, emote), pick(rare_emotes)) - -/datum/idle_behavior/idle_monkey/pun_pun - common_emotes = list( - "tunesing", - "dance", - "bow", - ) - rare_emotes = list( - "clear", - "sign", - "tail", - ) diff --git a/code/datums/ai/idle_behaviors/idle_random_walk.dm b/code/datums/ai/idle_behaviors/idle_random_walk.dm deleted file mode 100644 index bedcb12e5c7..00000000000 --- a/code/datums/ai/idle_behaviors/idle_random_walk.dm +++ /dev/null @@ -1,86 +0,0 @@ -/datum/idle_behavior/idle_random_walk - ///Chance that the mob random walks per second - var/walk_chance = 25 - -/datum/idle_behavior/idle_random_walk/perform_idle_behavior(seconds_per_tick, datum/ai_controller/controller) - . = ..() - var/mob/living/living_pawn = controller.pawn - if(LAZYLEN(living_pawn.do_afters)) - return FALSE - - var/actual_chance = controller.blackboard[BB_BASIC_MOB_IDLE_WALK_CHANCE] || walk_chance - if(SPT_PROB(actual_chance, seconds_per_tick) && (living_pawn.mobility_flags & MOBILITY_MOVE) && isturf(living_pawn.loc) && !living_pawn.pulledby) - var/move_dir = pick(GLOB.alldirs) - var/turf/destination_turf = get_step(living_pawn, move_dir) - if(!destination_turf?.can_cross_safely(living_pawn)) - return FALSE - living_pawn.Move(destination_turf, move_dir) - return TRUE - -/datum/idle_behavior/idle_random_walk/less_walking - walk_chance = 10 - -/// Only walk if we don't have a target -/datum/idle_behavior/idle_random_walk/no_target - /// Where do we look for a target? - var/target_key = BB_BASIC_MOB_CURRENT_TARGET - -/datum/idle_behavior/idle_random_walk/no_target/perform_idle_behavior(seconds_per_tick, datum/ai_controller/controller) - if (!controller.blackboard_key_exists(target_key)) - return - return ..() - -/// Only walk if we are not on the target's location -/datum/idle_behavior/idle_random_walk/not_while_on_target - ///What is the spot we have to stand on? - var/target_key - -/datum/idle_behavior/idle_random_walk/not_while_on_target/perform_idle_behavior(seconds_per_tick, datum/ai_controller/controller) - var/atom/target = controller.blackboard[target_key] - - //Don't move, if we are are already standing on it - if(!QDELETED(target) && ((isturf(target) && controller.pawn.loc == target) || (target.loc == controller.pawn.loc))) - return - - return ..() - -/// walk randomly however stick near a target -/datum/idle_behavior/walk_near_target - /// chance to walk - var/walk_chance = 25 - /// distance we are to target - var/minimum_distance = 20 - /// key that holds target - var/target_key - -/datum/idle_behavior/walk_near_target/perform_idle_behavior(seconds_per_tick, datum/ai_controller/controller) - . = ..() - var/mob/living/living_pawn = controller.pawn - if(LAZYLEN(living_pawn.do_afters)) - return - - if(!SPT_PROB(walk_chance, seconds_per_tick) || !(living_pawn.mobility_flags & MOBILITY_MOVE) || !isturf(living_pawn.loc) || living_pawn.pulledby) - return - - var/atom/target = controller.blackboard[target_key] - var/distance = get_dist(target, living_pawn) - if(isnull(target) || distance > minimum_distance) //if we are too far away from target, just walk randomly - var/move_dir = pick(GLOB.alldirs) - living_pawn.Move(get_step(living_pawn, move_dir), move_dir) - return - - var/list/possible_turfs = list() - for(var/direction in GLOB.alldirs) - var/turf/possible_step = get_step(living_pawn, direction) - if(get_dist(possible_step, target) > minimum_distance) - continue - if(possible_step.is_blocked_turf() || !possible_step.can_cross_safely(living_pawn)) - continue - possible_turfs += possible_step - - if(!length(possible_turfs)) - return - - var/turf/picked_turf = pick(possible_turfs) - - living_pawn.Move(picked_turf, get_dir(living_pawn, picked_turf)) diff --git a/code/datums/ai/learn_ai.md b/code/datums/ai/learn_ai.md index 5fe38924489..4da35d60000 100644 --- a/code/datums/ai/learn_ai.md +++ b/code/datums/ai/learn_ai.md @@ -1,139 +1,246 @@ -# Learn AI +# Learn Behavior Tree AI -In ye olde days, we designed mob AI, and we built it into simple animals as they were the "non player controlled" mobs. Made sense at the time. But by coding AI directly into the mob, there was so little ability to make unique or complicated AI, and even when it was pulled off the code was hacky and non-reusable. the datum AI system was made to rectify these problems, and expand AI beyond just mobs. +This file covers how the behavior tree system works in /tg/station, and how to use it in practice. -## AI Controllers Attach +## Disclaimer -Any atom can have an AI controller, I'm choosing a basic mob for this guide, because basic mobs stand as a nice "blank canvas" for AI on mobs. Simple animals come with AI built into the mob, basic mobs don't, which is great for us adding AI on top of it. +I strongly reccomend you install the "BehaviorTreeG" extension before continueing, as it is the intended way of editing behavior trees. In theory you can manually edit them, but this is strongly reccomended against due to the nature of the structure of behavior trees. -Anyways, we just define the type of AI this mob has on the ai_controller var. It starts as a type, but is turned into an instance once the mob is instantiated. +## What are behavior trees? -```dm -/mob/living/basic/butterfly - name = "butterfly" - desc = "A colorful butterfly, how'd it get up here?" - // a lot more variables defining for us what a butterfly is +Behavior trees are a common pattern in game AI in which you build your AI out of a tree made out of nodes. These nodes structure and conditionalize AI behavior in a readable way. - ai_controller = /datum/ai_controller/basic/butterfly +The behavior tree runs left to right, allowing you to put high priority behavior first, and only running low priority behavior later. It also lets you sequence a series of behaviors after each other to create sensible sequences (e.g. grab key, unlock door, move through door) + +## Blackboard First + +The blackboard is the controller's shared memory. It makes use of an associative list to allow arbitrary keys to be assigned values. + +- Nodes read keys from it to make decisions. +- Nodes write keys to it so later nodes can use those results. +- Keys are usually things like "current target", "current objective", "cooldowns", or "flags". + +In essence, these are just variables. But by storing them in an associative list we do not have to have define actual variables, and our AI code can be made out of modular pieces and does not need to rely on hardcoded variables on the controller. + +The keys for the blackboard are simple string defines: + +example: +`#define BB_CURRENT_TARGET "Current Target"` + +After this is done, you can start using the key in your behavior tree; by setting it somewhere in the tree it will automatically be added to the blackboard. + +Example flow (in psuedocode): + +```text +FIND TARGET LEAF: + set BB_CURRENT_TARGET + +ATTACK BRANCH: + if BB_CURRENT_TARGET is set + move toward target + attack target ``` -## Controllers Themselves +## Behavior Tree Node -First, let's look at the blackboard. +This is the parent type of all other nodes in this guide. And when active they will return one of three values: -```dm -/datum/ai_controller/basic/cow - blackboard = list( - BB_TARGETING_STRATEGY = new /datum/targeting_strategy/basic/allow_items(), - BB_BASIC_MOB_TIP_REACTING = FALSE, - BB_BASIC_MOB_TIPPER = null, - ) +1. BT_SUCCESS - This node succeeded, and will report this to its parent node +2. BT_FAILURE - This node failed, and will report this to its parent node +3. BT_RUNNING - This node is still running, and will report this to its parent node. + +This is important for the flow of the behavior tree, as different nodes will behave differently depending on whether their children succeed or fail. + +## Leaf / ai_behavior + +A leaf is the behavior node, and it is what performs actual actions. + +It is defined as /datum/bt_node/ai_behavior + +Common leaf jobs: + +- Find a target. +- Move toward something. +- Attack. +- Use an ability. +- Execute job logic (clean, heal, arrest, etc). + +Example: + +![Behavior example](learn_ai_images/behavior_example.png) + +This moves to the target set on the selected target key, and finishes execution when arrived + +## Decorator + +Decorators are essentially condition checks that gate nodes behind them. + +Basic use case: +Only execute combat branch if a target key is present. + +Example: + +![Behavior example](learn_ai_images/decorator_example.png) + +This gates the attack behavior behind the BB_CURRENT_TARGET_KEY being set. + +Outside of this, decorators serve a second important function, which is being able to observe the condition they are checking to see if its still valid while behavior is running in other nodes. There are two use cases for this: + +1. Stop performing behavior of my children because the conditions has become FALSE (e.g. I lost my target, stop attacking!) +2. Stop performing lower priority behavior, because the condition has become TRUE (e.g. Hey I found a target! stop idling!) + +You can also run observers for both of these cases at the same time. + +For most decorators, we can register for signals to observe when a potential condition change might have happend, for example in the case of the target, we register on the target key being changed somewhere. + +If for some reason we cannot have signals check the condition, you can also make it check every ai_controller process(), this is less efficient so use signals when possible. + +## What Composites Are + +Composites are structural nodes that control how child nodes run. + +- They never perform behavior themselves, and exist purely to determine how nodes below them are ran. +- The composite nodes are Selector, Sequence, Parallel, and Subplan + +## BT Node Types + +### 1) Selector + +What it does: +Tries children in order until one returns BT_SUCCEED. + +To be clear; this means until the first child returns BT_FAILURE, the second child will never start. + +Basic use case: +Try attacking, if you cant, perform idle behavior. + +![Behavior example](learn_ai_images/selector_example.png) + +In the above example, we try to run monkey combat behavior, if for some reason this doesn't succeed (usually due to lack of target), we run idle behavior. + +### 2) Sequence + +What it does: +Runs children in order and stops on first failure. Basically the opposite of selector. And allows for chaining behavior that needs to happen in sequence. + +Basic use case: +Move to a target, then perform work once in range. + +If you fail to moving to the target, then performing work would make no sense. So a sequence is best here. + +![Behavior example](learn_ai_images/sequence_example.png) + +In the above example, we move to a target, and then give them our currently held item. If we fail to move to them, we also dont try to give the item + +### 3) Parallel + +What it does: +Runs multiple children in parallel. + +This also has the option to loop the non-primary nodes (e.g. the nodes 2nd and up). This allows for behavior such as continiously checking whether we can find a target. + +You also have the option to stop any secondary behavior once the primary behavior is finished. This is useful in examples where you do not wish to wait until the secondary behavior finishes. + +Basic use case: +Finding targets while also performing all other behavior + +![Behavior example](learn_ai_images/parallel_example.png) + +Parallels also have a repeat_secondary_delay, which is a cooldown between the looping of secondary children. Ideal to cap how often certain behaviors fire (e.g. idle behavior) + +### 4) Subplan + +What it does: +Runs a child branch with a loop policy behavior. + +This essentially allows us to prevent the child from returning its return value to its parent, and instead try to loop. + +This is useful for things like combat and prevents redundant replanning of the entire plan. You can basically do "Hey, if you fail to hit someone this time, try again next time until you succeed". + +If doing this, its important to make use of observers on decorators to make sure you can exit the subplan, else you can get stuck in an endless cycle of behavior. + +Basic use case: +In combat, keep retrying attack logic instead of ending after one attempt. + +![Behavior example](learn_ai_images/subplan_example.png) + +In the above example, we are running a combat behavior where we first check if our target is set, (and have an observer that cancels if the condition changes). + +Then, we run a parallel; on the left side (primary) we have a subplan that runs a looping attack behavior; if this attack behavior fails (We're not close or some other issue), the sub-plan will just try again next tick. + +In the secondary branch, we try to move to the target. (and keep trying this as well, due to the parallels looping rule) + +Due to us having the observer, if for some reason our target changes due to being changed by another node, the decorators observer will re-evaluate and cancel the plan. Without an observer, this behavior would be stuck in an endless loop. + +Subplans also expose `loop_delay`, which when set causes a delay between each loop of the behavior, essentially a cooldown. It can be useful to gate things like idle behavior without triggering re-planning + +## Quick Node Selection Guide + +- Use leaf when you want to do one concrete action. +- Use selector when branches are alternatives. +- Use sequence when steps are ordered dependencies. +- Use parallel when you need concurrent actions +- Use decorator when a branch should only run behind a condition or should react to a condition changing. +- Use subplan when a child branch should loop on succes and/or failure. + +## Subtrees + +Subtrees are essentially modularized pieces of tree that can be re-used in different trees. This allows for patternizing common behaviors into a re-useable tree. + +One feature of subtrees is that if you are making a subtree, and want to change something when using the subtree depending on the AI, you can assign any field in the subtree as a binding; this makes it editable in any controller (or subtree) that the subtree is used. + +You can also re-assign subtrees by setting an "Override ID". By doing this you can call `controller.set_behavior_tree_override()` with the same ID to change what subtree is used in this node. Used in for example pet commands to tell a dog to go fetch dynamically. + +## Editor + +The behavior tree editor can be opened via opening one of our .bt.json files. These .bt.json files are specialized json files that format our (sub)trees. In theory these jsons are human-readable, but the editor makes it much easier to parse them. The editor only edits the .bt.json file itself. + +When opening the editor, you should press "Refresh Types" at the top, this will make the editor parse through all the relevant .dm files to find defined behaviors, decorators, subtrees and ai controllers. You should also run this if you modify or add new nodes, else your cache will be out of date. + +Now, you should be able to see the tree in front of you. In this view you can re-order nodes, add new ones, and change parameters on individual nodes. + +On the left, you can find a palette of all the nodes you have. The compsoite nodes are found in the top left, while the rest are distributed across 3 browsers: + +1. Behavior types (Leaf / ai_behavior nodes) +2. Decorator types (Decorator nodes) +3. Browser (All ai_controllers and subtrees) + +![Behavior example](learn_ai_images/bt_editor_example1.png) + +You can drag these nodes into the node-graph to place them. + +You can also change the nodes connections by dragging from either end to another node. This system is currently a bit fidgetty while I figure out how to become a better editor programmer. + +Once you are done with your tree, be sure to save the file. You can now compile and the JSON will be converted automatically + +## Targeting + +A huge amount of AI work boils down to finding a thing nearby and remembering it. Finding an enemy, finding food, finding a beacon to walk to. Instead of writing a brand new "find" leaf every time, we have one generic leaf that you configure with two helpers. You almost never need to write a new leaf for this. + +The three pieces: + +1. **The leaf** is `update_interaction_target` or `update_targets`. Its job is to look around, find something, and write it to a blackboard key. +2. **A target source** answers the question "what candidates exist?". It gathers a list of nearby things, such as everything in view, only things of a certain type, or items in your hands. +3. **A targeting strategy** answers the question "is this specific candidate valid?". It looks at one candidate at a time and says yes or no. + +So the flow is: the **source** hands the leaf a list of candidates, the leaf runs each one past the **strategy**, and the first one that passes gets written to your target key. + +You configure all of this on the leaf node. + +```text +LEAF: update_interaction_target + target_key: BB_TARGET_FOOD (where to store what we found) + target_source: .../held_items_then_oview/basic_foods (what to look at) + targeting_strategy: .../anything (how to decide it's valid) + vision_range: 7 (how far to look, optional) ``` -Think of the blackboard as the unique format for variables. They are set initially, or by behaviors, **but never in subtrees.** Because we check `blackboard[BB_SOME_KEY]` instead of a variable, we can wipe out variables and slap new ones onto the AI as it runs. For example, this cow uses BB_BASIC_MOB_TIP_REACTING and BB_BASIC_MOB_TIPPER because cows can get tipped, and the AI needs to know that in the subtrees when it plans behavior. And in fact, those two keys aren't required to be defined initially, it's just for clarity that they are. +A few pitfalls: -Speaking of subtrees, let's look at that now. +- **Finding nothing is just a FAILURE.** If the source returns an empty list, the leaf simply fails. +- **Use `/datum/targeting_strategy/anything` if you don't filter for specifics.** With this strategy the only thing we check is `get_dist`, which is enough when the source already gives you the specific candidates you want. +- **Reuse before you build.** There are many existing sources and strategies. Use these before adding new ones. -```dm -/datum/ai_controller/basic/cow +Once the target key is set, the rest of your tree reacts to it the way you've already seen. A `decorator` gates the combat branch behind "is the target key set?". An observer on this decorator can cancel lower priority behavior when the target is set, or cancel its own behavior when the target is lost. - planning_subtrees = list( - /datum/ai_planning_subtree/tip_reaction, //<- goes first - /datum/ai_planning_subtree/find_and_eat_food, //<- goes second - /datum/ai_planning_subtree/random_speech/cow, //<- goes last! But at any point, a previous subtree can end the chain. If a cow is tipped over, it shouldn't make random noises or try finding food! - ) - //and by the end for however many subtrees ran, each one that did may have planned behavior for the AI to act on. -``` - -AI's work by planning specific behaviors, and subtrees are datums that bundle the planning of behavior together. From top to bottom they run, and they can cancel future subtrees. As an example, cows have their very first consideration be tip_reaction, a subtree that prevents further subtrees like eating food and random speech, as well as planning out how the cow reacts (looking sad at the person who tipped it). - -```dm -/datum/ai_controller/basic/cow - ai_traits = null - ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = null - -``` - -Finally, we have some more minor things. - -- ai_traits are flags for the AI, things like "STOP_MOVING_WHEN_PULLED" slightly modifying how the AI acts under some situations. -- ai_movement is how the mob moves to its movement target. ranges from simple behaviors like ai_movement/dumb that awlays move in the direction of the target and hope there's nothing in the way, all the way to ai_movement/jps that plans and occasionally recalcuates more complicated paths, at the cost of more lag. -- idle_behavior is just some simpler behavior to perform when nothing has been planned at all, like idle_behavior/idle_random_walk making a mob wander passively. - -## Subtrees and Behaviors - -Okay, so we have blackboard variables, which are considered by subtrees to plan behaviors. Let's actually look at a subtree planning behaviors, and behaviors themselves. - -```dm -/// this subtree checks if the mob has a target. if it doesn't, it plans looking for food. if it does, it tries to eat the food via attacking it. -/datum/ai_planning_subtree/find_and_eat_food/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - //get things out of blackboard - var/datum/weakref/weak_target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] - var/atom/target = weak_target?.resolve() - var/list/wanted = controller.blackboard[BB_BASIC_FOODS] - - //we see if we have a target (remember, anything can be in that blackboard, it's not a hard reference) - if(!target || QDELETED(target)) - //we need to find some food - controller.queue_behavior(/datum/ai_behavior/find_and_set/in_list, BB_BASIC_MOB_CURRENT_TARGET, wanted) - return //this allows further subtrees to plan since we're doing a non-invasive behavior like checking the viscinity for food. - - //now we know we have a target but should let a hostile subtree plan attacking humans. let's check if it's actually food - if(target in wanted) - controller.queue_behavior(/datum/ai_behavior/basic_melee_attack, BB_BASIC_MOB_CURRENT_TARGET, BB_TARGETING_STRATEGY, BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION) - return SUBTREE_RETURN_FINISH_PLANNING //this prevents further subtrees from planning since we want to focus on eating the food -``` - -And one of those behaviors, `basic_melee_attack`. As I have been doing so far, I've dumped in a bunch of comments explaining how this one behavior gets mobs to chase a target and slap it if in range. - -```dm -///this behavior makes an AI get close to their movement target, and attack every time perform() is called. -/datum/ai_behavior/basic_melee_attack - action_cooldown = 0.6 SECONDS - //flag tells the AI it needs to have a movement target to work, and since it doesn't have "AI_BEHAVIOR_MOVE_AND_PERFORM", it won't call perform() every 0.6 seconds until it is in melee range. Smart! - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT - -/datum/ai_behavior/basic_melee_attack/setup(datum/ai_controller/controller, target_key, targeting_strategy_key, hiding_location_key) - . = ..() - //all this is doing in setup is setting the movement target. setup is called once when the behavior is first planned, and returning FALSE can cancel the behavior if something isn't right. - - //Hiding location is priority - var/datum/weakref/weak_target = controller.blackboard[hiding_location_key] || controller.blackboard[target_key] - var/atom/target = weak_target?.resolve() - if(!target) - return FALSE - //now the AI_BEHAVIOR_REQUIRE_MOVEMENT flag will be happy, we have a target to always be moving towards. - controller.current_movement_target = target - -///perform will run every "action_cooldown" deciseconds as long as the conditions are good for it to do so (we set "AI_BEHAVIOR_REQUIRE_MOVEMENT", so it won't perform until in range). -/datum/ai_behavior/basic_melee_attack/perform(seconds_per_tick, datum/ai_controller/controller, target_key, targeting_strategy_key, hiding_location_key) - . = ..() - var/mob/living/basic/basic_mob = controller.pawn - //targeting strategy will kill the action if not real anymore - var/datum/weakref/weak_target = controller.blackboard[target_key] - var/atom/target = weak_target?.resolve() - var/datum/targeting_strategy/targeting_strategy = controller.blackboard[targeting_strategy_key] - - if(!targeting_strategy.can_attack(basic_mob, target)) - ///We have a target that is no longer valid to attack. Remember that returning doesn't end the behavior, JUST this single performance. So we call "finish_action" with whether it succeeded in doing what it wanted to do (it didn't, so FALSE) and the blackboard keys passed into this behavior. - finish_action(controller, FALSE, target_key) - return //don't forget to end the performance too - - var/hiding_target = targeting_strategy.find_hidden_mobs(basic_mob, target) //If this is valid, theyre hidden in something! - - controller.blackboard[hiding_location_key] = hiding_target - - ///and finally, we're in range, we have a valid target, we can attack. When they fall into crit, they will no longer be a valid target, to the melee behavior will end. - if(hiding_target) //Slap it! - basic_mob.melee_attack(hiding_target) - else - basic_mob.melee_attack(target) - -///and so the action has ended. we can now clean up the AI's blackboard based on the success of the action, and the keys passed in. -/datum/ai_behavior/basic_melee_attack/finish_action(datum/ai_controller/controller, succeeded, target_key, targeting_strategy_key, hiding_location_key) - . = ..() - ///if the behavior failed, the target is no longer valid, so we should lose aggro of them. We remove the target_key (which could be anything, it's whatever key was passed into the behavior by the subtree) from the blackboard. Couldn't do THAT with normal variables! - if(!succeeded) - controller.blackboard -= target_key -``` +> Note: combat target _searching_ during a fight is usually done by the `update_targets` leaf, which keeps `BB_CURRENT_TARGET` refreshed while you fight. `update_interaction_target` is the general-purpose "go find a thing" leaf for everything else. diff --git a/code/datums/ai/learn_ai_images/behavior_example.png b/code/datums/ai/learn_ai_images/behavior_example.png new file mode 100644 index 0000000000000000000000000000000000000000..4ab176cd651bb9eb27dc367d04343e2e924f2005 GIT binary patch literal 24461 zcmeFZXH-*Nw>BI_0Z~MhqSRZE-b8vwkS@K44gv|GNQY1@fC2%f7ikhoXps_nQ4x?H zS|Xt-NR5D$(963y&lz7C<2>U%f6h0)AMX#!-aD(!HOn=xxpv+f=xI<>F;Rg)AZjhm zCq^L9Wd!h}Oi2#hS(&ot0sdU_HPTQ4RSmJM0vA_YAL~8_fvOX)pEz6vuCG1UwDbjm zZgyV$T%y!|mHDDwKCAbu6e&AhcgOsN7N& z3rtgR)QlB$1Z^9(1HL28nU#fC92*m0!*5BkZ?39gBcYeLURK|Ixtp}Ut8WK)$og-{xVmgiKg;kMYFf5VOmO(Mco~?I8+Hf``~g|eGF<|J?BlSM zz=hRob>R084sqZUpBV~hfs1-dV5y)_YLF`+P^6FwCvf5Zf13OsiT!`0F#Uy27C}vz zTgp3s-#wtA2@VP}@$B)7Gd$i~uhcE~pZ}(nBk%ncY>m>NQ^b8_TA!)KgH8AZ<*k4H za=^{IVHU&64`S+DTdfxtPin9KoL7Zh$$DQ>QW6>#=GVU4Dz7kwvXR3NojY_!qnvsY zfF?yHB?1ZxGhl1z{I|D2mzB0{1fWY{VLfkeZ{T^g*UgcUk>=k?I)1?D&g{duL?yO~ z!=2^IK~TP$_i6NH>=c6tpaiYasBse6bi8>yVBOCCiG(Z2Ju5R0pzdEB= z8I!Bt>-wV*P2Ri!+$Q35bx%M#e z{D=scchSj7=sE$T!=s;DXI7B9y}N6osrlw>XD2-^Z9!xt2$rlZ7qBs&%fx*GQZ~LX zei`u%k2hD1xoMbQ$_q<Q;V{WpJ7i!S>Z&>>?b)t+*>VkJ% zQd3h=pGq~Iollaf^Mh|Oy&zv&TrA2v^=n*>vrCGNi^GKMd$KCP$4zcpsHv&(DJt47 z59i_2?ZPcBZEbCoOBPZax&mdslN>mo7?8*7C-Xx@=8rf$>@uWHh`bCQe9tdBFFk! zOfy43v$M9sFAQ(Lpha+hH0J5*BGoaFDHj4#Wh}L63D_oWRWpzwxB)fnjHVtddPO1d z;K4UAH3;z%OKJTNyO$S?7RxIyFE9S=`>nAFs2fJC!3e9?WS@N}om61X~Su;=-j1hGp$mQ4S z`MH2j)3LFcHLU}m-B}*aCV&vXfQ>3%|NHl$+LI?4eW3gTTBa9pz%n+9*DERng{j|g zdmrsWot>RC`zksTL0Bv$LRldQ4(Bh1nHyWWxD-Q~Ay0904H;E7i>oe^JRk@!FcMp{ z-`k{^7>&h%@bGrMTzRM)nRszBk3PSWQisn}XQ!%7^OH;hNLdvSEa}!rZ|IptKtMgv z6%}qyj=0GM5`Un=X+YC`?O_sNq1VPFv2+z3fw(yx5Jb~DgsGGh*aO|>Gg;$F*Ss(s zw~Jn&-U)`AhO(YPhif^-msdBha;o@_wM=y{Fx9m_0kn8(8G1^4x%oO_QLg(Jz;qm+ZofhyZ#Fou= zVAM$b+3A&%=dkHZKJ zfidm6Q{^v9&(_!&s;Vw2txd6SSI~I)<%qPI;q_16%CgNAyfWQU1-G$BoET0{P68I6 zvc0`+Vq*Pq+lTxeNL>EtN~G*Ii6r5{$iR?QKZUFjBW$fo$=*kWJ*k_%VK3FJH`5|e zr=T+gQg#9q&fiavm?7ykTXEq{fN77t=hUdsB^19@q1Q@E%sZ*{hdjEWPX-Zu`uft^ z_gWjd!{cMixT)8%Zy#zHF~GWRrfZ+Vw?S8*g%+EiVTMf>?}=KNC+HgNWB0b*c~E~^ zW4CMSmX(O4i8Ha{dV06x3^$(Tl{ZEV=y(27RxK}Is^o|W)H0BLzFJs9`#EYS0r#Vv zoVW2$e1Mk(MfXsr7dEG()mlMC!GCE*(c{=HdyiFt^Qs!`?`u0E{%wx#I`Jsusdmczr5R<3GE=0-xwReFApQB+a+xt+_IfT6BGS$q-~e>sjTn zv^gmx$I7hR9;V^Ut?AKAD{09F zD(879mhQNrXUX8E4ESYHkIasa4#4bqBO)RWk9{TiBD;Zbwf3eecvZ8{QOLH{B5uy2 z3Vkyt+4Y;g_+i#1H!rsdOGAfTqc61U3eU<)Yc})sG`DXg8a0{>_tbf$Tl>6?re-c4 zDb#@Nu8hR4)51J1gQx+Ak$)DO0G;aTPw@dlV4(>9N6>=LlN>sx2dKL5qUD5~N2u{& ze6@vM7~5F=;bEqJDldQ8-uFwNuU=>e;O2yZ8LDY$2ms!w4UQo>jQSF)6?J8tbJrAo zD&VGPCnYvQ@qy zqu>X6QV7{gDRE`q-07RCK=0)ww}wCtzQ<+`_V}IDJvfJu;;ZY>*pe0wuAF{FjvfwK zQt;EM#nn~R=g-QoKwv2KQeRr?gIF{Jb5v>TeS^Zd^b zf92%~+byRm6PlfCi3!f|X#aFq%Y=X;Q=xaZc|Dp&hFif}m)tfzd%Mm0jzWMLmLw(J zHogZEr>jz+9#_?jxfK;{7Usan$VmL%n_MheY1oA+*}3UCGV8A646Uzen-_m5Ymy_! z0O^@^&rwRP>O2b@7b%XLC#N>D_H*^Wu~spgju`*(O^1Hf82O6%yg=mUy_bNbIr)_S z-ll$g%?N^*R=eVFGTa~1f80VQ>)_^1u78DeuPAQ!QnP?=kNnkETMymiz{nFoW zY*Sb$&=3|H>bGLxKNe<{$fR0`{L0z0IMZPV4t}tFKL^`6F+OLNEJtEZehWr z7Jx9Z$WI(%ohce0HshBl{Ba~sf)5;4ypmmIFUt7-Jjy@8;Q1t9*!lVkv$oHH9pR_H z7Z!}0PEXr6{g<9vvDZ;xEr6(1N};c>f9g0&+WuSGJi3`CWJP%5#R-o7^y#5Zz@&5P z6cBEXo_A@;Mx3>xSOFQrfdIiPB4Tb}@J<$(ujLZ3e?X`(mXn4%bOOMFp1w%frkVHA zBcmKyf1~HmKee{CnHm|XU{NuOVMiIVD0MSQT^&*o1b6%6$MSzw4(Psvi)FN$Cw!efdIadF&|lHB5KoO=1Do^oO#8Vg;I`OAP!rg>VI|;9BozZz4ynGRmSH@QBl#_kLtpDkMkkt zd%Qr0fJb43{9a5KcvQpBo1+*8&X)8r0uTnu2DV5fmc!6(W*6ABOuMVA3+Ua@+q)V- zK?1HPM+XOu)+66|6%=gB%_`?dgCzd$?v6sEcQ1)wQsI35LPbSI{4CZx329uS2i;j3 z#Bo=e0YXnoN}{8sb*MA~I`Z-HNl8vFoOi@?`S2ec9Po;Zn*gdnr>)a*cpH_B=!UeTU%R(iEIki zFP-4<=A`6gU1tx=)x#Zr!3R&sAhJL#V)}l#!UCXp&S1Tgii)REY9yK^kOBL8#Z}N+ z36MBMsj2ac1kMF}t;@7nL`6sQi;9kzm|0s_oF4CI_X$4%WR$=vR}5ff<6uX}kE`JR z%1ksG9etCvYy$|Zva-!J=J<{M8z83Z3J7H~pszh?cxDJo4B(jo%tg<@V7uGD6@O2x z+u1TFdc~pc!dSLbrOL%_l#*9!N|7aQ?M1FfedkHl~`J zULisK6*={zC=?C}Aa-p{O)T(X8+h@~_I66}!9%g4OXAFcwW=G~Ze*0+VqvMcuNs}P zcA1Qf{?463z~urc7RY5jo6IF~J$O(WvcFMW16V>3Q4Xv%G zKMtERi@C(VeEE{UK}<}{)WQPdHijL_QxXB(j&a8J-X3mmeM)y-SREMVs8>JyqmFRX zHxG*xB&%E?ztH*=Ir|w!WV2&0YyP#@uU|uy&(DeldQ6t%MA}Ow-`M!{asfE0hQXW| z!yaw`MIHN}07F?1=~ietI23rcAG#P%<|>3Rc|Zu?dwMj0{6Hc#YbyG-0WZ3!c zRH;m&mF_XF+DU_&S#kg@sMXgeTYNFJloTF(6*LG$TJeIF6$mLd&NwJ215-A-Hc?)w zmI?$t4{&t%&_zbF{x{Iiua5jC3q+{O3;?|Ua!gq@4QRk+6zkmFnCs~92_OLwYV%fVKI(Kq%?&b`#OO|=A%gai4h;O5E1j&G(B~?QUL*tOrti z@mGL_dd#+}u~!{vW=ndRsdv)=8Bnbx%n)$Nm1??uvNvqge=Vhz0ye|NRdh65tb5_< z39Z3#&`Vw*2=s0)Irv?`Isw(%+UkSQAYz>1yDRs-@(3{vK(17ZZ2SfYjB4XnMJbRh zdSgDeJb{guzE7P3l)HS-x~&`A3E~Ppk zwipEmFG_-}1p$i6&H3-oFIgXU?NX;?WMujV2E;MYi=?x-{EMLeSxLRel#3e~2qF(S z=TdrNyeDyfOn0zZ2^854Fk1!!u0tP0J)}r73Ei1vKDGbXZ6Ng(_c#W;=#{4KXX}%7 z8#JJNZ-D(8*9OeKUnYLSrVCkcZ`j#{({7Uq^pmZCR=IM0=C}~X%(9lr2J9nJ; zkC(D5FEU}&4G{JKSmfHX&Td4p2Rl1EX=S9-1ZPn3!qC%}9fTFXc=#L0{J|}IzQ&m? zY+tTV)Vk`YPfo({;Z=Zl&+G$%bOF$nKLo@)>}*?zt~21p*QOdauhN)aERZ@Luwm1_ zXzJoeAmy>GU9Y+DT+A)dpFdOSRgJdZRF4E(7Jy(4gw|jnfm2eN8*i7C4cw@>usE(D zw!yy9mQ5j3C=`Wa0b%_b_rxRNL?F%rR>ZBQrVcJQ0X*i14=gzfA@$uLOHs0^<_f*2 zLQ6~d{>*;H$z{-BJeKn3%Y(y1sLyBwNYEsxN;PbhVu&ujD2|(Xa9j#1|K0=B1>1i(DH5UmOf z69{`5*mQBehzO;z0231=lo^D5ba4j-B;7@4$8$JGkn(kne@pdO09{%FkW!O z>j7;XtlfBk&}u>ff&~9d2fO}?1LOWz^$16f8<8%eewr$ar(X5NnHNS$qksTqscC9z z0;+}G{!i`y-)wuAEYK#(8N5Zqru5C+wl2PU)U zF0d@rO<;)^8t|{}0#*-18eq!*+iK2#Z1LT6Lht|N245(}$kVJ{JEq{^j~I(2fPXX( z?f?|m#N1)QoDW<*pLd~&V)=l27rxzOM*sc$yrCg7@^fD{rfQo3*L#!#igW=IU@XKo zV!;$RzcVV3g5#t0;F z1zVJtk$%NKyUYoydk6?0;n@`%;kM9CpqAL2Y02&t`A>6sOV7?l{Py+^&N2B8kKPKZ zwsm^k#<&Mw-^XZP{iHC7qRXO-k2aWVi~sNb2I)bHpJ26wRCai}cA1h#+2 zoma|#jCa~V>r;auOc#rr-^Z&F&u4xNf~~#Ich2sg&h>F#$mGt2Obl2FA<(T|X8`+6#|>NGrW~fP3wy5)kGf85cBL;X-vIzSh}sdD&i;$7A00%tyNjXj8@4?QLJG;R znkgW_1Avgp_YOX4@KPEEQ-b`rj)@;7*84Pl9e!!8LSjwK9EH*4HMdbeGscIkwpR@- zSr`}5^GQCZ%g6G@z98iV`=I8kzVe%l6gS_&{r1N2=W^eyE#P{x+f7Y>RDv<-UxocM zd3TbPJSQ%tEj(s6^k?PLVk;KP9}$(eN#6EB0rjNrY%#&xcQ3`v*|B>F`P|@>svGqk z?e(&WE}Lmr#A@H`cojdVOFw6>?8G77>~!r7XgcoZ&IG#0Pm;0uS^#zN?gzi45aSWx zhM8Xm8g&B#i+`N-_f5A7MEucfHPBwTe{dt_do@zeiC=#8w)V^h1y-GwZN!2S!6A5= z-b1a_1^4m>1 zBKC!U-9*&v9QfCKj=AaSsy#WuG&{(2Hz~KlS8v4{l|33ts^@?I0@v!rqGUH4x2;G1fRYlanP z@0aQS*k$9Cr$!Zu%RQTGyiC7L4iw+Zt-=Psh92hZD^3-@%3N}Q-2Q?$&}*b^1}1(0{}1`;1VI zo>V=EkkZ+-UKdoDXRAl5rB1w}F|IL1g^+wPQf|{U+k9A2wQBCpKj))K2*sLQsS5@e zM%=eN5{h5?H0)HE(ZcS281iPDfRy=p2zqZ*N~DLZ?kek)j;a%KmNsEsKj3f$$Mr(i zu8OIX^9!y2M6rs~;PS&XVvb!4xw;nI`&C`gD-0cEs{8DLUfhP8i~>T;cB+zOH#YXB zo>zOX@4RcZq{6ESH02Jiane#@7%XT%poEzv#%j0A_Q5)3L@l^VED`a-rIid zO`rScT4#O;;`0M0+4HIUr1?${jeAaGH!)c+3y+OHx7E`!m7nVL(caY{2|A2kH_*^X z$P;A@!USIPLG+E*uGT0iGm}=?R*&6LFwIPLlHhyymz)Mx9vtFk`=Y4tNn{+uR^^}{ zyn_5A_3mlQ*11+;03CLbZUXze;jIv#9ZKgq4wYq?!Ix~A&n&rYVu{yFM6XouGK zg?UK2?Pqevy9_DHHU`F)zQz`;-RhJmBXS@UIfKBL z7l51agF$h?(FMlO`I_#_gwc{l;Hpj@inJKW<@|tcDs6G6Er$Is)v8_^2iI2@Zi8$9 z0n`!0bNWYReaO4-#WnDvBmM8akVOp!+IqChjksWD0hitDoWqY@k2q`~8enexs@|d0 zh`iAM8fM7ofQ&FML= zscOMOqrXEIS$;kz%iRW|{ey;37AQd;CP8>So8oq`t++i4IYSk;B=e2+ zFJP*C#gouB`WZe82iD7UKQ}o=h6DH&?21obI_AjdpgDVnOFzB25BaHb5|H9I$Gh5# z35OqvDfM=$8g)f{*e|DgDnFfa$!$yl;rWOwvfJFk!ee|WQ zGm&l8g`bH*O*2z|SRIW?qck*nRAcqDn9sNdWg$UEuV79^Q$0EgDqM51xiyZfPl|V) zV|TwBs!oZHigA|J#|3DrDwYw_kUb^Z$t~s{C9NDYA*0?M<$2;eZhX}x4hQuHvY(9M z`(mqYHDx^$-5z(4O4OJ%Ch0`H75$Xr=z>#G`Fv4+Y(HbVmRi&3#wIfGd|f))-#Khp z_@?%0y7b1)O^%tpJA4H(bibERk_Vb-vfERZHq&l0v$=&OQs6W<;hMGGT90iL)`{)j zX7$NSwtT0d`8x+`TEUs4+3cE~n@Q1u&+BX17?+*Aj^FwnKjf4DVQI#{$ogLK%mhB) zSX&Xdn>U60r&G03vW;tIs}Mt{tm9-oeT*7D;b;6)FTAqudEYD0IdTQ+Zh6})Whp`G zwXg_^Xs{ZQ#In45Iww+ox$wbZKuY)AZx!$+bK$)jLb`=@_|gbv&MJX#k+m6T9D22elx3QYQoZv$iV~j&&)#a*9RAVP{^9s}Htd$C_G8r|83)`b zzfDN<`8}oN2VLfjL2{Zl2nXzZQFf6Ibp)gl!qa8Pso;5I3Iu9&*f)(U9@XnRHfvts zgMN)0ec=crgRW;Aoh#|+yVsfcrFoV$vJ0F}-&Kq&(iK43omsA(r|kYoH+o;t-1EKc zA53Usfr?S$LD{hwV%U<~;~3{1xFPLpFLzS3ps;KAPg_Gvne|N3RQ&z%ZYvneCTTME zmt~2rmrUQWT#S8QlQz|(*6`Wz)yZ@Jy2C|*(S{OgnvI_;h>AFkH0fMe(;{8uVno13 zj4uS7q{p6aWTr_Y=!ZMCY~Cm18~)oEPDaga=A5vw->)Q>FlQCQQXoBYKoR+CS>{(n z5k}$$eIV;UTmfYS;iKxcAM!O{KiS!it~exxr-UE5qRy?B!iJ8E*G?abQ-eGh3#yBx zdD@QO)}fxthJxXNElsjs+2Z}dH|In6MjMs~Nc0LOQlv-wW~{TkGu6ffAr0POqQI(? z6KD+>BVc-_&QjK1dJ_$EpLo7j)cv!@4nBK$l-|wM7m>nt*tf-IBB-zx<@eQK29xbj z_~M%eljL@h)Li{?{Qd5*)ktg2RW{NyJlA>D+lgOt z#o@#T!eetB(mA};G1&GF;cpEBRZ@s1GHZAFhR@MnFuT_PU5cbcR1R#g>;&*E#JiS+ z8!oN4PM00W1ZuVDl-GxJ%cO0f4C?gZ?T(#QR>AEziDZ4W^P?c zckKjUc|=TpX_jr%^-`Uj$kE!(esus*&Gx_8V6vW!(>& z%ASJHQi#LIZ5L^&J0MWt#VkUO2kzL+^Jrgh(b@7jsq<>FX7aNBxQtA3lvk>GO7P2f z6DzF^M&)>om2K#ZIykxg5f;k)XXaVPYW5FH@PXZEQmiF`v2?CzXn(#=uKyD)_vtDe zTH~uJt@sq|gJ_c4jr`SS^X7Hl-L&Cr5*_NpiV0zV)_8p(S-=V);*;S(S6Wb>27Ej7S6h8 zxmVa@I+}H!7NF-ZkFF*Y3(at1$DbH7d3`7~rimFt&U9z)0+X3yLE9Xjm1R=a@^_tB z%nc3s-^)?Tju=SVxF-}v!Nksm`g|g0N&#JR?{TumUAYRXyZ5@=Y~}qvl1_&vHSW>s z=aSJ^8}Qkink_py`_vAb-)!-b!9gXvC-}6l{sKo}bgT8I988Kq@6>m-dH zc`oD*I7xi2x)VSmHNH88Rs?elTwD0f2ozmzDIj*mB~hv8LYC$TZ`Z@TB8`eD>Y|Hq zKVS~_Cf4m%?D#|tuGV{x>Yk03p^JtSWU9#Tu>Ui2<_pw!7g@~}L=^0sz_%jnV{+BS z$yGQDXoaRPAk4&k)?~1qj*>^DFYhJm69Bv2xHt{a@@VzBTj$f}OQ3vNpyU}f)uwZ_ zSL*E2T;`(Gv7IFTD{js8D;WrT3pgNmy@Ag->!?`#*^9%`2-P$FtiSJfPoP8qQbK|N z9_PQN>i;KCXX}>X$IT_YzTDBSGXq>R!{EP;V$SLt87%-;o`q#A%PQ|PX^@ChmwR^)6F*Ik#1ah=iI%(D{|V(Pq~ z4IXH9HLYr&P6;a1hL#j{cdNoky%OF_HKQDhc?aq++b{z(dQcpHPge&Q({S_s@T5sw z_~4*+J-L(bCPfQ>Y>2osiIC&E>3_?ioYA|?m$?M`|PEmwU0>3 zwI-I7DDET!$AusCswREdXALahz?+c2$?bNL49apD2-d^zW6bLOI{nxGsAX-HUx8hH zpw)v38KerUc8RKY*Eh#C*{g9+7tJYc)sb6eYx67C+1gcD7Kq?V+|COYjO;&j55V>H zXYD;KB7H#$e~wXXNTTFrWGs6NypgjBb6U_hWZu3I`NW+BSJ*rxcG&Z{IEH z=T@rhXg#h8pPe{Tj%jA!UsFqnuoh`Os@nMQ@X32y*OXxU^7;zPN{^7y)Zmpy?a)wD zbmwpN#c;GGdIsoY9JihpF%4GnA^zmi8yGv(K&0*#jePU4{J} z^m2QNX=Dm98W?UmPC@#v5*$M-F+>~H_PENUzPbpjRHqYbT;s6vl#0HS@1D5r*6Sr3 z)`Csj4y*os3jwQ|2f9`d)e4~cZeNnbVa^DGx_%*s5mvz#w6BX;goG z5H-8@PiA+kY#Re#Ummp^lL#C@3pSYQCXPA#O0HC26YV>6ODk4v{dz#@`YP&(XvdBE zoqQ;pDy{LVb{5APDDSxDoP?A+KYEokKLnZ5!LR+=cZUy8Z|KA)F;tD_T>HYW*a}7~ zDcZ2xt!%dK_{R>@_xAAEu+RBaGBAhuB0P0*F=NrCzun(ozAQGVR9F?b+S}zTF8w=x zFXG@_{5-k|Pqs-MnPMBSkL7RJ#4z-<$)+n+nzOYSimoTX0=MtL z1K#`F36=$i6=uPiY}oK5VTHpr7V4Yg=WH$KGm3z=Saqp;r zKgbGF12I7ZZ)G*9I9CxE4xDemtXkGBtOWlFdQTDrkBq(PR@UdluHXbt5AJETZ?D={ z`lga>i}kdU^gm(BZ0rB@o3ua3+oQ$tYmNRakKr|1C6I<-gqqEGzg`2brrqM1qTKgM zLmOCzmd_SB%(*z-3&HHRFU?JZUX1*-IMSmJA>kKmJ?qS5x>gzrW4

o3Zvu(2%r8 zUt0#kp1(=KxTJ+I-@eNQElC^FHu8bL4~3toG??I-VfKO3Gt32+`?IBY^TY^y;G~U) z>+y(RpLX_@X=DASLLh0RS$I)iDjjX%1DG@GhtuJ5LYjbDLYTF$NCWONIt#6nN~0Bi%hZ!BiKw2#IA_ zruCfyWi6RZ6fO(?HhwK1q-A+LP0E@sMcx-Rf|t!I338zi)n#l?Ny&2R7KC*Zy(;AA zyQFeFBRoD_&inP{6cpMHZeUe0!MOJKm=K^%#TrytY(admn$_bU{2^s$=2+ImrSd~T zIe9?RkjF&Hj!j5o*|RlL1d?B)eeJMz5~s~5!M^Wi?VBF$pBx+5RvAU2UZ!5E+ZL;E zmXHo9W>Ebi5v+TCPRF#}QGC8Vs;s9}F;ZO};?4jiv`Sj?wbXDe5?7X<6;Cr1t_>a-FFJWlqjZ>Oy5n9h0|hH5E$l#%f5M znlI2Tq!w%%C8gE`6z$~@3(La;b3RrK-}fSA9epfD=)XHh>1x|#SEQs=$}*?qe>=_{ z!wlUS+7C;uNWnn+-kW4J4OP#<%-up1T2xydBMPC$8vLJDLMlUkd3(tGs-V;#ty7Xt z{DL2Hdj3O0w6lcR-oo=;r%8LVRkbMAU|QcvXqewdGW5+!O2MY$@hsSxwj}({CQdWk zN-6M5o#fGMsa0;6xp@~o-_F=o_zc+VcHtK(g-XWaL{o@oSz3oh%zfR4BiPp&ueD)3 z$$YC|pR~sTYeO<~qwF-G=k<6N1#n)4*%2R~&vzRpC%25U26NdC?73^4?ZKGP8nmpz zw-E3QDHvmcjvAcA>;2S|JU`UwEgQvtf9=i`m^!aXCZYaM7%WxXp_F4v=P=~Fs0b%p zu5aYOQt^At`AxWIWw}Y*f-T}p=K@_B$NF4N{%Xiw0qe6m?p>O+n_d%rVB~Q>TeIQT z3nN@YLwWQcA|J7hf@Dj$hax_hk#hwq25P?Md0+L z;@TmGC<^m6goZ1mmm;A#(KgfoHJN1oN0VBNB&ki6dh_&D%VUrq#eZGPXf(BLF}1}| zXK8q1J24IDp#yffvk&d93bo8r3EP}NzP^>C8)9^wK@qKQE;7-ac=l0kn2QL3zP`;Z ztEaDYG-y<5)n@L>m74qG_m9m90gMR#=r!h6E}%!PdWJ`~#N6!il-SRux435IEk$$h zX$KEIzhT>~uQTeiIB_hiyTXy`X)zWj z-KwI8Pt81D`I%voln%Ulwa~4%n3wvSEB!66zs$zO>b=p&M)f}RRxl?0pIp~TpFc#w z?T50D`sJ*4f1OX4X4G1xp3DnMDV5(`lkAy1n|#)`o!AEdlO?_A6`?Dluu%lbzNzqP4MOZ`*hTQ@Ps z@0z~DR&I3#rSdMsrPmGb%5xF+&tzQ_T~c3g-li+%B((GE7BBIn!R8Em1 z*-KDkCA{i$0*g^SH(nSTefMV?AGC^^LyyZkHEGw0c*G3xtGwSzs?)OS7*188;QjJx zHyXcU7)$EIw~tFK?a$+ab427z9J8l_)9(*vS-56sivr2!b@ujhtLe~dW`m=C0||rs z%mqju{2DsZBrx1|u%gys!sBCjOHf?y2NzbK+`|6jLv5u{C#q5yZo@!NqW1m%`Fg;N z=owhZw$@n2Ry4L_!@KRTL(N9T;yZt>5h%=%42;z8kP}u*n4s&FiMiu>x8yE*fv{(| zus1t#);PsyWNVYObd_pdO3R7+>1f6^C;B-dixNp8>}L3TBz|U(GSKMcitF(&Rk}M# zl|pFSa+KJT`JIHB!Cc!)CVf|hlPrca1=B-CZ>EsEV|ogZI1RuyF0z-uxZ~=!7N!rw z6?&}|9O{Tm-2X=CyDw#hGfs5mvT)C;b=@d2UnEsHS>Fz&#V9q7kVfKnvAw$P_ zWIm;oDuF~_;4oHFZf;*+FC6@o7_Wd#qPxehq!oVZ0^W1ZT$&LzdXc2U$=ZMVXX=xw zOsv7CA$|Y(YhLf?UVI|DRyHkKS>K_oSxP#?19?TRXDP@K&$~!DTW!RDOi!YwX^dhNjGr@7wr zSZE|iWfV1Ju*SbnkYswY(vyQ;khj}WpYnGtP4RqH5Op&t)78;WKvu!@$$3I34@e&u}dyg-=9dl4{}mQFrv#~7!|yQVCm zds9M>V1L4rD}-(srTS|dJ^PTIIY|xae+BL+*NkN@w-Qb!SMn*GpJIeYtX-B;t<>ue zbIbh41cr!6K^B@U!_vrkD8-oZY6F8;EB#fn`5RI>OHmK9HX|{bTH`pX>1VOO_sqHLaWlbn+mxfXNf?45&nP#;-J6?24=_@hHS!1Gs~5n2AmqymiXO%ZG@yPWFk^rVjs?=@v(AUL<&ipn^1 z-Y9%$uXR-;1uCC63`0hfs$7Byi1@Y;(^61Z!iRb5kHspH(I-A1;svHOjR;k3{HAr} zHrWOq!+ECm-=j_r^D0xiuGdNQScxv=jZ~W7z1)+?obsOYkkqcXP$9Rbz8WFPS1Ybu ztr}72aLVB(^DmjLlr6V)7=-4Vga(zIe(kj3fo|ODDcX8OtPf}o30sD^=h>qg-Fow` zN!>6-B2W3GtfFt0N8f5-O&j-r!)?v26$srZ_gYK4g13J@`TKrsN=jhOl7NeQ$ccwX zf9mDiE<};xq0+@GS@473v|*{jfb2egVJ)htL{Hv~5k8%Y=@U%j;1L|Rb9w4Rtz*Np zI;7UC9lK*1PAG??nhQKRrFv#&IoeKxn`6V~#T|zDPXk(sTLIPGq6;?O9oRfijO`Y; zy4Lkgv(OMe*L}C+SwpR<+#EEz78&_^4w~jc11)1{ueY_N!u`z7tx^+#&pZ>#s!AM- z`#rYFK=KIe22 z7x@76*XwRMuaO>Jyj+D(Y(=%2y@ooQ!{_M`+0r(~%dKTF0{eSt_*>*CLPTKE;O*r* zY&g}_^%BT5dIPUO(}uDVz=x+GZ!9ip#kT1+*eatJ zr4nfBm#vVn?O}v0k-q$!Qd=3+IAY;P`rD3?-*S0le`1QWc@|TuMwS!~w}~=_TaPh0 zps@@=v8U-C`0G-nbbaq;-1#7hN@Ui7^Fyt<9WEJNHIdZ7;PwQWQoCr{=9wNa{ctck z4E9RVA~YRdFJiPrh~?r;lBrD5m)N{| zZFWJc@(%eH17F6fQ33WUf9n@KA(%9!KQ2x88*s$8K#tT=pH4b{WWjufIfGZ)V)HY`)^?mb@^Zf9Je|E|OQ3!p)O7N5;$NvB{rnBgSfQr%JDg^-Ql!v|fB)c!B-lrt0*vD#AXidC_$Rd&Vg%&@12`Ie$ztEA@J-=Wd;^ z#xD77e0ph&$hZ$PaA?0AlW1HK5pBdE4{3UN15~H`8mK{Vf2{-Glg+Z^82*0WG#O0Z z)h8#Si_&4k_A>_CryzmX9GIlTKRC7}K>DYAuf+pzrEtKRtUUBNLHU)y>3+lu;N1QH zY;ECcT6a*aEx=vvQZg)>Fuxy0716E)7-DYE5rNlOTfa1dbW4 z3EEEoBQ$F-)MjwOw{F%tpOz{sM z+D<8j*s^;JNh+nDgAm!WHl6bvzn8gC;&wdk!IpW)Psb3~Mg!4vm5U)Px_4ZK#~QH+ z@b-@#f-5}f|4AHcjL_>tW+!Nid-k)A%8I$4!Dft!Lf#+9Ek!;2 z+y~_25nAB2K9iKn0k7oLlP|K1CT$r=P?xQOG5*{yQiZIQD|O{hfj^k!99eZbsM*`d zjxmZ^_T6bmQc@pIJVi8%n-#Rv_k_gY+726yBK%mgGy3p8eZGM#*?Oa2*PF#26Y3%F z*lb-YuVT|q|L?O@Yhp^kH@0Xd$v@}~p7U;43)lUR(=wM{c0<=vFKLH)mn{uSUg;Ld z#fC}68>a+7hy;L5vnhFso@Mgs^lkQk?GQOG@Hlxh;>p`ERnvFvdVH|t)a}9j?bh9J z-3>;AiK@p@yuT729C~Dw^SI2}Q^sp$?ftJ-&NHaVb>HFvg>3;*0fo@E3P?vGfE2ON zK?uDhQiM%!(xqDfDF!42P>S>tLJy&Yrm_VM3W1P>j);^Hij;tWz()!c zO*@i(g4>!<48FjjUGobE?Y@H_6GEsF25t8aD8DfS)WunFHoQD=VHN7(X{l#nql-jV zFG)L#(m(7#7mLUSI3Gsu?%1!Z4BIzNj-0+GD6IRh8dnbo-2}Nq#&6OQ`_6#;V@_9p zWO_OeUQvVy-m;BBt+i!%sT2+p>i;SF_{3#I)SA;RWyw0S6J;XqE$fSsxuSu7ip$|O zq9QTnn?zI69$fCwr%FXiqq(x=bW}$IcxVhxxR1`9+6^aPk65}>rJq^+-xM!_Rc~AU ztUg#Cq44Y5Q{D&Pam-}LuD=~G2 zntOx63ze6-cvr<}3W1NA)2o(+M1jjTaI?LJjdwmTzR#0|yKabRu^BrSUvkTw!_-P} zQ=v3v?u(=2iG6S(7SQb^4L2{JmJvcNmsZe;mt6UFO46h@mbtN^n6rB47Kf~mj@;*k zxq*U=d%XdWYW`VkxuVM&vsXXWOx??jd=t{6%c6e@5hztFz*RoHL-eEamN@81UhS-y#H!Gf>j_Ali0G?0VZ#WOqw!{ftVtn=+ zibg;wCUbu{7y+O>AR-LNz2swAJ|LNkz2;j2WSY`PGbQzQB&5@si&({oyTZs{IoQXx z$F<7SaFxDRv9R97Z}&R%g?BOqg0Y>+@QQ%uw)|SnQ;8Opt(c!I$A`Z0Yhqc$JD#$6 z?>JOnIOrT>G$C}%!74^+K5ezVy#7YO{*U_|(s)~^bFzS+MJ)I`SN88sM!dK56ZuO{ z?tf+)kX%;+FS~37_Kig74I~P9c&%0eYS%bxKWxwMnVi3H=Ooqrelu=R$~!u)*o_^p z_>O;}tuTl@9HYW<+WzMT%g6p9a%Rno6eU`-aOdL_PO-&jV>Mxk9`BVApvRW)Nu7~< zp_KQHE`Ow$A{I@)Ji-)9Kj&|X<1UGE(nbeNpNXt*l-=}tsHpm;8zCI(!PWd{0Y$%e*y*Q$*Sun6av+o03Kk=v(kAX=w`ZIRKs9|Z1-%tQ^BZ)~)*)zAa!zkWkK(nB#{ z{>H_Zng4)lj~RY@clmHLG%{o;)UkLKwEu^$Qcz-iCUj5CR>i+rztfN;KGO#&_=#C1 zzZ9bl&t-1z7^^2U`si&^5P0BsAefk{-{AX6XtSxGgd)jU9^*_y z-B8y{=;GwEYtw)?E|q(PEwH!$bgbW+eIzL{ts;^P9ju*XBIrgL)5oPRH_tn_*Lf8g zaj}X8KSG}Gftq`*f!o)j`D7l&Hw02QV4D@8_UC(y%xn5~2)eeeuYRPJro73*pl*(s z4(TFzfKh$Ay~Ngc0Rc^U^#&U(v$-RO@)Ned<2S>MElTk{2ZEEC90p!xDWtnIndHRb9_$BxTvpnS$m;x#w6O-LcjX=Arc zYc2A_)6S#U0lxJqG5&(rQ{=(Vh86U52IT!N4+uo$;d$c%z?4M7H8sSMPY6so051lw zi!@lFw2f@aF@qle55(L5SK{J}bVQD5f0WtAk&nj$b&F{W8p$<)tkSfOrWU{DKmTuTU1x;?pwqCtDa5t zbl3R>IXdZ9gJoXvuY4=PdP{RXAhwWKTuGMF7^w^yZ;K1E2WX#U;&4t_vROoPpyG09 z``SCx$XyEGXvO`K4v58uU`|2GmbH+v>Yn3PPqObjT7>0OutF1DYV>17$9Sn`7 z;FCs8B5^LU9p!D_7RklmH0bQkk_`v9R1H%qD(wGq<;}pAuckxX``}Q|TJ&t}jp~Wg z$be2+V1xrUdZXMc->-^amHYxb26>hn7JeXg(=2qX42uNhSTMoGeJvbas$)ThQoBM@ zs6z=&K)eJAO~EKQ*Q04HH>x!L^`j2d*XuKwF19nmT*C3i4na%KRRGvp30SS~uhagR zB)5y`UAK}AxEJ&KwXoiz0>k19{71}r$+4?$>qW_7HM%{2zHT|qpYmFD{sAJsA*XA> z=X|V{FQPRd)ulhAgYr})Y@vXbs02ZOrG_W^nZ%I4=)PjsM~wES3SG9Jz@ATy}!aC9}YOz!MH%Jw6|P#(0U$0qE!4Jt42>)VFd(Ig)DC*9$Mu zXp(4M*A%!X01Hdrb;9bB=5U|%N7AMB2qQ;c;wSwbg3GH>-*}(>35Zs%b=eT`(uAMp zDMO;gc3&XhLB%Eohs`r~lob*2#XHOtRnLwq$y}q77U=arUCJ zDRpX#FBUdN5mwV9&8Zju*-;?z$$I`#v^Zao`ME`fgOmGF9#1`GJ=>@9ES`!^$lGeD z81PQrm|XoY+tg2fM%gW=)Q&fQ=wg}2!9J}%={@Uyuu9q`_X9M`Zd zY!E>#y>PcxJ>=`h=|t0xt&(aJw6N`3txx;qJCn-UC_LcSif5zf+*MDM)J#@K)=hJE z4cp*XynXiZ))?*48^0_uV-&>0l2w_*{W2{ z3*W}L<4dz=A21@LeYgbgqm=}63q-oNQ>8;cBoAHlgt7mp@mOcB`{dHd?)pxrWYJrg zlQCYq^$bckwM$_%@P(s7*zE>XW@*(}j@CSf=Tto}?8mQ--9X@%?JdZLg=%G;;x3%j zptp{j#BD{bB`c1NZ@JhAQLnXiX)iocKJwQx8Q(E{oA1T~AkC4bruJ_D^R=6Cq3om# zw(JYGk_m)JB0jqQX{;F4FVLQLMr79q8mB)i-T4(9_YgWK0X21468o^nM>xH1;WmsC zcCIER+IIgyRSn(L5#ISIJ8XPE?Ij&jp53IsE!(7)DeNpcOEOf!oM0T#Kmit!Q+Y{{ zsxbO0K}sRRS#n0Vy4X0jo3~eFHnL{oxs)?^a#Rh3w9(w;OtR#bxa*gxYNY&9I4C>6 z`JuQg)ngeWw)f3hdc-Rt#gsPJt|B9TO^RY}7d{`<@gpbE+h@MFGR2ceeWKgrbBrEX z{y`z@HX?O!idxi!7~~Xkfo0o)La=34y@oxqp#0RcU|CV+=a~08v1Tn2C@wCSlMdP~ zy*L?0yuOC^V)vm<;fTDeM4fLu{YPyv_Ue{OoC(62We0he>%2F*+~|+;U&6C4KK`LH zdV_De2&r7dpUxu}_YTI_Qpj%;>bRRqQ5;>U?48Vk(Hn-$~+iRo4>(A+~F=9dYEn~JN}mjLqH zN}m;j7^xSe3SGNAP4>mRd<8%;4uX3>`F(@MqR~bV^@|%PtsK<^Da7vpwXY<<{ zy)?An9TIMz(ve*e5j5!BY3J4<@%e8*BQ*0eVm95S9Nt`P_PXK*3m1s!e-=E(Tm#zS zCBbRuybUJ`ut;XrKm~Ki%klJ4M$vb2K)omf8#j1D#11I^BUtWI$?T`>lqoLerxz7$TER0qr{9*rYG#=Nvb`I%?YWU&vp()cpxQ22u0|vUi0sNyZ zek90Df|Y{zqA$efZ$ZB4z@r!_#cMSKUumB8m>4rix8W*$64F*;6Jiw7w!)BCSRT*< zQw<5fs53B&iSKEr>;J9l4{pJj{GBbi)0Iw~7*k`?Ms`dT6d?<>ZWcCDeTKc7;%l`D*XhW}!-kW=E_ykCd6Gx%#BYGknqPk$|`#f~>hm;uJ! zV3f+N?_im%dOlQk=@0y3t}cbIe_so|7r@1()VGFD=pv)V4AH*(cYI&D=qXiBtg4|= z`y=a%a;8Uf=vuwCz~K^8jiLo*`hwW@Od`F~2AtFM%}z!!atZg8Bo{k2{Up z+m+mR-Un4JfhovFwQ_1F0q`K{^8v0_tgV&XK9v3FC`0&}+|-aqIp3ZGa&^qX;!=S2 zo|4qy;eHezDGekB^wu~M0HY{-pmc z3#vVoZe`q1i2u_vIw`Od1?@UvT+U!fH5t#Spg^GfcSMpd)G2o}YZpk#3h0|oYDo3?F$)iNdv3cdK|1^S}u6oV%QNYuWy~<^d<$C@eD9z+?7lv%O(o2FJ2ffwubbB$=8PQpA37{btOp-eDa|{)iwg;!jDdA$k zl06oYC6niXr@;W0jHxKRV4sBBUD3=Y&moaJ8^*Y`Z>+bRfJ*O+xx?3Joi+aB(bqpv zkzAOSG5T`)?#Ul%5g7Yz}jI z1#((|1&#+HQYpcYxeH7v!L#@f&D0eGDO2i8xg!7(Rt>Pxn~sS!z_)3-%v7;{coN|T zs#1mMODL6eNzC~h#GAL6%OI9)DxLty_V}a??EszQQF$Dc|Hk8lzu@5OfWZ(OyV2UI z-k01Mjs!vS@vcR}lT%gd5DLC&;7j91^qd^^5IA88OE~4uCWm56-?~jIdY=P7C}Kck z&|4Y~(%TRBd6W-aWiVo!57lrR)ihSn+t}Kunn&glxfW3a)*XKLW-YX;r}Qb_^PETE;OD3N+#w$#;nZP8$K~9 z%sdxFatQ-l%=Bm@#{_kh1!t!(r6f*jjm-nX^cqh%_f3g#0oxH)ZBHhLB0~m5pKKUd zS5ZwJAP~zfmzhB2SIqF(l(66jCfqo3jA6ctw=ATh@tsM~i4;JM&#U<5Ds=qhi}D&p z1vT4GU3gKaAc?pVOg7IkVBh}n(B)Q7XaaXr);S)(OQSAsDES){+A}A$rn+6Apa%pw z{uhy7_H7R%Cq*$boWI448&G^+R^@T?KKoI-&7>9t)i_3S`(yD=-9cXvp4Hw%k|bV$e2DJ=*rjl|L&yENay z`+j%s+_`t&?_-96Ju~~9=bY#Ni+_YEDM;gCQ(~i_py0`X-l?FVpfUo#M40Hnm5;N9 z+Q1)FM-^!al=2bkE#SjL)3@?(QBW#laIWn-g+?eSY{oM0 z-hOn`*<0|`{OwZs;KJ!+`i6p!B``_tl%HTSbT36bj3<=fWsD6ZEN1Xy z3{=9yVq^%k0MTPHe3X1fcZI_c9G{g%2Z2Bu8q5+u5rHH^vsX7ZLZr~5qNIjQs;e}E zt?n;vZOxdH-!DKe%g&xI+J$+)O#0ttx+Y<;17B7gnxm7GRq4CKXXPon1(lU!<<|wH z!je0~YAh@)yLFXtE9l&j+oglQe?U=5*`Di?CFD!MNMFiIiF(o}Gz-Lhqww|N)2iyK zq)#o_I5>vz;v{XBhIf{hB~?wjHzI1j&S*symO1<}1stXq$MA{CNrXpq#{G(FWtu%T z6u%oCE~>ivb_d0E2)y@LU%iUFJ5sci0fp8>Fh0!WypJHKAo~PgH|&dqJG}L)+JDrd1g|WWdyTjApYimYY9ECHE8^^yogHUQ}YmY8Ad~*tagwoxuF+W5{f1sqI zBG+rny*sO!m`HNcU#IA1ab^)sD3|oG+WYVn*@ngR?9{){$Pl?4K;~~%gaYC_$1ygZxuAo zOi4+238Q!-_TfLXB?E(kj&kY5qUsA$obP~#0*-U%jCU@1uCD&@&P(-GIaf*piFWECKes>E>9BjrZSnC1tOB{f+PcS}C2N5_Ha12r>XbS) zr3P#}RcPbw;@!=GfZHKGCnqNf8JViK_E4B@>vFpvnvt<_HmqRGqdG~ISs^|tsVA`T zh0DQhyfvNeGa*(g0s;bHg_rE?`8hfMM~@#reDp{~Q*)p@ktCW%L`u#I_u|4mlA1IA z!=pz$e0(b#8-0=lBHddV)BvCk4-fUm*ZR$Peq5ZvwjR=gmG%_`=c9@iPa8Ni2K`s>#r*e9ulW}P4 z0Z07tquMd}&I{~Po%ZdUIDkg?)2pS`arof%@(r@6q{JAAI+um{qNNM`w{QKZ1*e_4 zR8>?#a-g*Z*F}Bg{=$?^D?If@OiDokSr``9?&8gJ=xq~Di)d7EFZ%8E#YPQyxWRH^ z`ucJQc!{Cm;n-f@-f6n^(UldL-{*9;Dw25}!B(aOq7?Ki%V-d-sQ=^%h0BCv5} zWMzA;_VrG7Du@MH5+^1|c8(6u&Yr(~+22I>)i_3MB(5uD8gazO!_GdG%Gt8CyqzbL zfQgCu2Y4d;ZECU8!>5##ROiFKt;2q37%y5qlETBoMJvjHdqmR;bpHHS&R^qTmYkYs zrVfcu?>x6(ojLBM0?wWjcm&Y`x=s8)eaY;GuyT(3yX>rvK!j3&cgxpyD+mzKP#QN` zZc!Y7oCjzDKLjGb^Ad>8eMl?L=o~Weuy1436|?XJ>abWo>Sn zHMclVEib2KP^YKM1qKG9*j-P&=(CGYI& zI~u(^=4fuV&pqSN(}h17u*@kfjm&2v7V}=G0C2K9i}+sQXywU{gB$EKY7B65ZeL)f zm%AW`?Y!Fu#b%BXh#?&v9X2jH5iya*$u1?_2K}9!)hlIXWra4|90P;Gii#HFb1FKr z4?3-=O{3EKW28I0_GPD(Y;0u!_9tM-NPG(mi#G`gXzfw6ni3NJ`Y20%BMoKZ+t%g1 z$9YM>CPE`q+o1=|T|0NT2NM$$FTl8==D+jpzyDCu5MFZ4#CXmoX)3~SHZ#Pi}O`M_uNN!b8bFWrn|HOw)>WpNTQXs0etI+ zOUD(ySrCKSwp`i)H>l#frr;w zK1ZsjTO4UrUSA(wTx?<4xOY@WicZ{B9adLgAUyTtJ_>vn zYzsm@13}j23p;Yn&k!xgiUhd&yp(Ze(%AGTQvrV0V&JrA z#MroCCMqh*r?o^=uo0W+$or&6y&^uIE3PLZ<(JC|6Ti*G zeT=>jdM}qQoEC&bMnowXet9V<`{6^`+_smOyV`aJ1P1N5A3q3P zUD>{W{~k%j4;f71Af}*@QPz)hzSX}RqTs2ov72RzedP#BU{*9)yuL77sPU@vhC1U* zmN*+CN@reaH^e4Rd}P;Y__ZNC4)9H3c~SQH_9uX}xNPR#T5k|q?4%@zEp%R4?<1aY z3R>~d-64Hd)zqXM9f@OvuQAQd&DYi;9bF3n+y#n`E!To4WbArlIqzL6_yOm$^}E3F z^ygz}Oc_AVZivQ>&kc4A2?cV?(=c7f+j)U>Fu1HQQNhf{rf!5`>&ydqI9nKP)9mNE zG+s|WXXjcTUfu;R`lVw0WIDFc|FF!qjOcIu7A(Fd-DSfW^{d_1 zI%@AcAEAwgrlwgBXh-wK2?Y&b`d*faW{kcqOikO1dQ=oi4FpakHjS}}OLne=C!3h_ zO5WKGkL|5jNImbpAQn5XddB#~xSwoj(IK4_@DrHiS1S9Gx!eax@aY6Pi-?)a9Mzaq zi3Kz96N>r;)k|OnoSq0PsH2JJ&5NbJsHmvCJUOEd|8m0x8$O{7X$PK=wbKGCz#ML# znO`pll($X$+O~LeIagHPI34QnOv3{An8SQW^&`mNM2i|b3dRPr&c%GT7C5mlA=gGD z1tBj5>TW(SCFjnmO@JQAU}jTBKiH~cstqDsu}K}yY-l*85%Pust{lbE+`Iz@D;QOZ zX8;o#fG+ygr?DCKAGILCfrKju;(xyjcyY|hSaBkW!r=et;mwE=;^Dm@hCr5|nX?k} zT~d+c9cMeRq#ooeimH2i|G%wLurGP+OON7Mkj7^Su9C&%Y!@8PbRrirt8lpXp|U|T z2;k-&lCEN<5kF{$jZ%M7!(l*AjR5077$9ca+SI}0b~ub7l6CDx_2qONf#kyLF5GNM zYz>zUEG5ArA%1Jy?sqgK47ZCzdnPU(WpRmOu@urq~UieWu;s185vM7j1Fq9AzfNpF4O#vC2&0hh(W|%V--00+rw7n znV-}UCKxlpZ`4dmb72aD^%#vQ4#}n4Nne^AzXU;H{&)? z(YaUh&!!Ha+rp=L+k$+&UIdr%=_i~pLGkSKG>D4!UkEXV1(G`f`<2(eg72M0C%Uwr zat2THSQ32SpJuW=Fn#agS|OEP&v$v0J@JW@1&qz`(SqWURkZM?(rsRkRvg_Ev%P zHnQtr0&`==w)$t55G-(8Ay%DMu#buXWB5~0CY z=X9a1*Dk+w;O94d^EQ!AEI=B*%`^X?e>qnEpLfons_NnBj0{#-#WRi~H29V#^os}f zN5%7#hGWDspOGUu$R@%!YA%l%wQx!APCiN^)UUvPG6yTNee(V4>%?F!dI9RHMuls? zzdI5Xn%yjYne&@fNrQJ609+JQK?BIzjWTJh|Ev_hqD4@n0|fzG(tjxf{CA$nz7^NJ zlpPJPn@TbbBJ7H5LX&BWMnVFux3)?_il#mJEWNT%u{f>ptm%X9bZeFeI(^X0St=i}mRax77fw za*S(2>k>PwxoOA*1JQzSA#G%eBtivayWz*C5%kd0iJi6R>f2fy`Q10QkEHG}L=njR-140?lJwep$T9Q@< za@8RfAcUR0UolkLI&(Qq^T#!FO6_{_`K*SSU8;;jKlP2tC+mP+k7%?U zRERz9K>esQto+3FQR=XdT3*F+*GdP{vSj>#M2dO6< zv5!a(+No6?Fc9k6W3~E33qpHK@j_yrYQImMau(1`iPL|`8}cpvo?ZYhEUY_Zh%Gl7H#h5sOkA_lFTL9|~p zVU=Blr9=|AuenX+(xohTCkKkPaO)YsUE&@Sj*ouVIHH`xwoMZ!B|=fxi|wENeEu-1 zq*wn1UcI=(8zawl>*9ZaS8%R~Pd#Vn^phH%Cq7!gLT zhL0cqJ7WK(##bmDn_S7souF7MHeg(Gkm2Kve&EdE+HyK62Yn9pG@R4H1( zXAb7ZloT*I?SLhQD4Nx7aPMYeio*)*^(oa$=h7@mbAoT%d^jtEtz6=}v@DxK?6<&g zD63Ly`BodI8Qu6rGB@hhVylhiU7=1oLQe*s-Hf1ZX@HL$&!dc&=(-X{0#gYM=y9#+ z1^%p~3*g;_EYR!?6#H~nRzd5JCB_AGJk6!mkH*!TJUKbISn0nKg6%W(Vc@mTzB_5; zzWOF8s2y=4c(#xK2a>0;YL7p>FuWRk&CvaoVqAQDrTmqH|8O26egB&VKv%DdR%-W; zt&_dt zR8)4sK6>FBQW8}!6vNnk!z={`RSp^(S7u)A3)NzbXUbdPFzk~_QiArZec^c;ogmCH zC<7i~**0VTT_QvLCT3@H?qicnJ&MX?XkbNuRG&W6M(l6AJ_WA5WoJm`i5e)@A6<@$ zLkVLi5I1G+#n1g~Gl4RTIc3r0N7fP>&i^iplvQkFV7tj0)yY4__t5%*&;60t;$`u% zlG=hn1KtcZ`glI`j+;k0*4Op|AcF*;mG2yYl}+=_-kJP=fSRmmsqzGP`^s8E+C_HH zr|aeap^9{)K0-Kap;g{ntWp<5vpT`FgT0H;v3i*EoS}j(ry`l8;9_!CKZfofeC>V{{hoKzm;(D=`s(`<`x`#m ziWX~WjL`7b_x~=jZz!k5$LlzaGccIje!XDPE*kxPydzmI=bo1|6F#f62gnUH967Z^ zN(u;@(T^$3((47ELQ|+W?1vQv6fiEH7m2JE8fYtSiZyh=w2ra zqMxS<8say{VtQ1q^?<;ku5@crG!qsbbQ~%VhU|#}8=4ly3?g(j)->tyV^77VkE{hbUb84TE3B) zpc4{Oo0o0P_y8^(MdGOyAF;%Ik{@0kA6r4VmiuM_O{Dr1Du>8)_{MYBMK!fIH34{9 zCt;bwB$9c?Rzuoy@cjM$)3S>JbRZnKe)t6jZ`57=Dy}d@j?TIbw3RqD`(oJMRz>SF zHN;(n5oi4v<0UYfdp7=0A@%SOxfEc;IIRhIGhRB!W;i4F#`Q&?rX`idhm=wOlaQ^V zN2kkyN*%TPodC40EQ1IG%)X*w{XAQXi@MFg;ybdv8*^S#M(mto7tMbo z5E9FE2s1ViNFa+JInqQbS#2P4Usqe&Y$ar2sA`x(^+o0Ctm?7r%9q)|-JG!|3mdYc z>y2zK_74SYtL6y%2KM>PK?E|P*(--awS$YSwYOI~cM5yoD@@`M)`C=P2vmE5RPaPE ze~MRPkIO#vVB!hT=XrV$fT$~$SeI7DE?$O--BxS>|4lM~+eQDQsr}PA*D+NhZyWjWa}NJbkmZ zcV(|WIygHUBQgZKpLaXvuA{TDu?;ZQV`Ae!ef>K2?sk=~A9(unz<2%u{sO+Y)IdX{ zp|SC3EQId9u>cgL{^b6SOHBl6Xb1p>uPJZ2&Hh!gf`r6*!ehnfr*x`Nkno>S5 zFQ4nF-oq3Q-KT;~!Q0ywqL+t~kxaz>#0f`s$h;qi;b*l;$;rpVN2@^VkITb}d`|2l z@a78X*wRd$sUYq2kpvEhrw*H#jHXGBO_q?5J$Wpk|3IkDcH{hLHIkNC%1WF1eNa#& zg%BcnmD8XblUAcDa66r{&UR@e8x5nu?zZ_%02B2O93C`g_VLM5bp~zK#ikcXL3X8| z?lu-?G)88XEae^1P&6<_zuK9~sgDwg zM;8IKK;El+NIaxxWK0ajdaOMJ{ka@~QB+vEw$@L)28Tc9WbluTRe~abZIM$&HF!46 zB_Wyl`BRdo)K-XyLSth3U9iQw;L<^UMcsu5lFp_$URn6gKUEX0dW2=a2>=QC# zBg=HV#(e5Ht=i-W_^?4XDmJ?Ee5t2ra#)j$oE&6lhWS1P=Vg=j&(OGMSlIaa&CMCX zy;4t^-=V(d^P?h>?sKHCa$wnqL^i`9=7H5PD<+6gb`5Xr}hw_o&5AkyU`)7yZar) z!-6-C_~{;kq1$fFiQd^jY%=fnR;qyKlM=|KewJ-=Q?3_lk8|UKU zmqC`vNtel%O808#5lnhk)`&7X@9wD>(UrsOfK;K`GUvFsxQ50CWlc@l{#qh!9!e&; zRB=yFp`IRfENtwjJdA-qIQ0{s=;js`^*8P90bHPW!{OV%D^M-qvW6)zqcOmAGcY{d zW5u_+ylf5=IzB#L>z^!fN0^E|qt|1Uj-maY-AW5QNM>f{wKdY__IN{Vb$fjP*>*?% z+f=UJorEEnt^iG@s|-hK(aJpgaGZ1idN+XlURRgw@BhHd%lq)`NLg*p+}tKVrz$>t zKo%Ni6eT73&WKTGQAhYP6D#x_Fv$N|VJv_{2 zb+h?wak@DTx#%5o-CQ1ad8^&(l_utOh^ihD9vY$8P?lXqh@=DpEV;2u4rsAy)!V>- z#&zK{H#!T_5a;B)=Hnf63n8{+V90)m5psA|Y1mxp&TVV8I0TgL50M0Vr<-GmDJiN; zN{SK&PXT&ZDM55`eYJ#@+rwoU0B(@W6pZ>6OPH^aDdpuQ{Qf;cvIJ<6;O~lk9g*(a z6>!`|a8G#MTpykyI_JmUN=t`SX@=^rA|4bG56FI)X>`PZwx0XX&T1Ot2qg>TZTKLl z1#LO->4c4Fq-I9auFsjpI!ZiWz*17D$x*_wBT9|cBO*A#!|&cnxe&~S+ryfawY`!M z2uL>My{Rd)Cvrc#T~BZJ)LgjHgI6_Qez~*Jad)9wdM0BB4$lyBjRv}6Ik`>i1?UmS zZb0=v+7#lMtSfvcJbHav;O6P>c>ZJckZeU~eZm`5US1Bc_W0CPbcbGS+qgtww;hzA z|Nm-U-W(NBsO_eY|L;ZKp98f(Y1as@2@Gxg^ad)zoS^?a%kgh|*ZXaO+#Kq_);>I? z24vp%4P{l;-)umGq}Isi@+DJ3*aXI@9GH(ZbA+n8F`<(+$m{kZ3lj@Vf3f)@5}PWh zBmxI0oij(%Z8#y1Wm(nB44gj0X01#dF;Z|Jd7VwF1A_~1IZ76r8k>%R8H^o^sweJb z9936?9BHE7g2bvSV_VIeufZ>0vM<|0T@AU?Y&6p9JIsvk7F)C%=c^mqGfVBDhJE;e zn(2Ad(CpEyZEREm+L)7*>mg#Y+S%8SU5_kUb}?LwE!75RCOvYBPZW*=TB>?KM@^0X zfB-f*-!5r8>dCt61X|)Yi_QI!6x`<{r(4==EGs1y2qmvt^2`|i$w^+XyUM=9ad&&Y zB~gXU%O}2KzD%OA;gywf#l=oxkEv0J`x&{t_UQP+(VE7+cXgD+#E$j7)!ENQmLI*vA5O6+z*pbcf?){wavahu@8*H#*b$dG;tTnoN)|WSJ-bqBYPoM7|SPh_3SY-%tJcVuMs6w5h-3N_aL~Bl#iP)goWo{HmiV43vNIT zYd_;sBc-7G=Bld1pGcG3JU!(a{k}OC#ki8L*(~aAR*0q@}K|Zm_yy@9db{&|s;-4ChQBwdt{2 zaiU-7AE<5F+U5DZ=tGR+C@rMBf602!M0GJ@y*-}-(D8s&bvT31aq88r`Hcq=5z+Jw zJVm?7i{et_3}N#**Ngh zz!+aB)wbjWq|l@~Ej*mXm=uMVm;bfvVpG*CaX^*@>F59>Xv}tkKrp@G2?Rz3EJxR- z$8xRanv&|k)^c)tlmh~qXKF=_wMkP`V1PwNY(A5T+E7!Hk=aa4h$B^&@6Mo0agOUe zkK-OkQZAg#XNz&%7`T1-AAxaWfERn2d~EnqbsGxNLa1 z$CDBsS0}r*U;4!*#cAB8?4tO|*mQDLon;L$fwPz3hzJ~DVg{IPQC3syg@|~bBPNP` z0$#j$6PlN2lrNVln=Xo%IRakQrQ7HZBc8h4-2A+T#xz7lF={_30Wxw;>X=#6H`<7lQdE3 zR1Yy85mS+cgcJxplSX&A-dRnR`;rB!sjVlb%M#b}q=F3&0`nm`#l_*{<0_jvE_3ob zQpep!w4e{qxSqzB^r&B0AZ;|Yv;Yre0Kk6jOEc)h2OPXJXMomM;d}3wlOQp-Y!%Me z=a|PQ$2Mt(vXD2AzdCM=%p_^(gp@5tQ{(O(s{+oc!UUvDHqPXJ==-bM;w(KYzlZA>k$Q0439e8*U8uI_lAVohE1YpA#1%qLnmmS9aYSYXWph zprbCDCwBA@7}q-LuZuCawKeVV4+5NUJ7RAxjmMn}3+t$%(R|Fw#o_!=Up72qPp!n- z+I$@t`iR7&-m-hH$uMEkut2^w;cBt8H&6@62T@im$WH0?#%R_vrvH!x6c)z5kDz4& zn03C|RG`6@PAWGH=%PzKjQsYkUEE88NM4>Pyh6v=>ilzjd;%css-HiD994-`^A&og zjdH4U0oG=YbKcgvt#M@Ti>ApfEybnFjPA~Rr{Lx2Kgz`|;Ji)3l+eF_fv6=aBedY2 zZ&jV!VweN4Ug8WJ$NC7Hzw9~rr&}i`B9iS6#AILpvUb=J08CCf9Is&kzA!YE&`)hg zK~fS8AXgMXm68D^1%xwKI%wtKAf}>%Du}*cnu<8s9rH<2FyxzY8B+>nDRpmjJHVtxc&Y8NGh@D!c;R#%57kH zvwE)A1=ER-KLv0?28M<&U%a4y`qUq2^|x|+3mW!CFHgtiW{74=Dk%{XKV??>{MoEN zQc=lH(cRDxZA^KLoQf(eG;-~_vAV%bo1E8!$J&}73-hmO2Z(YcQzW9K1P4eC8J{z& z+P6?UhRoRfbP;3@0rT6=vdtx?$4lrdGh88d8}}BI2qm{#X{r{62Wo*I5)zU>t2+U< zuC|N^EAMYvp9Q2-@q-i~p(!b3Oor;V?;0KU{^^Z7&W?8g9(YVjELqT)umgt`6&JSy z@)H`0X*1uTFY=a#hK5?mBhq?KG`q`+>7~GoO`EJM6o`wJ{WmnAIvZEGW~M?PFwz$( zFV8cZ=MJ1V27*p}Bi9f}u(hEsQLO6NVKFhL+Y{!2!GW)xw|Q1pS6{NQPV=9KgyxzXHL`z6pG` zKY>}F&(-d*{cUk^acEc=>K~Q2s8YF)6%`eUh>07ORV5V^@NaK#^F(81i*-f|$Fc$O zf}mX?wNeK0v9S%T++fUlzyQqn__)MJCmskbZHBNjQSKN>bCwbExzT@%+k}XiN>W3E zA_Skddto6Hzz2}{aeDXS=8vZO%~<8v)=q8#k)rgsJ-IE)sL`l~=}dV)FhB}8RNy~9 wCuzrfuS)*^Lkc*DQ+asGW^;8u*`qz&&ViWUS+VWY^rS9n)0q38d90N6F#ga7~l literal 0 HcmV?d00001 diff --git a/code/datums/ai/learn_ai_images/decorator_example.png b/code/datums/ai/learn_ai_images/decorator_example.png new file mode 100644 index 0000000000000000000000000000000000000000..ef940acb08d170e0fcdcd8bb2f3b3b4603a71a88 GIT binary patch literal 27708 zcmeFZby!qU`!+g+fOIo73`k0YbSOx7ONS^R-5pXxgOt+UA>G~GpmZaHk`5i;=6!$p zecyZHI@dXWojb0ha(VtaM1I^1E{s$bE5pJ4DNU_bx)@QDO)KDflUhBCZZ}qX3b_##`ERlzZi61BE z-sUf)CGIqfsqpPJEWWh0BY2T&od#B#>*KYQ&4X}lea5wT&N9S+8JT@@yPYoRJmEfJ z!`X%Dw^pUhb1u4GJ4Py!b?rBKnSP4?oO1dZ1OidV#K(X_p?2<=GQc;4-gqcfw#$JY z0>S5`2!}$E|NqPXU&+KHBsk_5K1TRT|3-HZr^l2DCfUw2j;hxuWIG{KajJ@G$t>FS zao#&I*{a*#08GL+w`^};W7?ra-I*c@4jSZq6)PArU`AZ?>f(DwJe?f^Z z)Y`)N{sdPlDyFOc6Cd&Dix$f7yRvqS0s`P+cjbO0oUy>FSe#I7_&^-3B&Wv-4d&U| z=M)m)HC`9oQaKX zKGDi;D}ipO}qJG$SLU$@9EWH;Ps=RM(a?b*0Ud z0Jvtx)z;qbsvl~->RSbxT3cf$HtmDrfrZ^(N=JbMfqS6|j(fk~r3@MN=hdTA-bd@v@bpx~J;|B~b30$a6E zyo|YkdM8-KgY(VjAcy1jOBRcdlbLTlJ(~-unzNdk1m@=Fb1Eyb<63h_<9MeyDM}`> zs6@~@#Kb@WT;>Bbw6q9C${Fg|4LhDKEiF?^OQFh}ZH)0nW&-M2XMj4Zs$%!JG3@Q_ zR}naO-r2dj<<@I?8#2ci)rk7U4E2X%!5f+$;<^Pm=^5y0n5}Ab6?fu+?R*TyrY!Y- z2l8wH#Iv#Hef-wIfP#UZ0U{uvTmELp@#&VMR4z(K$K*xsFCSYy&l&Xe^b!{n7?h_J z8PB9JWncvH=7VN)#cI&VNJ%d*A^kWcMMuZ#GZ6>`F(;?37SHolC>=cf#}DwXrUFxXFgtDOS@Dma)PpN$~#uT9EX?>4`>(|8b&&rnwEvLUD*!?baaDD?S@qG!rR6i z@Rk#(!>*^TZN=UlAr^(JtE;7*9m-CcNE8{LJg`izr$KL%W4^sAGQCdFj2N58o~ za*xx0q3mM6xw+YNJ|T<%TC}!4x!!L!BZ9C9rU2Q(-uyy6=$Q?gu0_ex;(xV+=~mh z#Kgqx%F2l&dsX~q9=9^}Dg{L->P}>TS*b`_mMTo!_RSIXT38DS-vMf;gpkuIs@8hx zTM`=$TDQ&&j8jU+`wbFV>O7WWUXDRCJ$v`f3vP2BRO9%MWWNvh8(+VqcOv58;Hayw z&n+pDBOU%FhdJ$TqOL1E5<`-s$4nXWoQI)%ANJfwV*%5IaEAJtWQ9aCYSsRM=uyCr zVlXzP*B$2DWM9fL(#ah3cOu~=$oLMdk?M3B=6rGo{D{i#;k(5xC4~-K!U}Rc`NL!cp12p#} zon0I|g9a2m)S9d1a@&I-rClccTNOtvxKg2WLS7u05>3;u>>%?QNOL~tc_37t0(=0b zpojvZ($&pPL;_RzIhAVqGrCpo8AXJ-NP#IS zBu6cW>|PU75wtQQK|ih##z2iESyzPs%EN^i zKuE#m?xuIL$f!_@OgZ156VVrLkA@a%@mnrO=h~JwfQ2xL4;J>T7#BGkZ#VhAhHB7+lW8)I3{}?otDOQM4_7!G zK5`y-D%lirRn+2Lf0D&yLl*Q`X0BpbP1c{D(wxOPX_T|nsZ-iQ0zcXMh$=3a`lS*g zI?lJ_P`a$3IotOwg7E#ahm~*BCbWipMI)WOZpOIl{YfBIrE`r`0e=}#u^Py{f9cM6xl7O+f zJu#6`{2o}@n&&>{oX4-` z=GQ?eR?>NHZf?0DY|>;j9t0qm2EYB9)H2qj^}DO9E0d#@=8;1{!|CGwYb7Sg0i~rZ zzz&!y#&RUXtdkGrq+=+;>*{!S_qGfT4XG}h@qYqtCFTw!SO8?nl!gLk^ZQ42Dz`hA z^PXaX!rYwM@5;Ts54$V$AizK%f`aeL_ZG~|&Er#30a*o*@_fj~kXVj(Gn$n}dXh~4 zxPIeQVN0U>z^cY8<_nb)*c~0+3nT9c z2ndpDUI(F?|KOxvX>nE1NEwenwz2|K>zq@-}ImV&%Iq`<(yCzJr7K1VoAtFfKrU?`q0{_9t0U!T;t zJ@3N80`2iAhvWT?tCDYSV8EvAeQ8-4hEAnX|98_~-bG9lWaK9y06>x#6D7!{Qk{_r z6^jfRNCs&*?X9yOo}Pld_CTISL@HR%03ghPW7+i1LWTYb9In+<*xpW=!fyCEB|1<( z#-@rdW~{6+>q|lc9yN8 z+^OG8F(}^Mnxg-9EwFoMk0WURI;QC zCMp(>+AociMwKRkN}dR()t1e;bfS#i8zh#Uy{F$TM!J zsja;w?JeAKf1&Slc3^sUURDK;a?v*t__Exrtr0|UeSNjOzCJk3(b08%{!T~(1}WW{CjDDa*9Bn-LrQ-ib+jP-1s#G-9{!p-svNa>c|qN> zJJR47DQ#XpKC2c-L&~_dwY8D+RD{2`$x=u_T{9E`U|>znum1jOzS|Bus>W2)39U!C z@rPU#;WkVy4zE4Mtml|g>Z!s2OxOIr;OesrDJotwYA9Vi*1FGm^qJ4EsT}rCqNVm8 zPXc~!3W50p1tU2CU}LH}Zsa{FNlAZhFXVN0b{5WmO}Q-iAcO}O2S!tdiQl3|N#~t; ztu#5VV#@;Y;i+`-G}O}raBl1;7_Qo*xd8spC)|s3;aA*7CFEk+h^US^K+bPxavXNc z>k1tbJsH@R!Y&3dvw{K!KK>TM^^qW*bX3<=j^y;h!iRIR{*4V2ATdhMCM^L{O?O}K zcVGV|(~BQ}yFuv$O?TMOa5o!{^-IKP1({%GH~|3~dW z5PxeTfG^1GGsiTRC9X327FJx<`9Ow>O(o^*94QbET|w)OFHU1nPKVAV5*C!rrvfTY zXMfojPQa+DtUh4V zD)uzoze=YTD+6Oxk$%_Y8(?izr+AEb z@u;T%ey@}iV(5Q9;z?!(OiWB@Act*fay@-d3_*xW|36v<-}V17-@msF48|50Q9C-? zo`d2U0WCJGTFZ0K9CeUyZnpVS{NvI;*Ye+=4=k}+7r?bkOCIhN5S0Pp9bDkHA5Q;k zFJdSJ0_njcC(pZcA;R-y0PG1PItJR+^g|aJm4_l+g!Er_F@{3ejhjD5rm*L9bi7WN zlA;Q3jHLs59YAsWUxoCKg~ekir3&C#xvxhc=7xHRKSl4`Tsf(`_AEEP8MguD0NolJ!GBZpKW$$lv=>(^9 zi-4}C#=g~l-;ohtNW>!LMh-&7k=7xZTJw|Ssp#myethV$=d+O7F#=Qr%bA+OL&L_N z76H06KoN`s1n)>IN=nKt2y|#1^8$XpOhb$xza0@Vfdpbu{C>Y2-DtPk17O+H&aB6z zr*Mg>57^?#!>zxhtu2<_S{t*nIwq9}c-iPSPZzKe=%(xrgrofq5;ef!;(CxZ;D4~Y zo>TlQ{XhN)6yzSK?&PMtufh{fE-x=R!Qmo63?2%x28!#0O%+~?w{Q2KCYVjci}&6R zpn#CDvIxa)WYhHZ_2mLxluo_Ja@`Q<3PqJOy;J-K9v#sJV>p%h{2xY zBQv$J3aKt20&&hQlY?-%ML;Ox)L>+ zCB4Jot=q-*YnFqq<`^hAWXQ%@ra(Vvy4up#O4{P*i)tW42L)2bXdh`Jf)AFSc>>^Hkv9F(=`7xe1ZJW37oXFj%H9LJ*sEh7-&tsD_71x!N*W+D_733 z1MBi5{DQqnxuFfqUJEJ~p4sP6&vfSpD=i5Nsrv1bE$tc~oSdEYj9p{&TT78`RV#X; z4T|eWBh^fv_o|^u=C0xMu~Sut?-{Q(>1kAbTca#=PGWFhbHxco4YP#dx`qjHk|s`^ zuQ7ns-pUpaA^G~xl@Pg zUq|5=3IcW2haOtCPqS+uG~}$UabC%#c(}N_ZKZ8L5|-gI^L`{rB8kaz{=0Q8*D6xB zYYBsVmlHT<@_)w-A0==deCm5bMBaGX+E~ph9+$Kn?irt<#YQbC(&01jlNR*m-l=BT z*E!eK`cu4^Y@V2wCV`y*R>s0WMK>VTyNC-q20{r@F4ZaND@+YW2BxY`_8V=U_M%WP zZ}XWZrd{}{J^J)dt3Mo>Lk;?r{EqiaL~{o0GOqnEz=wx@EFk^3SDuOjdbfo<40ee4 zAIIKEI52@Wq!oB7lZ*+=@Ziwy^?+L{4^SNHbb7lku*qs+f_VG50siO?Rg0F}ZW{-ML;y2~3R(H|UuYOgf zrb>wtfdwcYw--4OeFbPItgxH1B&M-j%*86LaIJivBurV1 zBmaqMc}_Q1gz1FiIb=FDd#g`8u?$-47voxUqc--R%sIa(Xd1QzXoOvw_Wb}$^)bL+ z@xD|*%fyZGqBhVW!NnIt!K*&qqR%y&2I`yvlyleU!uWzpvlE^y#;T69j1OdLDs!3! zZ?vqqdUDM;`@DDBU&eBos>i@P!KYB@xIB^ zbC*PccNXeZ`e6OyvrP1W$uX@{_$y4(=;Re)3fGSDukvl_O@(1YL=npV6(ams*G=W# z^BenR*Q8|_rEuKlo(k-P)D_w_NrZdJvZS8n6#cNgewlup*c-LspY{%aN_n^JiyQfS zB!l`{R`JDp+R`KUC$n$3TeV73EMszPM3=S}Hkr8nJr=4w>0wI7zHYLfRDkzy(s{X2 z&lR6=DwUm<`TOJo@|B-))OhfxlCpJLcc{DC(X8s1lk(lM>2^%Ny$L=OqMw@j6M#vL zmn*pFA#%yd!k#+$2_KHGKd2J9ee`G4w^yythMxcQ)Yv_{v<6YtwJJ0g&GCw2N7dfG z?``yg%czlvKT4KwlZ-}eG5>d-c`iHhm@S#YBlQ+LoGL41OY92S3WoaXp2Cs$wt z2;xmBDh<06FGF->Prf72-~E!g{!M_&F@9+Q0wVPBL_Ti6&T4XvY>de3$QQe1O2!xA zrno+cPqXZFM54BNB)IrRPqEb272!sXwRUbNL?g*`NAq)W|3dVpLL zWgar=K*-3kT|a{5%((Q@$Q+w@%5@dzK2;i)^bNNe%o(&JW@7+BzypVpL0i&XQj|Si zEQAjAV(a|gUuL!5x*A=r3JnuWQunWik2g#}$Zln7MMbOawCU=`ZMT7+ip1VAzb)Cn z#rkiP)JnZ&Ng=k!wxbh4ho2viWxF@JB^?eIl!}*+gr$C(FRm&x=fw8}<2;u+18@7U)N)`*N!bBVpM6{WkK2m+La+ zE51c$J$$ZJz-cSu1F1YI$b>~bGp<2Szfz^vNW#z9|BYkDoq9c$M7wRNZz1iNiq!oZ zia4bCF7gBMz1IR1adf7m)5%>WsX&7U@A3dEois+*73hl3*^H$LYZ-2Rjr%EEjV5gs z7bgTp=yHek&Iom|Nk^Bwbj$U`oxbw$Osm|6yLF=3RhJG+$w&-}E zQPTfQ+%|B9rL60KfA`OIn9pHNv4_+KVP)cIBpPO52?7b1GIs5NxjT4sR z?aQmJ#5-xtz>43o{5Ye504zS$QN3F`KJ8~dATOCSyA#Fk7}lnHDp>N^qKj1N-E(q} zC@NFma4=~$^=%8W@20h=R&AWU(2zy$+2|`)dL-09L#!wg`x5j5G0$9ytwmNbp5vCY zaVy`|V3fVE<4cTYY+gg31`1 z#iPl72O=(BvoJ6>R(+{zGopFWo#wny0&h>TH7(y_+LsRki0N0N2I~?UXiLW^dl>WaQ0Pz|7X>9Tf@IuXT{U*$)$BI4 zO~k~ZWhn{6lB$N?O;XqGOcbRnJ^Ssh2ELaAo#YesdY$+T0Q=M`*hxGH8x+ z059hj4DBNv_temEFFA2|zX9M{pbA~WPqOa#>U$@;$F34-HzM1o#8Y2g2vH+dH9t1U z+SoF7)Dv_UAYz_{)UDP9YQz^)VG?rdSJG*q>`6fuTw+dg>ALxdmqqNlIB zV@iW0WfuxL-eq3i4`M`4sdN;MSpl74fo~Bw|&{i&aQWw2IdvQ^WS52BBTd>6)>hwRAI8 zcyY!Ya}~5w=0dE!aCERsWb#ty(-WoV5Y@}h^OY$f5S89E5GJn>KM_stGPynChDfM6 zM{V0-)Hgc!3da}+ck1g2p=e$UZY5QxryuVVk#_QZb_n*Y6P@xQx#hSs@p%G~7-J36)fDO;8; zY{gY$PcMNjCet-1g~!_U?HNxHMH-e9de6?pB`1g0)m04P7)AwEeE7qCZN zKp2|fOgUytK%D-o$5A)OZ6pe9dyAWl6=hUe`hILsd;V`7FEf}jXDu1%V587A6NS>P z?m5M|cWcK9vLabMmzYk=%9)FU^18Rp@vcKWl7stTJI4=qps_m0nez z;efOV_ppfV5y$ws?OOUXXMH)ny#bm1k>77BoY+jzh+37x?Qk$*wPBitC*wr^1?;6!6EO)rM)22b!T{ie4GI0D-%w0?!ss0%6 z6Z!r92LKwvHkrr>A|&QEOPw}Sv=(x?I0?|;+> z=+MM6=DL=E1!JFnyH)%~_}JG>3JPrg_exR=m2JW|JfPhH#Sx$LrjRVqUH$9^8qGT8 zDZ++nS7dz#o8z4&W{(aWaMLizVG?;C^3(7t^8``u?fzjyGCOkZ_Ey_5fw%GI7yNl^ z_1A1?7|EA#DwhD!aK((V5B8!i*Z0%X^qAdTE*p$mINBEdy9=TuFgsaEhE%wp5NntHunewcoxjfBBm=vb$V{QGS#^BwYi0Q}G?mR*_QQN?!!g`k zY{2|`;%aooRa5Ny?_rht7=pU@&C%E1Qe^p(F2W`P5fdHJbQXcKzPh3kAJ}V%OOWL7 zYh~xj(*L4yt7oT6rd?SkTf3Cb!Jq5uKS+t3Df|hb5DmlLL2@MhN@N(pQ~F!Tg@!tM zEBjmXJK`Wq3W%TJ*R+D=3hTr6?J0yE{sEJCuf#IlL1LxFvdm1kGV6-bcQ0MSggCb6 zZ!2C(?yF-3_TLyA1h!jnjnqe=@9rVGO~iLJkz5aeUoHsC{Q8b*BWl^~x7@AB>%d9j zN^#>$d@--EkD;`W3_n8CXgS7YNP~%FM6~!$vIc2R7$T@=y&CUCKYg*BIGqya*NJtl z%HdD+27~e!qNq1wKbn-uPg2WQyT6w`{EV5+h)X8SCrXo~YlIGeFAoWpEM4UDw5*mk ztO+|{+lvE(yJudIs%Ll*b{1CVsgR}9{~>pD`F?1L`u!#L_@Fl9MAEx2RnO~Y=D#`T zqe&|@R@}ose$1z}rRIg7aTBQ3^*{9A7B0}`l3bd^DPXfNS~qIwbvpbh5TKC(>(XJj zI1V=(=8AOzeP}!^hc@5zb8t*!9uRl7XG_osN2j4asCw>=Wl=gfX08zv89LF)Y~qvO zv4ziwC6BrNBfXox0Q@AOr|N^Zi|ZeN;6J;_|L4j5zji18=es|519cX8-y0QNToFWb z0!48h`-P=B=;#a4>+yo-p`|ty9F?|^*hf#bbb+p@?x?V%w@z;m@ik+!_b`fWxEHw< zzeanmNA_ozrKmbfigt?pi7{>RDM9(OBqye|@QkXv1?W`K?d zS2eK&_v8+4FKY}=J4)Wjj2Z8Kk7kp>2>UJwq?}H1dgWh|1gAp-<3p*e$*sC3Ak1Rv z?l9^t$@y*Ff|ePuVg!<&-W@JDn>_^+=R|UJ_t=Dx5m~wG_g?`p#@Oh+On7PXL<=hm z%bWrY1jdg!xcp9n=1z}{_P!;pgKIVcPg^TEm!S)n=3%M|0RbVPdKf}x3Kk{vV|yV` zhOW1LZ7yJM4Ho)t2g!Ztaj2uIUwS1bq~;~akCDdz(ex-^=DXwh2b;CFcV4!ocBz$m zpXd0>tgo&1wAjeI`egXc&Ie>TFQ=nt>gFd+w)eDTv5?J(Z3p(o$xopc$JGi^q?9PU zeD4}=6cHI|95pfIBUl{UOP={5^oAUmb1hXpi!No?mHBDNkEbSXCLlzQ5=n*PR8td| zRF8EX&87QSLGT&~&&+0=?=oVX8R-hSs8O+pyRTeCB8*4hXloPK)Li7STO>7%@^TW@ z#s!s^zZB_&Yl&n%4VO6Rc=_H!G`7*(34NZWWPWkxefytR3|!cLT5ko`nHsT?5VNg> zQILCZqMR``9R1cq~53Gm>Ip?Q*I@sCH#0;nK?u*TR##O}?wCI2vo zLQJR}chM>k!FZF?y97bb`$dS1&YB4BXsF7stYJt60%s&v2NR@0q&T9%VSlM=87+gx zdK{2%5d0i3OZL(bUW^QkNz|-ZBZGMPa{D=OVGVh-T_D{s}w^^r+K#)vVMDktF1itNCnE1camEpde~!GaHts@!JoS zCQ4{EAzGIAyBF>&m-KO&az>vC_^i356ifts#Ss@lVMfRL8V5!hs*vVELPQ*|D#sq+ zvI6F%CeB{sz`iU&ni*-)uX6?jKpvXMN+A@a9!%r&r9`0~%&2L)&W&)?H_`UNeyC}AwYOHR(7#KK^8FW&Z+9C;)EBsIr4G6OtDKt-kqUT$U zM4w6;v{{aeN!0A@zqEhe~WC zP_2?@q@nMXB3)4ix}eAG63HhQRlcHRKCwoynw*;jc9Y|TKe9&Pt6ys zm9u{}N*$4jRp>K}OfH4V!q{~-`egixBEz9}`v0xzBVDo>+8A34gp7Z6iu?bBetmLD z!KTMaa7PT*ZW;3l4qnD@7sX{eXiqg6xUiP4O9lvn&Zc4DQ5Od=pjBTE7mjncXKR?y$C^!6aB1lNTBV`#$xxY!Scbg9K2lxg z_+Fqv*~(7~!KO+jrC$s|{<6TdKeEtj>Zh31>g8gp_{ULOyhrR7j)nPCTfhWd*_~RUUnH^{f86v))a){WQwA7*d#DWuQB2yd(8^b9ZTa zadpOtcrn_z<<}n6*fue@)ZQbcQ7vST+E+lTQrci^jHS-QcBos+&Nyk7m2W$Y3VC^% zMaR1gd8DN9(Q19Uf0$1@z7;78AmATA3ymb0jSO;@vI>?uZO7fpdlN0` zq6WZ>g{`uQde{uEInW^cePRH@iY5*LL_rfv+bwwxtda<5GFZr_` z5k}34Vh$p_ATJsX_)33>T9r=smXi!W{sZgR^6x^&XT0>eX#NjvgrUK_v?4rl$tD4( zJ4WODcj+Ise^^>n^u~;7&i$?x`TF6bZUf<8@Tk0LRAw%@Hfb%sDJPlfT(a3A0RY%5 z_Oydu3e#bEkNytsi7Zk>KScdh|CHSwEyWrlDl#+5s(Z9O1YVMRtB{$Z)x-IE;)Rms zRrY$p?LW3KbhGrtp8?g%<|IkftH*`$m+OZu6xRxIZ?8U@hrA_SjRD8FDFOD$Z+^b_ za*U5i$qShru)Po0)u+?p>enkTZAB%%i7V*;D2u8uFpJMdNvS|d5KTV+FeZ=8zC*j; zKg-0IZ&tn6^(|<$-T_9FehRyXa_yNPM{W@^&Sgr+bD>;fu+f=N5*LB4vq-EWQ;KM( zRU;Ra&O4IQaWo$ak zy2NxUjS36uip2;67@ne-vFhaupN1>{Sx5BYMq-3?>td7{v%2l&3|UH8rVG5TVgJZw zHX>F#O_t0?9gWA-e~^jXDGHeFN9OOoC+_(%M1qrdq}MIRA~#)U6;0xLYyM{+a~G4U zXge00RKZbihiWh1P1{CqTJ}C=d^H#53(?0{ijvNI6DrS?_cs327V3pLY`mv4`?7~9 z5X@r{mo+;2l{mJmhDZH7jO;&?VAsgCACDx9_P^D8k!GqblQqj+AL2zs?fKN>W5xBc zR21Cy`9h+7vWMAUKI=Og%P8aGuw^}?wJ)nUl6!CB!ldfle`;F?>u(gK$}dm8j201)`V}*8!4cdl*Usjt0`ga`rJ&ex$!HUabpG;%qTG*% zTK4+ec|`-&$k!w^$)8r&_9CdBBvHx60ezRWZ946fLfi8}rI&1V5%Z4&XJ-@-Ut}_Y z?JF6eXamHntdf2-prLVMLc?j1G*i&xdH$hX{yFmJUM{?@|4h#38Uc4m3we0OebqSP zePLd)z55~OWNQG~=4cQB zJ(qTIHi<}fInmUsaat^y*G?0_?Zy6+9XtE=OUnL_W`Ur*pa_a0o6%$+Fk4GQtD1}- zbl-xTx&OAx07r1Jdqcdi=qMz zd5Xy`euPZLE}>OZ3rFMd?5V^FrSZWJXimVJ)r}&MXRW=IGyHmkqy{>FJ#MFyNEPY1 zXewX#V@Wp(tm{^hYGM9%Nt=AtX(B7H?#2GMNmZ7t6unEyc01GNwRi~FHYB;-Bnj15 z*MGg{iyoM(=R(%-E6lZbV*YDm)!c>PYZ;gTjj(yugy5c0od{MqimSae)fu;jX+U)6 z$Baws%UT9A0lh`tFG2g4NstfIPaPypFKnH|z^c1hXk!r#A;* z<`;YI<{#?((9Qbn=L_xEU!ls&B3MS72N_c&MbBEG&`&HyFi%TguN22*cxt1Z$S&M- zYRzXXA{7#+)Q>7Ex*17F+hME(+U=8EE?u3`I_Jk2{?w&KRY?^nA(1sKT^hBSnaLj& z!(@)EKyAM=`s1@8Wg@WTXvx!%feh4Ys5?1G*OpO6`INpw{0iqcE8%7|>`1k_09fmB zkXYin7A!!KlZ=6LilcTT zu7VSdZZ(gYIQjWYq@3nLu?$8yMJ*dE3xfT-`lq#owkcVBKS`YyYdnsuueE=xF0kR) z;p@2{I^{ceHpraq$p*#SWD>aK#~|%&$Psbmy9;_IR;@kMFD2PSI&{uIvSogz;RZVA zjz=7m=Em=6)U%Q~1_f>#R&sf>oL5B3h*M`HKB{Dc1JPaz=aMzeD)0t9+xgTO9Mn`u zZas}wbhKmUx&}1gcy%!Z;1b~!29?p>X7GT}F)6~IKSR?$?$O#Rh)EYOW~GGPeuB5Y zwO#ra2FXK`yxa|xeEWRqHFIP&iPaWg_*OQ~b;}shE6GuMa@NE&jN(fLCgrk;Q;Uy7 z{6?zw3dOstUM}rGB`qzykL(JAB02reAb<5N+Sz!k#q^%APnUAHOk7@1(xbz34QJ|YVWk?6CVp5oiuP-Z5oY~6h%^k1_g)oa~kCzOY63nrBW6Hc8<3RJ`NBV zE2hjoK{p^XgAGZ9>>Tn}m7tn`5 z%8~}>m%QrST`XbI3hBZA!#>2%HIC*I3I0KtbnUHsSIcD|egV5~IaCtxt~NmLO%<9m zv{$Fn`2=&gFcXw#Vb44AO5mQvwYR@JiD%?u_H6m^PRciIwb%S$ttD9RS`N-1< z-G0-;Yb69hZZqBve(w(5Xy{E~STLR1BL*6}^x0}{;-YJ`bn9yI1+eYx%4wgEGgdsu zvxa*ccqDql}2W4nu!rk{HI}5RVtZ3XKesQlhNF4Y>}swW6&X_Mf&_qT1L=~ z2O;;!Txe;VYjcQJa4rjATXR`Xne4i%O!7r63EQzu2FXP2u(cJdK>a`5mGL+)dza;w zVJun8{0m%a>FL3**&`;ri#(aCHw^9V_0tLMc5Pc9sS$9k2 zg(jh11A`yI$>L{CXE%qr_I$J6*1+jJa4e4qdg8!v*xM15;r@Ifl>saH+XkC?c3$OF zJTg8Hj$z0oiZT1v;%YTLqq(S&qTfdEH1zmzPtE1>6$iBOyWc4#)1CL~8Uh^hgR)^x zFN}OQ7XkT)ybWf4X7=OTXdfqGkZQVLtMeg(pci$*UhTR1qmoCr!abSMURcV-rbX!} zDz&o6NC%ttLkqwbV45`S2KW{tRZY4!)plZ6aa1#>qh_C-+7djN2jxo?;hJ8ymmBhS zlrcBd$tf2q5;s|}dX~GF5AWyZG_E&#AM#}3+kLcwD46hzg}b+|ivZsN;4rXO6~J{X zvv9=zT3QbwSYs<*KlU$bC%^q*w_1Vz%QYo6qJM^c_7UpU&q+;vSM_@w3<=u;{4QnX z1*4A?6hEeZUdzK`yQ+^xN;%<;+O@dWHw#WZ*3OJ(_ffnBaZU~nR}B1_!LnIvdZ#3E zTidIx+1RAT&75vgJ5jPbAHIr#wq8^~T@)5Vdk6JYO0)2oqJE#CHvZN}7w} zLSqM{P{PNShjlJ)12Pf1hni5HxgZD7#&T}0$L;)MS&3x+t$9i;6=`Ufk``25#g%mw+6 zQ)7KqJ)hSaDMRc^4T4lIMsc$V9T4n6F8=y?D=C}Ddcpi3RQaw78N1Q6bbgN$7s%H7K!p3u({HXm z3Nff(diV*Tzm}cTpJ6~YTgczs`+WEoHoR(%F8i~WM+Rb@A6tm7=3b1HKzkrEi6x`O zgb#lCC))un~5w` znCT+ZIla)B5Os5NHC>$kWQGO77$KD&Wqjp%x{`00Iqy_Q9@Qf#qtPfF6z^qOe2Lo^ zd%;K2R61{IR<4@4hG#pa2&oxZakcg;o)BCTf>l4WO4*OnuE&ddEtYXbeOYH}WQJPyb|mKf`lk_mCzE^&HR@Yln+?+q+OL*^k~cfW`WaoXz4l1@r#wLk`KV>X&H1Sk%1uw_iXT^7# z!y7jXfOxe&p_D1o2OEz$8O90$ipSK&(nx3API9EZ-pS029_?$J3`c|nx@xcMqNvV7t#1} zj=AQ~B3KLr(39=FJxKzC_5*jBXGlaoNiG-C+=(7y-KYu1FfV{u0?i(N`S?)6=)xM(|2y-X#ed+I2 zx*Tlbn+xC`jJlx=8?m5W^J3gk;z2#9ZHKvQ26Oi6S6)fQmv`G~ECyG~M`sjU`z;IG zCsMly`$c7{@AXjQBjpB_SO--y%#Z$oE#w|`=#N#pFOW``p3}LYuIgK>>fj-{iv8m!f$~Bgfz|t5;<# z4Xf+w9aytnHDHUc-w@Tf69exIdqwv<&gKRviqKII-O&%<{}wh1r?$I8UAQ|HxbTBh zmXa+2B;SDJ%KD9VItve{bMe#J$Ma`S7-jt|Qog~1>fS5o1KT}S4quP&8CQj{fAwq) zQ+nLSf6!ukZZD#I)V25#eCOlC=`lnD{3`>DaW>ng=?UYX*$=0AC0!Vi0VN!$x#fOw zKDCYqq1!O>*mE7Bv6Y(LAL~~4MgGqw?&Q5}2pKQ%Eizd9nKMnR+BzrhSWmtRoW7b{ zazm=eT2wRCh;0d#cIGTheQbCTG)|8!_J?0qFJ?4Z2&}nDBPE26PUhXR$Av>>se7j> z4NxYGQnFGICNUC*;TKDm1SG2NZekT6k#F+3tyOe{WumV>|tgy65(1lltugkwurCZq_T$R)VZrMW4wL zKe=ZULr9K^wge4^?vJ?efV?~{xY2s$52rfw5|21??7K8;sl6sVSojBbQItEYx;qGw zK%VnPgTm1sK~S~uz<8s`7012KsKj$wEnEJvJl6io0mpMnuk)&P70FmGGPnpUKi*U&u4uer!#`~T{pyo|MWV5&Tch0X6sf# z-xmpAS|lIh$kvP2EaZ5~AU#U1rNDgKTNPQzFGW!~u++xOej8t(qeBytPw(6|@6#GS zwf?bkIB_1Zw8bqGKU~%J3x`!-?{SDLU1Lf^+`Ap>wzEqkPfz_*vtkML@X2*X7OP4S z*(55Rs|O5@D+kBuXsOpL|M<CWGJAJ(ye73Y?5 zZ9BwNR@()%SFP2Z9o7a5i!V}OWD*5Q!Yj?%UvGro6!ic3&294?E?Bm%pwZFL`afzr z%b+&DZtVvsZpAG)lv14Hu0@Kw6n85W8r&%mphZdvQlQ1%t+;D&3sSVjwP+#GzBm8p zoM+CQIrF|B-Z^{=Gnq_w_TKBauC=apU*4eIerecyNnVNew_qkqUEw}rV?n+!?z*4* zWkVA|u}PLzWMo7tu0Tmo15X;kRXimXBTQs`%C7>Wdc@{@D`-EQTTe1RaNHRI{ymDS zXTgx6deNHiMZa0*L zzvi8nw#Ls9wxjSc6`(&M6sTQ5NN)7)9KJ6M)JIhLR;EI4$$F>a{YqibTTtLptPp4C zty*yIvBoF2S58u=lAW|^<@-`3tqx>2+Y#K) zKF8l~dXU7cP5OCZNoO;KfnKRzwe1Gxi673>OP%6_$2SACJ9nfjW-SmuLY_}}ArdYQ z9&4}Jh%Dtu5f;7;8{SD?C7xhi+MU-e#D$D!8z81l%5kRN6uy>cYq@%5)l>>8@*_-0 zM;oS&-fqVO>ouIhGWHi1Qpvf|5I>9jzobT$az~;4Ej(Ur<453GzLFoOP3O*lUk=N!U;hinzfo$Y|E{x9)cBH!hM84UlcI5UYk{%8; z(=CLT_*o1ze&2$U0y_j}5s9!V?S5r#4in&F5ymJp*O@@$Ts*_iRwK|$5!jq9^RZmv z)=zR=Bxt);kh|(ahE)x@C0!_O0-7CX`}C}3dE|meYqgI1;w!#~W zG5I?C^!acikojOsWa+)rCsgea@gZGa@GyQ`{Ez+*rwN^F1KkJ4nOvAm$>!v2B3-v_ z=_+rEhgM>VZNN`rHf~-$W6}=K%OGE>GqTG|Nx2#KSF2c#iQI?bgW4`aBQS@F^*&)T z9n-Z}`%@+7Qctc}V* z;xc;HP>*5Be^Z3RW9`IwpO+F9NmbKj$BUR6q5l23Mb5RzYD&&fd&dl}&`nhhb8^cH z;Fp$*WW+!3U#AeQK1p>o4pp|HBM>1yJKla?zGMNsYfg;Ii3u1(B74jMuinVRm6_3Y zwP?koF;60(`c-1}?P7r6)d+Y!-w_vE9Ic&1F=`qBpe7%6s=HqRccG<=K}1Wi(zbD7qD6Vg1sR_o~gV(4ZL_=J2WXHKoMdutk8PGZYvGLAU&*~ z!hrx~Js!zWw5_nT+aNgIBPOEWDcSzzkPj<6>Qz@?zk#s`5RLWk(OZ*T{Q0c#?G*&q zFEr-X(=Ff>X~Y*Wbl*ie@5e6soz#w7EWN8yk30x#s)Yd z)9C%;HQm?jS3AMhQ)d{VT+%BsvBg{QL5n%d)IdnyKh~^4#{yj%>dW#!!Nbt|kwIK6 zHt?=H^wFq+58|~yWfGR$BxOx)-pA7jHiz6l%(`*f#PoFb$IpZ$voo>LdU!oiL(i2w zO4zY#N_5r-s{$VUKV)z%<4};~RgHV*OW~ zAo!q3_@ov|Bem`NoFbXMxkD(f{%w}qpdL^>F2VIfK~-N$kx`2IW$RK24L5e^^8 zaCq|-r1O|_IV@dlr6&}eD>MId)4d@8u*2ylk*v7slaE7h3I-8&iDHB72wgN7-o%KeRv@$*Wy zwx6j-`u=U27S+B4ecmjalaZlh5=cS+lC7YqhcnGKD(BjTPP&Y}n+++uVrzb>FZg}+ zqB=lh-&&BfsxoDG2geLDxpJ<^n31QG6r@Rfx8;=w%kDR(S?0vgebeoywS^mX30Gs$ zf$o36Yg{|xY})S&)20TNc?VJ+z(J^N{=xP0Uxr=p0gw6@v@F(>V}7ztKtb=gFq41c zPE#a!Yl4qe#%zZS%t6@mX=|S-XV|Fa1tlZ3#QZQ(Ucl)#jZ)Z8$ad)6TVV}hR?9|_ z5rcDo!dN&7uZR(&?GEx>rI-oL)ZH~iHvU=doLb!l{CGBt3JBA zZg)lC{2VfaZw3qp4t`f>Ka$UZTz_YqZd~xC*ZNS|02z*Ch8*(aH>x%wh`qyhq^2^_ z#sZaA`gzRqo*ipG4Cs(7WZ*Naee;K`%>tz6rz2%3e3Os1Qi{HCzwDYx0ORgmsCJ?V z4wW_;O+~5-X~+k0d(eiDr(yn5MthsTN1kf3;Q5El^>!B(Z}{LRTmz@t^tE=zgu4_p zDq^YE{x}_%_YtTl(i1V`?KDnEbnK7%h9N5O{_6)9JVC(Gl}?Ijg!?V^OdG{7f4AS6 zeRp;lTYS0N1wEL_!l)|SJ;c9Z%aR(?m7No1M7s95<+gFTYC<=PBZ7SeDzj6`8-EzU zab>$27Fo5;Xq&U&z6G1V(1E0KNrSOOJp@@2q3^Mrtp07!h5CFb$C>vSpk1_<` z_?dXd*N1F;`~o^KgGuMi7T?Qm*omL6K6g4bG<@@OC*@*W_tUz}E!qen4bMLe8TE&d zO8Of6is$_djH=?Uy0*Q=ps~a{1kXU$!JNd8D(U2twf#}tZo#wWVZB+gAO*>b( zS*Tdt^#y6|b=9C=M9b)X$P@gwU%pM%Tg49JxC^JB1)&shr0m@YqS|VVSbvzVt=T(0 z;89-LRdN~3i^ds1UECR){J}rYYC+p1w$9F<)eyWUW&voj4l*xdZg*P6v?)&{CA~#4?VvuwJ3uP;Jb3O^zsbD;=Iw%?+ZE?BU6TD zWoCghIV|u+@3nc9(~3^Blc?tCRJojURtaLm!$rOI)r`a~a=6~@R-?KgQB(};xGQF%^^odyqjNVNlrmq;;hrZR`%GtjA*|+RzZ!G z?#%|yrIm7TU|Ejq7(DF@7#jO(xuSLT?Ol;L@PGNJ5Azovxo{sljrO7lpNO#{tVM8u z8hR8yy%{JY-nz9E$uA{{m%3^ch5tZfOFr5}m(~5EC`UJ9PHZ5%v4RE-qxtghVW8R#DFA-e_hNzkX4Gs12>ljG&Y&Fec|8Z-RV zkDFy6GTG;-LY!E=PIgF5reN7{Zxn<4D&uJO%k3laS4=6U%bFldSYM?m$myNj;B#aN1kJ1tvu&hTTsrwLucQ=4A;^QDPw2^S?*-!$(s+{*?#>Qhd_~t ztJ{*XorYVpcIyrWbK*Z;U~j-mRb+a^DzaRabIxgS#1j^B=9weD#K|x4c&z`+rD>?{ zUrQ0OVqauc0h%Pp5{MpQ>mF{nlSb9cfsZq*F`nmAd-xXxYN3bz7nn0ZfBEYwL7OP? z9WWrr<5SIg!pIew`(H{jtX^f^GB^8hd$DRv_u#kP5_Pl#OD{*xzaN!e+o<0RmO@ZO z{^G3ap$7mrw9KgyO@)EeXAY^49k!rp{`eG3Mj^q)njqw!iXH>XuMvv0|A2^i`ps!X zc`v|--dpE2b!IEZJG+>C=iNmR`WfQzz9sUzWQ0w~1DnCDgY~cpjPM#`dISA#|AmM1 zy)#r31x(XM&>v^DjifM;b_oo{M!7e+@N%aTV^s&D9{&Kz)MWd&6G6FcU+$aNuy)Y& zPgt8skzW1hWh)ocXd+s4(Kjc%kw{%ELX`c=;#Q3>a#z7~-n-*U*}!%B2qK)lfchx( zwyYGklEzD^^}%I7@$m=j2o&tJiv8T>>OCt3CM;Q-zL7?!d3?|azcmr*SK$@DW(V%B z#5Ro?GUo&_HM0Fm<<^Ggrt4MSnD1s3t#jgpMyH~S(X0hGAwTw3t#^M|z0Yu3|hE!_!or7a$U zTj1uK_f=b80}Z=(igDVH4RT#nm}0H2D_9A&Bj-+Q-PI#Rn7^0|jZ&={mHqO^( zao4(_djhPQ&K>+F5tA>{q*?DB24_?r^L9Q%t;$OKcIb#RB^N>EzoBpXe$kXWCr9P@ zN;ig(aHg%X$QL&Yyx@w!Qy7b_EAf0 z+|cRxQL%HIr)d*_!n>sciCY}^&Y?Ltv}$th^yMZ?>S3kEo!Np*<&rOa6ObctXARBn zU-ZnHx0PN@ZYC>AjC#+tgsXfNsO`8DG$qsLQU8PikEn2Gv)gwZ7x2|k?RAA$b*eOUs8BA@rjBM_ z=hszHLAShWYHilh#{-duaXP?)1c3MZpFoNjyF)w1zh+!1YPj20l$gc+wj|^57LT>D z(ofH%a(gPrH27J=6SJwgWv?n5v)9;n$usq4?X3n3MWD3X_X~DM-&jpMwA+>TG7CK0 z_iAZ%h++<~lf42IkzS;CGpgtH8w4G=n>`^L9 znvRB9Uir(@g#p4*ei^9MhYtNCV)%pykic3lVdr~QTd>bJ-&aQ_lh0acmzzLZnHE@& z9_4HnqoKvT{n_!6%l)&2&iuBwihqlSgJX-(mdmetAD1Zt>vVeSEds%K6L0BFybotj z^^=$p+Nu>w=l$0c)+u}2pp0%IIFU74~NpwZx01Lc_Y4*?1xF-krAC3xpXO= z{;J|RRxAeU0|~O)?77b~?iQC#u#55hqtxBj1blZIWGz2j1|*fMVP0@DPt_jDeT@ba zjW1FJXz)U6Jx~pQGj%i)`BfAM<5Hp|muL6#Gl<=Uj!4XvPW_NI)iBWlbEK62_5FU; z46X6%XZ`J=dqWlYNV^5svCpz5W&8tZQr|F?NDDD4E9 zx_)+|-H)!g+^hZ0hP*yW3|TPFC~OUad#_9W@_N!V=y4ded8BF6YvK@m=0v#qs@1jY z&wFiP)!#9I7fzaro?Q)ETD4sG<}u)%r=XROrbP??+{2ym$>sKI-|Aka20jD02nJpV zC>893>mQWVODIRM_TqTtp13vzHJgE6QqRWlZ@IZ!n`(~`LAik#NCniyQc(GMXVtZ7 z%(T1Cu?1z6ZdK|0jWeAb`98WnHo?>7D<+LdPDF`K|HCn|{?fNDV@Ao;-y*;ObXVL? zW%J-nxhnU(<;Vg33jsdXRe%>Vg`cw%kTsiXTfgsR;gk6CQjtZ~L*N{uzX524KQI%Y zuNfzlK*Vc2HgFs}h}$nf2JQLW)rF79w!)yTN<8J}1Np@-1ZW~R0gd>s7?4*fg4KKx zICnqiBpCb}=Bm-n2Y+q8_&vB*b#I+}VCNd}1RsL5IVBmBLTS9aKyjV=G${iVU>G0N z!UYydft7Oq6_}T~*1m~a-EL`03+@dsW7P2IQ1Jnt&KnZ{(Z?G&N2AhvPR9zHXgkn zrCAoS?v-3babSDV6L=b_es~>^3JMS&>R<5~I1_g=%jqAvYf0Duithb@|E#?`e|X z{W5M3?=sUJF>iqA>7OOLg=!(1qR+&<%Zum0ns<%Zu3yl=Pq&M7v~M%a((s*-pCEtj z@i(>qyUHkAQ(EpL5qUT`4RXQsP5Z|(5K%q~SO%mTja-^D!ndfhynOus(aWGeyir#x zQ@uAoDZad7S4$vNP|Trh(`1x*RNQJE)cKG-%Q0P=M*X8&5}^CrIx=L$N!QWj3RLrg zE7(H}MZay%tca|TzFW|1mlYc}5_^s^5H4LhE_>Ylnci&DU?wLU2+xB+tbd^)lK^v4 zaW|GZ<~Ye^VgRBsD3n!M6GNl(m&?zLXPF`Bty|X?ZTE@M;k}nvksTL)&#kmh zG(r8#f1xw#*ib}0jK;%%hr{zJndSA=1Ah*hdi~bOl%&}bDXq` zbUuDzCk^T%?o$$6*Xht0f(MfD^Zvt~$uHs>xD>es@OO<8N`~a)DFdSfB%G*EYj_FB zkXgGeb>dZzg<7TLW2S1wz2KS;XFaB^q7ieBGX~467~OvDz$n!;*KJs;39OrgaE_CQ z<`6+B`aMlIdDrUQvsKV=8l78hAd7@n)M=FQ29z#5F^UF#Oiw|@ozC}`&L6fUWU;6t z=(uJ+HrScT$tSmUh_XW!1S=OQE%T3InY64L4Xjkm5tmmED3x<@jAeEBC|1zNu@|fx zn404X_duU4Gssen5>L6yRXz2%7b4+;YHicM;PqJ@x& zTUb2d70NplQ9eMyoO9G&mSb{l^Px@zym(U&{8(P}s#y&aHmCQ2!shP|441JHpo>6EW zHFl0mlUDM`;7HmtwX|i+xrL>Bbv-3+&XF9h_R}l!xmyEgR`pJt91u>Jkl8H1;X5xb zU2FJ5F?k7zpWVpu7-caBbyo+c$EYY)q>%sq#`~M5Oq&swYzl<KwnzY;%>;dg?+ zWjzxcGjxIVZMI{kxgmjVG zRDH}b4;Tt09!uCvJBQt$-N67#rK9Pbv;#pLQEYJ{`v%BrIW@y`?(%Qi%E*9kG6&T3 zMw|Q|r(zr};9wuT^ZQ~NUy{MCU&j^68d?&0^PP>{tT=tGsR19rc~d``Gk^H;+K$v4 zSFMDlN-_}>y6BQCjM26W5W%NGs}A3=`-hMh<`tWM!l{(cj27cvArt?-fwLoZ)MxYn zh9O+QDBpepJL(F+{h^iQ($Qx(40A7ElduG&7B=rX-A+r978!tYx296hL@TzskSo*@ zHrvy7!M851zl*7Lv-Prs`&u*NF1##~l&Ul4uWgC7*dyUcblwT^n|#HlQh_eT48&$F zmNIr#4 zqd@By-N%iIv<`HMHZ7rM-!wEOi=P;F1WyhKMo|lGj{%PCa`f*&Iuk0Pb79>;`nI$Z zaZ_D~|4<)1i@eMbAIdj8z$>hA2eewPKS^UA5&Rxl8Lv0*Cs=ZR>cd@#1<^b`U-C(_ z5V}JVb|y8z1_wq2>|YDl83;~41Z#zkU*UeH`3KCjt@Fv>V+@sdq1-jqoe+qV@bFy= z?(_^dXZH87Ciqt55ibyt*X)N1_yFHTzU0HMkHu z<1XSQZCJ|#@M0Z-E-EN*8IxK2`oYN$G_z8#10ee_R`Y-ItiGm-m!MTsn7#Hr6!e07 zkQxVD!dMj)eqx>fN>Zk4v1*$q`(#cZ&R`4q5u02Ei%i2#BhZ zBG|v%Y_XQaBu*07EF?TM)6sqrDn&UJJ|;?h7XXIP=}ZYij!?0s=Z*MXDTI z_P+U>P^^wRc2AcasaB9|b?tXP;{go3;_?_Mx`U`3I$`xjmNQoLZ61@Dp{cWNr)M7n zv?6s44h{5uwM6#s#iI3249}NN;mD(e^qzn{Q4 z(1k)J5`w>~SQXuUQT%QEjyYl~92nVs_$Xgz2$(;%LjK877d^L8nfASEN$LBIr=CcU zcX=pIO>OBvVCx!#S5&c*o6S29 sQ;GeDyPO^dA~yr0VgHvOYjye;O=<%%?~42+2Lk?76g3no85^E~HzpL3n(I{&@b`v-H)%)Rfu_S$Q${aI^$?g`VpmAbbQP(DF1-?mDKa|Ll;_=dzY?-T~mG{*MH}c(ei^sJ7~eIT{#67?%=V_I~lx^>xRvcENXZzIlL`cR~~?wKQz9FS~5C32Pq zcm*{Ex6lDEK6irHKw7+)E@8&9{k>l*RJ(YSfm*Nq^C6#6wbqLl{vysA3siw8f&Y0u z2(G}~Fa48)-FdmGVt>0^KS!_fwg06)cd}0H> z*nVRDpBIoOSO6whef28h#*Jy-2U#JAE5N1yOcNFv`8pbl8fz2-mV&1Em$d)uWN^3L z_k{=ax3=4jL0X!m|0FR4+x~Ce{A;ZldcYLLFr;w%@85L@5Xj;)ppJrs+k8gX4#+ZC z@BeGTfIJ|OWW>KzwMkkAA0StM>e825P=@r|P(a!CA0eOu89>T^FBpFN`p}E(*ZF4q zK!Hyb{#lZnTkOR--~41zu5Aigi0r>^tLp~>je%!sN+DtumP(utAA;A)OvNE59)opD zozYaLRG?WOV2QE`&_trUCpv!FscgX;X`Dm%Y^5%R3?zDoQwkKwL-ae7OqDWTb)t1c z>u_7Jfm&~owmTQNg?(E`?QEZ%*$EQqDGJzu!qVqNzA98C2=p9scFd|W%vU>`)e*@| zh3`uyUI9L`k#htEZvVO}1Ikb|gAp~weYOMp(~)m$iO2XcV4c+NBzYpGWR}DrL_DQw zbATb?a`WJ3`FWSr1<*C?M;m0IQDtoh22_kk5)-|M-pm!zjlo2A^i*@LQ@B&PT2ha$ zA2x^Z5YS9Z+{oW|YXA+TWg@-jv5#9GWl`!ibF?x_~7I&B~IdQ;rOX+|MS9 z^YmW(C5KsA6WAC&Ry)A23if?Ck$Jvdc7~f@Ll3XC+yS*j$^)v^87y&iS8pQNpohwm z-iMx6DGhAs$hG5-QmSoB{(Pi{YlK(MNJ})URZ08kF?0`5MN)!HV57k1^7yzd2;&{+ zHDf(8_p_p)elHkQ1sXo7#PTN*Jb_R8S*7xOl1{iu@c`M&=Sp{}@br{3l}|kX4IxsK>|{RRQ}dX}h}2b(ODf4TYVsnJ z(f%m5#9om3$WP~r7f8IFtabDdc)YIO{|;zDJS&GvcI*`Dnx)w$c$Bz!V%;ehn`0@V z8m!S@TG+D!O-M)P6nvx>ah!d{HIVQK`M%u$j${j$igY@HQTCffD4VP@6gfXR%d%a4 zcFB7(iMM#FJnTv?17_wBdFozOe+xsElQU9ZsS)hIkS;C$~i(YaUn|8HO(@Kwi%66~h z+7X%Z{noReiSy%-nE3<_(_zk1u|q{InZ|s(k%xW(la{eLH~lp>9EQgFc9k9~6sXsZ z$L54EGi?5CFGtC1MT=-#dfLv)+4Y@l2A4lukE|-E#NRv#+Ll|Ox>paz`wMRt!|bHG zn$61GntWE1qONaeDMm7FB+VzYkNvuO4mhFJ$1!8%*vAmRe}WFv(}^wUwx+OxfB510mzy1rVSC@K!BEOvsXoi( zDL8?Wlk#M%M+$p?yX0Cq`&}TK+zn#0ZTaxG**E0Sx`Ysry8LaW7QAEMO6+P$9*BTs zT2SM~Th%VRERjrCZZ{g1IpNLN^YfYAkim|fs~o3zf?ev}+!UOH4cooO3W7klObm|o zBB-t(urBg%bDuU)0&^qDQudre`?VTRTLU(iABjZU4 zR457GX^a9{d}^T!q(faF4sBw_Bh$~!IL#B3qZVLqjD9#ZSEV+Q{MZJsnI=)Fe|ARI z1osZMceeHMF(qrI3}Oq6thK3gC%6(H7sHfg>syqyv+PjgkV_1{jG&~&R^==rMkc{;08e6e}j`5%;SNZHA+Q??>l1j3ZZK+x1Z zGQ=9K5Y47Jw&bm-mOE3u5Jn%ndE#_PGkX#>Jdlnq))UX>6X+dgi@+hs(VW&tLE9Zy zWdffJzC@)cs;J|}VbygbDBqW{N_~R^u-P%xRgi2j5FoEzJlMcgu@z`F{2hdyWY6<% z#%y%^Egw#6gR9|aXo_DQ6**|IMQfNs_^qe$@W@N~2gvef{pY1F=vFrTZaV~Pog}L= zun`R{KR@X+>oRo84mR5Rg8-XnZmngEOIFG{*Q>gZ2aLaD$3{Hs4x%;S6s0|8j!z(_ z=`6*vdUwTS2YiZU^`YjJlJ!Bf*1iK3^h-BE4v_dBArVwBBUI(&B$(+Vv3oX~0Nj8- zF>Ulds-*6+$XTlJyR(9Ysq=5mOcp4OSnws^%|!=^wlbb zf>l3`j^re9yN~tJ*SQtdioHZ7K9Pg8@&R9I4Sao8RXK|?a}SF1%IEDV8Avj;cc#RA zppRla{3=Y{)4?Wz-f@I{jq7UktJ(oToCUvoz``j^g*kT5*JZNK z2k1)&-_Y~=q@S%Hc0|AmrcW=IG@J`f?|yLIeoJ-4B<|5GDf@Y&n813qdOnF*hrMD# z3CidPeEefjv3X*ZxL4$Pfd;mWN1^!q&99Z`B5;Bz>`jobn6FNTa6t`$4730SE*`z! zSptcCwwGhdm4rCShMDJ=_0eQ5mOp7_uFZX01Pr#pR+$4pHh~QQbh>u& zuKDoGNh;7n^1pHEzwp$5i)aA^@h`CYZ+!MIK>9Dd`CsrI0Gs{=4*@;?7nFr zHJozivOj&|Z7z$ZV|{ZFS38rGe|oP=ilcf%z!tNbyINx)q{Q53*pmqU<#G5{e!Mc* zNDTH7QeWx%eUD*g=|xHbWeP8{iNkK+qja`W$6gef@z^`vG3)kZG$iY`j@WHb;52}g z3nbdGTDZ8B1S#J#v$+NA)jx!`2Uh3ko8ImTm5F>~(=eW~zd@D>f-WUbFP^D1_-FYW zQ0{d^jLB=3xQb4wlIBH->54C4FD2qpX*`BPdW;n5!;;?lj+v}%&*erE5i?|!juIMH3-wSQ2du9k31*EBKi`2$@9Rg`W=%c0KXu@|CQruusF@wim@OvCTT zblq=Ekcn71_*3UzgHoA$^kt025rS9xhFOYF*QFBn5nKv?yuU%CPttq_?#k#z54ZrS zL54M;jI!(*k+~m>Y!d?7uO0uQC_XHo@_1lii(%!rlC;Jy!L=8eik+aDUTn67b+-!(1THo_pta(vYDz0$#w*?4Pq_jZgk~&+q z<4!2@$P{c#Z_4{4mC>QckgD^3VkUkATc@2p!`+YainQuCFS+C>EoifqWNZB0|6|WC zqo3`90pG|W30j-blgk8dW-I-8=V{d0D`Yz2&snFWqh&~gv~J9IL>FF)nhvnbx_!a4 zJY?lutJx1+$5O-vKGIFxuj8z-E3D$8W!6`m&x?O__<2xIMo^B?o|d;_f?iq_I2cGG zLbjl9((ft{zOBmP($$x46DS?3CUk(Cu&yO-=|w2ZGLM8bk>rD2{`N@ft>R^-r2SsP z9)IEdbIbE3OC4?0Rpg$;MMdS!@}qh(5R{e}7f$q?Kp^hUOc0alky`4I zzAe`ko{29uP{z9Zpy$6u!zb8dqQOQxOAYJDN9hAx=^V!aPoZ~Y$Y$^K{v|C_7+ z&wV_Q`2n6P+OC@}cD8SfS;~|buv;T{<0q)0vvCirhEO$aU_Xrz4p|W2mDB6k`=J{G zlS@2ta0ffMW$TOr-{k3sN+81n4FEx?K=kRF!1#2@9Y?0;dW@?z68Am+cpDLaU$BZW zTz4u<{f_{F&TPZ7f`g?s;87Vr^JE>S& z;5ugDIu_O8KWh(p{mbg5d=dVE?-LK-4?teA`ztd&6W_1fC{&gF%5dvIUgZaJ^6{Zz z$@lEY8o=)qZC8!;ddC6Fw29S2Mwo}SVawItCktpRI^l!35!Xv(>EZa5XU#^b+h(v%%Uzj^Q}a=b47 z1>g5Z zs;xGjf6cn< zDt123tfm9W75RkjMjkTbQdDR4t1i`QY`}?B)Y?BDEd3M|s1KwNqcJ}1w$WziZBPWK z4Uv^QH$}TNG~S}SnGm0Ny05-s=*XPNOVCfBtgR^F7ccztZ0y2rrK!=uZzh#tG~H#7 z8g&E@nxIqwNog~3{H^vg7#`&KF>}IS8|PH zotV&lJ#;{{6uV5Ox!Ruhu$=2_zA@3BsKfE{#D(q*J(-=b%cU%P zf1cxg3eW;Q5cWi0#phJ&mFi}zCZB4jVt)A1=qGJt)IIW^88gbZJB-xr=o!AFq@uYZ z{)iu)6BSB*kN`<`_1e7QuPA-==3`rs=Ix85Py%u%*H%5BGYQ z;-8v|mQuKGk<&?3_D3#=k0>`RS}Mgn5h>?Bt{l7;rPm-*P|0+*B2>K*=zkFK(VD+_ z!=Yj31x9*ME_iDS1B6!1v49U+Z+dLjGlOu2gOak`k~;}E{0$djS$-Zh7n!0h>NZ>6 z;;AULk+`X~dy|-#T=Y}8v};sS9b8v})^0p;UbEmld$ZsjFLr()s}gO8zj-shV)f^e zAUUYq13o=d&?I<2Q=|-vWnH_} z!oK{-nH=O3CQznsj6__QZCf}@IVTdK$CyoMU$}WeUbFQ^0Eczs&8M zF@KaqN{?Jk_s!!6*5Wi{1Ed*cBGQ38?td+G{SSc0{~(&lwv8-7)xS%S&Wyg%=f6sSm(>5qoh>qBxq#jGzXkvyAWa@z zSSBv2uNdfiC9S`A5e0$L{(i9^8OJ$Y7<_U>T!q1q=1@X|dDUNrqQdg;Qe`G9emlJa z6rv@W`&?YS_}{GU2b&KaiB+qo7RcfBA0@w1BhQ{zsW+G|0rAT@QrJh~L$Rjyaj*9n zBEhxPrO%|PQEnA;k!R?x6{^FRO}t4-4SJHd+gnV(UuXV?HPKA8GY1g6P(oCVy_d*3 zS+Zpgc^AR*%xG$LX(UK$`g+QvrWzmuaoH`qxApe)r)rLV=&LP$KX*ah*s1zGpK*~K zSK@jm*SPYC`QY?*mWU|8)dS|QmL|rF!73i^+uK#M`Dij)-oEH{>0{=R;*KB3?rU{k zs0jm+VP)@lIgTS;vx9DlE08&u5kMzAf-n6byCxE!^RdEeb`Q@DDY@J3ifmtEnk{bG znkk7RmPqryGOnF@9Qjk#MS7@5&#r&DPkahTo2%A+JZ945-tzp(pE8~;UKMsWY+fAO zg=m$#F(s1j=^?2Ers(WWV)E0h1h2=!IAQ%hwI?N#sTfWE*_UHE1;5aQ{g#a?uj=+!6H-sYe`@LO2@`<$^}>&#Y93_jNc~|BOhg ze7{toz2;x-wWnQ*YSg)T0@rz;LK{n4oKq4UiWETs2Mb6)tc7ktT%7saeNj>GN!n6n z+;gWmsVVA-LK&Y|OVbWjGJ@0>w!sEglB-bg1Ch+>jnl!zfx*0+T})4my3zE-jxgHZ z$#hi1@3cj_-c^$OcJM4&+4TY^8N%V+AOKnmNUGR;kBmx<(7`R%A|zg2Qibb})thb* zb3Pz(NjfFNBbOS8R4TvPI614sbf&`IEvZI3sg_wh)MQI5_%Kn#gD-r9!-d2eKIKqh z^7@pWlaJ;UU&BZi#v}5Jl~Twd;PKsZ(g^}ReAFW~Ss4(@%5;==St4Ig`XVTh3CO_e zoZO&z8_Z`G+r))edIu(JZr@&Nm&;B?jT= zjRP{gAE_@DkH3mNyQhMAm&*B~LvQLvS`Eq>jV~<~o@`JrePz znNA!2TUx>7l(=64==@4vkL1t<7@=ge7r#o0xeBBd7745wWW}4he&BwST$@T=37p7H z_*?fPFFKCKwiJV0Dr#o{W684g{D2`KmHa@=0DuPw9q~F%DAPXZc2(m;(89&c94eq> zkq^`(AwU=4pRDY^R>uGTz_AQkPQZ2J5~RE!H1>2;l;#rI-Xt`hQA{5|*v4G}DTcM3u#m$;D!@NgV6SZkSmb36UUX%0K$H z?6}ycN@B2AG6Bchoh7Pz)>449qJd<_XA)X^vSDI%zHgb(02Kah?~!iW@pGt~HRhft zYr$51b^@zTxI3a_fTeVwl=z5h?OHw|5rPPr zij*uP&ynYw&7qOB6*y8ZqRsb>)wb&g(!f4RUAQbFtt=rW2}}Z8h+|4C!{9vR+ZrH| ztIgvTasm2EXrN#(i<fHdD2N%pJyVHPZZ66-*mwN3UY=~#d#ayz7hJ)#<+&Dq9QhntO==e*_>sB%QcE4b225r&8%A{ zsA<-D90_|b*tepS5^3%h13)dILXoN2AnJbA(X3cBICe&TY$#l;izKWCrc zaY>2EZ|yJa*dnga#AlI18ceiRBOYY?fj@}E$z%nO=A}M`D=KML>&!pyi~!nB6PJMH zA59bUR$rwRPl!BLP#G9|VoxxSYj%IhDF*~X`x3ubNl$SadR>GlMgj$nvFd2tx_@Gq@AI04A6pebppQ-rkDDMtWqCC8yttWv8HI=pMqKQ;G7raQQ;aOuL1 zzT2dD#QRUX4yy>F37&U8J3J(;)Rq%Oc%hleC&^Pw-MVEA1E)`2N|4exPcjg-C4eFe=&K}cK2|rA@t04D7r|p^y!8$>P@_$6L^-)U^fn8| zu@*qb<%J5}VBqWF;AGqJy27)KL=T$v=dwGmcd$+>!%0@{C;p4`__^l05XeRrf|*t6 z-+r!B1nY$QS+5H*4LK_0TA0T=g@;dk#xkC43~rn#zJqy-0s(?lih1F;FiyY^5LsUr zuMWlL?CvFbfMMU8d-;Zbc}JuFD0O-)XIqL_ti|)kGgX}o^=0Fgr=MGSAm-tsKg0^=HTTOi$k5X@#m!@okMBcI{?6V2x-V<2dcUT!+{Jf?1c8PL5YdPDl8qTK&hz3~bxzqASZwJTdo_2Fa@0+gZ`Nu}so+eyFQ-ul`m zu^bztXR^l{js~2vvGKQN45ggW8>}BL22If4GXe6D3*T@9<^+4T-31zmGuGZ*T374# zs6I1;#>F!ll$U#YhP037O?Sq5w}iO8AX0~L;cep^9P%gxK|%AI&(S%CBUQ=>(g)S; z9x$(zVObyUnB;0KR$F>c4>ir`IaBN1q9FAvGd}dK&AEzZlG%T8) zU@3{N;Ms4r%;l<1voC8!k#E=n;J;9m_g-zQBV^^Rp>1WpFFP1;OfsLUd$knzjD;8MC!YD^mD(yCq4qIjdX%iedfWR}xbmBQA{1m{WHi zkuWbEOBUko6Bx%&$zWuT^Gp{)0c)W7F|T|qmsFb0t&Z62|KmSes8Y#voIJU-px)de zjc?9Rd7t*vUqs~8jq)rNhZ|aPn{#UXjYHJQ|xZIxZ=pCZUk(lIG7P_=?yCIHQtQ|qm&`8D( zWQT$03fO=!=^vmOLa-<{Pr<|H{Ajif%dca9;*Lh9Xcszw1yY;6X=Av1f;rs0cE9C2 z#bjdzv1#2I=!Eexnd`1czxu2dcRP|(#W?D;l+-@!?DGV@ZG3#;6J)Ov!Xj%xD%1MK z)s#^sn~=%)oJI9n&L9ZS*7tq0 zOKibRjxwS3zM=ki&?y;PW}aU~cVT0eK)VzOI`>1;FiP@6b+0Ye5kRvQ@+SFx=ozWm zdfN5nKy=+G`M6Y$SGiT{LchZgnJ-)8gSKBTP-(%L`i|O)Sfy)Gmfjo_X+|{s=Hh)=8QI-~91k1m^gJ59kCCcT`lNOR1$4qck^vr{ zp*QTbSv)yufa&X*$dvyyivnU~n>dNpaH-@MEvSGFfCm4A6zsn*8vh?ZupS@W-?I0D}Y(iz`?bt{5r--L;2^-K&lX! z5X8LE8nP$ujcwFOOw)Ni^x9i=@E(BuK=5~zXMD!)o{O!%aOZh5*NO`8Tw}wcRW=z5;-tuTSC@^ zp^VogYKi0$z-{5-VEYAXh?SVo`Oq4!{VB|2jB_q7l9W ztPHeZXuie!l>=`3>zLEFj$~;qRs1GT^rFMRM0$FY(EtJO--eZ;SMpuke)SgU+8l6Q zpixD$Zj!(>dEm)E24Atf%n|mJ4uro9jL>2Ss6WBYf2cuPGNfq#?aOCdizKa|C3nke zKi|F{YuIt~y2UAB$yn`mx`IOWV2cD!$Ih;h_|imH9%v1=>Hy3i{Q4Y zwwYO3*`ulyExN#XnLZs-BGIZu zrfVFF)6XUbwl<~HHPw@a6Xs!tD=RBrgzrK&!o=@|2c9iGK>G_Q#kigVHKhg*!`H8e z!lRsW3kq!Ew*PR9=vh#zLqnybSua0IAMaZps*{oG(IAlK_nj~4g(W4usK+Wi0_R3l zbgcidbWoH&z(xW*vF@FvPWZc(rx5&@K9-;K-w&#ou}o!4{f{ZSKXW{hb$0Z5!ysTr z4L--HAul!13Saoga8=5YXIxEt{fZ>|Ppq*0fx!OpT6+B_vNWKF00`^hKBJsEi8RCn zQ0X-8X`+2c#86{|hp1c3i>#M@n0nmEEkdot$QID2drdnhNxv{-qGH1+D}oP>k%*fy z2d{kUoHY$xBN+i;l7YMG3API8&uK66l$d_|%P5Ux#VGS0aEijz%X=*32Pg$hGuK z5fqXinE-dxD#xpYkDjM&Q4{hHR6orps>;yO5}wjsP?bq)KK*lti@VwL4H3!ZtgDS! z{bflOO*)VQ{T%zVyTC)e5yIw9&&VO3B?~xe;yKZl;qXz*O_Cxr=KQvr576oF#<$X^ zc~fN;S+zx!*4c(d73pXkHd)C={Uh0pYI?my$?IHi+`P%D67q?*9s$zn#4&K?i9ALg z*XqE-oWQ-4Y=lvisnE(=P9i2(QGKjT_f69Yw8H;_Wa~e4pQ$~Pp^WnZm{q^wh^8E*`)EPTbS3~K(z{uhKI^yYCBtwUBPXO4;hCxCG?H`n+_@WRXJ z*$Lwj#fPSd7U%wL89brM2vL7g%|B~y<1Sa5vsx{*o%SWpm}GUm;1Gyx5=V z;}w#Rwz3zW>w%$#dU1&dwX0@=otS82Lde?PM=_NX$9Q71`DRVjik!%)SsEF@?*pMD z7qnR|^I&ha>jA1y7lV?<#WLag?gUSx>IwnY=jcfRNgMr`>Z(8aK`JZOf4o1QU0b{8 zOGerf0Yh9*4BvCP+RSbloh@xSyDNQkm0>x)Lo0F$)JfbwHf0 zPw$aVN#a5>ita2d6b(N-WM)XNj(_Lm={@nPx715zVD|?F*38G~^TZ80Rx$wjf$%T1 zW<|?=wjqr0eI#@9;GBGZByS8$?xUrdZu^p#%>G{IIDbuB{3j|D`+`NLnOfsfCE#67 zKaHI+tks+x9sr5-(`5{ZXR+PeX0&@C3G;+s*v{_bVRHX|=PVCOSS!}!;6;5JtZ$Z% z>2v7b-(QtKT4+XV9F3c{=aIwhUxMfrnM9sOwF#I%L&v6w*gsV9L!V0)0(`}prNGb* z->0drQvyiyWbyqUd9+e&!iiR5%xM;Omy!w2^VGueB$s|45bRWMIhOj658%~3keIfT zT;n$xMk4nMrBAjD-rhxrNq7v%H;qjkt@p8hI^34%9B*H-^xC;h0Z=8 z?psZoKBqEMx2qZ#$DYNF8NAZwmWqZtfXAbllkP2U&U7bmp4btjC-y z`P_sBwNx|=GE<{IZK0{L8=*2fTs2Baf?=@3$9rq|<2kA6>J18ShQUCQKyMq9n9^g; zlevNUi61gpy~T>Epr~t``)h1s*=tFd!MdrG>P~TGS)#j3)7b&trdwfiY|bhli_jT- z5jAmJ72tmj0?H=BWc*&RNO*BKl$LZBT7N7rC{DE#r3$Cb&1`teiHk z74x&a8{^N*O%`>`+GFVP+*&q-xE|$P4p&9By`J}EDvOY~F}D9Av!=1a?9(ifwm+8j zJEx?cU9=}k4Tw6eEx=KNpGjKXoUNBl-I%JfI>9)@ScGW<{cl8x@#iMYXEfWyKsrp1f#2jS z2XVD_uW*K{@vrrUvrLQ^QQ-vDht&Y-=Z?5oNsbzqdcHP?HX7gzIi-JI2TqJZJhqJK zWGR#BWd8{U3|BW!fn`-+s zJ|Bn_VjVF*0ots|mDUFW=$`2F4CIc;<@>;WnG0a6qWed&us0ne?4z4ObKv==CZ<}a ztd7!@0?6S3+7l)qwGDeJyffA*vOu7i6{T9Rh(1WnLPhE?);nPnnFUI#5R?U}QNd2S z_A2HK5ar2oaxM(HsH_LVmImflCw_1#fPFbcjlpAQ_@!yx`}loj?U~Ot2UaTL)tf(` zoubuK#k<3A*!pb6c>z5r7W*Ad7QeIUAJsGF(;~BVb!diRO?f!#F%k2c{WI(|d=&e- z@Qd#rWHZ-U0=p+5;U6y2@3iU>tQ-#=7O4OJVH0alR+l69tqNF4qxpy4WySs6l_W*yK!~*1 zAt0hS6kB(Y$}_n5)bgYZuU?98iKm*CCo{3JsZo}KSvU}SYgn5iH7QGE&vtkf&#&CK z&OgVC2F*wu#*CYvY~1K{)KC#PZT*4r-ttfIMd(sRY)bj1EXf~D%wOo(2y{g_JI5^h z(DrvSrfLhuY<&tjyGm-BoK@KSW(;#>hJMipiX1R>T2d#&y@73a z`-t^gmFsMl3VLRP{E`0FqwMJJ45L6WJxfdp4$-vqT4SBEjGp{Fa^bLNV0@wG^EjXI z+fhtxju6AZy33TsAul7&ZnDFF%@bF=SZ8@F0xswDHj&^M~(>X#^X z_QK!yjv6mcpX9VaWUqxcIlGU~i%B?7^i6!I+PBr^OjpoZ@Zs+aC+|D36f&fu?ALq& zerkE{*qML(sah^i{2j6aKn&0KNMD4+;5`qv*)}t?y+Qhqkncjo{+WY2!|VlYb5^N2 zXs)A))CXP9e?033>-2R$^Sad6r%fj`%PPT-sp8oer$VDqyx2)0JXNnY%ORN@=N1G3 zMyE6DLol#MdXLVJ=$GCbdfU=_#Kw3EN7dssaOyJ(7-2MpXO_-bYVJolMbre{tCoMpMrDLrPY)>9FC4LzNk6u-=3^@tsh7sVbx_7WDP4=44<)!0%j^jEL?hs37w z{mSJ_bUuG@RP(gQ8p<@Q6GV_+fi_ct4TdcNE1&HD=vQK#lU6n#%mF!dTG&ON6~05J zxQl6n^g}(5Hop@3>U?KbRZqQa;4FiBktw3I=-^rrr%`3%QshQ8B>!@m_`dgNbO!Bf z#nP7Xzp#SS-3frv{B6WH-{YuHp69spQkMbW^eP;m#cPSoOTNk7fw!-tg{jw#FP4Kb zgp2Jv5XzG8(I*+1rEQuE<-BFzi3J)l=(G3EBKBiCu^#VcTvI2(5TC2%_R~3qiZUXK zPxaU8V{dw;PAgXHCXFkG4R(w5crI?XBzgYwXR5U=88@{Yt${TjG#!M5&xt!Axx2#8 zS^eEFhqRbSYHiM?l;JH)f6^k`Pu}r)OI!nZXLS!hSRWFh_~S0ea1A>vQF>?TqHH#^ zHhuGtO*;aw;>%-j$rqq&SI=>2Mmar60*Fs7!SDRrxp#s%R-_TS=Jbh_pP{on7>z7!@e&|f)<9YL*Ye^&|0;um?B0q<(CZm#$_}YHr z*gBZ*_A!gr_PCaP)I|t%N)Ndl2@v`i=r`dH>jD4{f@s#d$n9H)@{MfSi#*atxwKo8 z0^f!99ofO>>QDP;=&{lT*@V01?qL_(U-z@xUNHe0fq1@7Xl`ve5?uJPiX1LiTTKM0Bq}8uZHt zh;iY~Pr%zLqE1e*?|O%IPH5H%b>OB{l*DYxmwrp_GWNL6bcutHCndO4??;OPg{mkP zc*iCUD8uc-nRv&{6CU@Hp9c0&Lg=$IobfcWN!Hx_bXrPb1psbuJE4vND||%HGiDy? z?OcCqbcG{Z>1cO!II2&igS4QB)Tf@D>#5&rt#q8u`^giG@$7N-&MOM;Qq zUbG)(D%a0yx@UU3+YdAUdZC$-NiuN_SO}0^Q?pyybnCcfDR6db7L;s7Aeey+cbt^x z{?}){Cb%5gZxM*bo}0@vqicmw^q!6kK5f>N1sADhd1wGs#B!pH3%HktXZu83yy5q3 zkmE)()4AQ%1Z({vhHlH``f)*No9xP2`CdWZHK|qX*F2}IkeNbe_`%qeZUhQw*?m9+ zzd{)te|xP4f2v?UUfjP{VSXh~+~0mzQr;ES@28QS2(}8D$2jo0Y>B$h9b|Og+$T_< znAV-A%|?rCn?ykL0=5jxlaX|XP0ja<UKyrq9HPzmdUtYI{9cHhu_`%HKRF4Zc}~Y@_^h&wii_DoQJ#(c7f1;#f_Syk`4<^|Tp2%iU^bnTrNN`UZX{7tlW2Urp8O6Dp~Iq#KM1B7*}t*+GDRuUA)66Zg)zxodn(5UqmO9mkXT-I4k2nPPv&-D;Wq zoweN2`QzmQU3^F)enB>2`FZfMCDT`<5>!=$R`O(zV3nl(m&+27w5I5#ufF;M+&EKU zr5zFzE4K9gFpH`1s$kw)`#zW|<5ELY-(8Wmkhx=VAZ^cIVqsaU={6$=C& zv*%8H(CI2gM1D|%7V*v(It8Gk_j@vKWStM!YQu8OrDcXCK`~DC0}kc;&ji5lz;BcK zhaW7txyXP!Bvj&mH83c2l3yhulvKa9%;UcF-w1{>YljF z0~r>^OCKkT9dL5z+P_@@+z91fJSD2Cw(Gwku{4}hoBsNsp=tQnM7)$$mLBHPEJx1$ z0eh2f4L-rnJWZp_QPZGK{ja<1kz0wyx+w=d75VgC!=qd;r+4%UJ~L+bZ`>4Fhfa)_ z7ctJ?q8XSfhl`Dj)xJ-)AFnTJ)-o!@-BctVY}fS};jY%dQ7U#06Z;}VQaawd_nS;< z=ZMWC8Bag0)OzE0DSABImsvcDg>aF5;cB%XN?o@rxOsitN_L3?!$Yxp;hXPF%m4Z| zM5^kT7*pE16 zM{E@&u8w^%zdL5P_Dhyp!cAs`TSeO*wbM4gxsngdnZwF$oaZLt+?yu3qDt=kZF4U2-2Pk?(ipk(jW_;`BWm>UC>~&TRM@OW0#DG;29>p{3-D;h#^-rejGe#ExQ| zIPLRmK1qIiG#2zDWO_f(e#U4lzd1;p7?Dae<$~yqF@$H26X@+CmU0FczOUu;_SR9M zTawfmGvxtVeYcun5&W;|N^#P~2sf61=Q>U2BRo?KbtY4LC2wD5s%HK5gge{o(VU+I zm-3`Vza8%t=$B$S?Fw5iTBI|5BZG_SR!by3yNG}75Ft){qU-n|qBtp>&USutZ8n>y z+h65;@$r1vS>8jczCVXUza=8Kp1d5Y`Tualxr2u{UL0U72tXL|47AZXh)SAEFu%>vURvp%V`I9 za|7_Sf+}WSjSE8&XYVk6n_lm>Mlt$|RXd`)w&$&Nu@V=zAp6e`J=+?WuWMNK9+Z0~ zoPN_|pyz#xp99m1^HlwLWbW%yQ|TANuUQmuUNx5erUKvw_xu_ViVsMLucvdm3-*=O z(tU28ir?fd>4F(;+Pm!vwq6E;9#|}=>RmEZ(g0+q{^0cNBfp*y!nxgHGeuRF)t5zY?30a#jUMa_cgb5D^y`3g*HlXN=6;nmpv=UD)Y+*hRsD9O zZu9au<_Io9L%Mg(hktkuFNh7K5W2=H;sntU^Y%J!6<=d7o|-idyb8D8+zi&NCn0|S z`72EM@9u8RB0yE6O{!r}<#$KVR6R;KEFF4uC%dlZzY)FIw{G+Bx5VMCC-(IxI)X^mun(|DgqI^|2#(9iMC!1`_5SNO}f!vdH6((dyOSN{m$w7!ru^1gq@)=ZR^ z`l6UBxYCTJRAEh}SFY&t+NxY1{phkW)s^78Yr-a149hR-J$Eyw#dFhwqgu>y!f$$N zR^22S!aZ&AhGW8;R9(lugT20+%ZO14c<^*Gsv`p7enl|Y4CP};%=c(~w4RvYzI78+ zR&L@rkOUcanu`_HwN3iFAIFornB-;_pw2at_uUf4u|lQlw|^jH6w6%xYg`54ewS;k zxoNC1n(0S1;XtU{jEm-(bNb%jSD4Q<@t3V;s3>9&0x~{K%Bl97bFOh4{VL1J=`0$V zFNZS@pJdRx0BUQ0EpVnFurk0_26DN(qLJD9I~gU+{DlrxR|fhBUXVo_$t*CQ+e~wSjFbc{;@NKn!8^9VF3dk zlO1=LYyDv8cYdFh_!WJ-Q}PY2@vv%=BUkkq9*?qZaM`h;8F*_pjib-!3ci;Q`A{C< z)^Sp3C3GWTyt;JLwda3J*^;Ash~MU@r*5AUR(#ITK zYjHbmM;k9Q&CEHGKsma+P#$<-Jhj5isDZO!qd}FWR%?DpExC$g0_B;SW4Bx-6Pq}( z@cwNE*?-oXjfFo?Xp^6d+(y%EOkX_Xo$w$1l03_`zfq;PQ0CBEh&j(bFSvFzU*Hw+ zC(bXST*eT8qW9Ii=2K&2uODP7?&9GRi0Lwbyr@sVGs&z$6Zp$aCp7?=5o~HDAF^4} zaB5W9FY{A#x9N4njiiO-y?|29a_dAylvV#s*(JNu57Ygt{VsjI6S>=`O(yODHeR_& zVI}Yg2u)a=Vx1t6gz{w0eJ%4`aE6|jqNgRan|G_kcRSX^ol=jDQUPry(d!{EeY7p|7SN-5cza)Qvkfx!QeAtPZt#!o+EdvsQ$YDMg&j3gx!q3;y3~iSq78%ZS{nW?3 z^34l(W6iK&-(D*E=qKM$6NF3gHKjJ*WzgOffs#^#kB%B(%f;aPG0#HvnG>DV;tmHj3ZNF#8-8rI+I6IGSh5{8EW( zA_-_Fn_ME7aS7(cYm5&n;YPBnI_y0T3?R3H? zyTD29jju-Xd z%PFvo!f7nna9;OC(gl@aQt7sC>ochs2h=q84ZBz|61o{nYf+o6oJ%RUom8F5on>}d zP<~3D^Zh`J_8RakV_8)^v``gx#7a0>tYn||azFsX#}_Nx#NBry`b{LWf#LS^h2$9S zW0Oxk>c$eqNa{vB0x!ebSnEb3F*+T6~AkIDIsP2F+_4L;d9 zAtxpLu+&S9^fU9cVHZn#9JvpyY}BESV$LZ6Gkn0014x^*r!OP7oIFjHAPLfTA$bvM z?xOPA=(-S#U4-u|p$Y87^j-Uzf)XaYZoHT?!qVZbu^3L!{kH?(pQ&=nH>RS(GZeK` zK>BAQugEvnt=geeH?MD__C{3C=dM>e>qsvgJEI@qWLT~jaM!RrbegS9lpJQdr((`> z(v1qgxV{>n!s+fcZ{)kCYEtL%>&zoV9!v%N3PPFHi=6Ju15EmVjwcKNV2{vZFJF6M zAfKjxkn@TF?*Ec4Y@^~d__ZM(kh+7;wRT*#Rwa3NizByThB;zn`kBl>Y26Rz@po^v6FJW9^C@K7e$>8wZ2*YhFR9{j;T@13LR-w)f7D8F$;?oB&6Rj)dbE8 zIm=M=j$waj@xKzNC)v(jKMrxKT`AX}`^0bnC#X^f7@kcz@re}I(duRQZH}Wnr*Mb7 zPX$$*&a!Kh+lAveQYXDkIO{vNDzbZ zf!h;xFK@zC9ZPG7OZfK602bdKgQ>1HA#&vZRmyb-HJR>fc3nlOE*cd9(M3c+h*YJE z*aAzHB!Cc9nh-)2fV8?|IfbalPm{8uDiz<>u2gTGwkvXnm^%RE{pxWge*<8R+}B|Mg^~ zASGw+DDUMGVN*$52TyidPgYNfuG#lLTP*)*=rNoBB8ON|y*0mde3@J%KILMqT?{h_ zT7R>gf{_92w!LRGH>t2Pf&!kGXoz3k(ZJ2cahN&ZZ zM15>2EHKJ!B6@A=hJ&DtZ)BBi_H!v0_-)*;x4;vz6}2gPvq7%w;)+6H-=P_bj!C*x zuC!z9X3y9DX!J_X7}hVFbHne-S9e_RL>!Zp)$%=)bf=O)A$+AiO4;cXJKW)fuI;;0 z&~{9=rN2;Nt5ia>s@|B6FRd;+R=m{O9RG#*Rws9uagfkL!F{{OQ5Qz}BtAP;SK7=E zO=UXEUY?gApF2L*$+A_Z%|*(c-W*@qjF65yNXkcD8m^&!z3|6Mcmz{w%xR^^QJ?J= z)5m`=_ss8*8aQOIyTVjrEqVc(q_WX1S7#~aG(2RLlE2(M!4YRWw6fJp3QeTSQ(pvX zuV_gxcJ6fAxtkgJh-^57?TEvg_4c+7Q&wTJ$`@4+x^zkvwBNQ28PN2*=!_BvmnvZ2 z#~P)IN>;=R@%`*jjigf=XZb`}#;xhj`vN6Q8YZo1vOJZ>+>C8_d3px!esY#{W;U(h zEo$0ZS>W+y9mL*UiVe%7m^rO&f}f*AN8K+$u3<$09n6gD>FBm*N2y|3uqsV_HJ#lw z^-<}5SMEUqi(sa($o*s)=qbyl&PmmzUaJ>MeVTv8-1m4dwn)FD?LFvgcmpj7ld(pN z243t3G0opT2IJqNV8d-zSwv)Hk=R%fWP{$;u;hnkYvkSfEy%jG$#JuVciF&Y;LXeN z#IfN^Ubdm-Y`^K6{0H{lEZ)+>Yl+lG7m;rWpzN)U>Uq=Yc&4b*dW^g+`CN_8#r6v& zy|^cSV%72?I`@ufpHCJOjgx(!Dy$mWdk>v3VBbPT9@^N$93iLWEqQNAyEiE{DPWP7 z8RbcBpVA^P!1>siHRXuF zeTXk!_q@OGjfeROQB&T`fQpc~C()m>dErlRj2a$mZ@sh&jxj$J15WDj&7eSalr>JGy-dGquea<)X{w%&nsjjGX9RHG|6vUY}&Az|f>+Rxyw`eWz(dH}K zypQ01p=CA1ip5F-IWJZkD+^E3kECa%=zpY0>YG{KClBxnJteyrAdK9|W>0VAZ3j$` z=b(RI*RxnETy7ri);5<5faBo8J|wd@9KC=#quSYhfe>%@y$G@3X`z5<_8L?9V%6(p z`>xe@#`ezM)XJ3-D)OS*gO^bW=F5JbdIvXVFD;tBe(-rpV4M0fE3kf2NmV@Asr->~ zgyr^RWHM)t*zHIG_+${Xj;kbb=7<+s{N>)FL;|Vkry6q_JNL`R`d}DwXsV_?Eiu<> z?K_0{Z>puJ$e~)=kS`imM109h{qkFd9chB*8M^xANLr(LWk6V!kQy|qBgR_#eK3DQ z`5e{s9gpajEGC~Ou$a>AN~3*BmyOoV(ml5iC-3T(hLF$gO14-^@u#hjaklY;3CgcRasC-c3i&l3@ZByWcD83YK^c(;HXrM@57D!V-zRd z6FROvx9N5U6wyl6b*M8Ct!vpIeUr)$Cjq(3W@@oEh zmpp`3Gp-V9+rSvR3Y2i6Rq8CS!1g-J>#qv|43<`Mc^Q3Su4BTWersARvoKK2kb%Lv z55Px)ddP3z3vfjwJ|BMTjw&OK9skn39+<~`3~5S>%Ogv=6W(@yYDy~x_r?}19t@kR zK`A-0&YfT1>BKCH6_2>?u0bLJF9X+CaL%nOsp8=+v|AR(!A!p+}NHWgR%zSfD>TLqNXr`5a z#E%Gw^}A7%ClgAfZtu6PpDNBs&7$~e1ofEBI?K}3ZDzR0F6-ptSRbzyq4?j^?o=5{ zykfd|UQZPDsHaW)lDCu${|?7C%JwO;DYx~{G7sLGmWe-+wfPUlg@p2!D((|81y3ed8=7#otd+RqJiO>0{ z_FoP(`$hZrHhRdu+_@`B2*cgXL0dM3qP`hFzJCKF(MP~s>Cbq(3!hfQEmnzcd(QBD z4B9i{O%4N0NdyG??wnjJES%|*GR0_=!|h$Oj;pw2DM>orAN}AdHh{ucClgS+$M5Y2 z13VrD=RXM!S2nXeNP|Js7`gOH>%GmC_M-;NCG6>mzK#C!11vCj3)seg%KYwxsbe^E zx}5pu|Fum8Hlqr=$x*$rRbMG;ZL)N_Jglk9uL^o|jB&2(i`#bvBott?Nbym(>UFaQ zIdcHGu`4RWY%RJ4XHUVj1_j(YU+RcEWk^4!95g|07?Aj?AQC^}MgumHY8OTvV4!{# zZ}SN|8b!UoclvHrf&HJ@ zA&kVOaOI53J!iWBu?2y}3IsWyUF26P2tfhKaN2oZyGP&CCIwU6E#a0+t$BA--)G-e zLznjV5}@zfmw#L9@c`dv$95YuW@Xcm~y3d@P@6= zf}nBN7IAF>_F-jpS|%{O;F@NrLuNG_4S4V`h-@SEbhz7dQV1-==Fg0EZFf)SB!p0hw`%JN|`sp{A?{O z{qR(>qP(G()}-sKf-gX6hxSR!_`j%6iiY|OTxO|$$2n*iqrHU$pn-x`t z9fz5ABFlk`+uiW@U0vU1ZWYPj_bVho<4g@y&bk#-HT32I$7&8h<|E1ZfomOH8ke9Q zxev^SqzJ6v?)-E=WcN2oYS6ibi&ZoWEUKQ5LY!56o*~C;<^Fbxes>P&_4=1_c!n^? zAc42n^0}Skob-e#-AWs%bU@QtxyE?^p`&j;-2Jk0JP|>}=5WjlQ-7Q4xTzC%vxHi$ zavAU|2U36jXI;mK^WgSB@fR(su3ozcTbGY5VU@+hfH#;6-tak|{nDAMQ6~g=)j=-l$u%xyd$xu?|C>#b zxdB!ZPoPFB>OZXne$%&(*-4x|DR!T6Q`{gbTWls<8}UKB!}uBZ5TB)dcj3?#{>q`! zY(bO90U3JsefKj9H6>8}gMO9uxKlpIF%iLcGxWVulqRB`9sS9|fsqf>y^y!-aXtuf z2V#6!)l7eA#D5$Nm3Qt$)p))xkNgNnmtdf!3i2?M4lJn${4w-R$i#27WK>8r{h;}l zCf)4tQ8+PEMa$%JDyIeXwpuZabnBM~%|`KczC;fpci=fd3VFL$7;+Da!_Su?7cABm z-QD4L?vsU1=joB(q>QZ1!}~nzJ&R=|Wv_)aNW;|JF)?P}5rqGEIcFVP32m0IJ)IT8 zU1H}>495UNBdWn&i)OU_tJENMmnI1jD+N7?!#eeHS>-lS)qOX~q$uzRwlOE^?(fa+A^FieysAPdnj>m(yr*bSnsbt#n-G%(>+s}jvt zi!p1U+s^jV03ysTc^Q(ld`bds+Ho_(jK2e$;WIZA%SwR=eU)SNb)iW`@X3UA@%s|ca?4yCof`^V`J z0dNs(v`^oJl9>vRTQ$Gi_S|3V0xGmXF(Iw4^z3wFHvyDC0eCqV>&!i?QiafA)B)?0 z<*^8>3P5#9aaZ**6J;9sb~#A>1FXvEJ1)|oH%}5-ZSPcwNP4Zq>V@gkSKdq#Wf2Pp zLSjQxrT7^?apa-v2V(nyNz&u9rB>hZs`ZE7-$=j!YjecPjrix74C5Vf$0JuQlX9ce zx1QT^*ZZ0Qn)O(qr#MW;ZpSEF^LLBRpooO0CqzDS#QSsk?9=@ZqF+f~UAI$6r)B6L=g_=xNMc5QC9qYjrj@W);{oqSkvVd z#Bf{JhMFMvqdo}tT|to_iV>k`Aj$W=kVgT)MFS?_|0uif1${nddc159cmlZSM;G%y z3*|0t3z=KZ7O3Ciq9u>1eEtNL;*9TCkB=}NxoD<(esY}R;hChc#dW^nt(EU+UQQ@L zCuyO@flwU4(B)$ zMy=~gF#Ea^Rta?CN*npF2c;u0=r;vVMOy8D83Lg!VbE`hf4s?GH3<>6C8OD{9bZT~ znqCNDElR>^jQk7VqkG+WxaMye49!Rvu!zrgHU&*(n5?f*DnGkqceUrjywJ~(jtt|1 zSx~z}Rm%dcJRrE>PI*v4@*8OX7}uqksQAs2onptW=GGkF4CecwN@xJ6=j5dOGQ3(p zh`*W;6xePx{^c^8tDURoCEcf1EkJxfOLxx!P|I*4L>zBrGKKw(-PHHX2b&zbr`!5{ z)nOk59}EAgW5SPpC1}|%uAeQ&rI~lvk3>KW;phgS(1OdPref#?j(Pwa&|d>b>(*`hHgeA1=y_*I#s(q4wPGx0ptJYKk@09DAlg9ebEplHmEIQ@CADW)Tu&2 ze=bO5YzEfzrR8@kd|G%lU0%lH{SYja)}OM=upx2R?`Oz{F+Wj0^XVM)1xVTjy6URK zWnOsdHykfm>Y;yITCF#h>tU!$=Fp3e6wa``Ao!kZ*y=7=`=7VQ|F@*@A0t@6UijIj g{eLPMTHoE19Jjiod(PJotiuJnYILRW^6f|e0;;xD-v9sr literal 0 HcmV?d00001 diff --git a/code/datums/ai/learn_ai_images/selector_example.png b/code/datums/ai/learn_ai_images/selector_example.png new file mode 100644 index 0000000000000000000000000000000000000000..f19e8f57a1073f0d99e9a4e950e002e14d6c2969 GIT binary patch literal 11099 zcmeHtWl&tfv+v@;LU5NX4grF@1YbNM2?P(p-Q5Z9?hqtwfZ)2gOK^853yZrgayS2a z_3D0nRj=xPx~FQZYtEUS>FH_tb<0h zbzsaP{^oN?WO-e~lups-vrgg^8O*d7fAHy;8REYss+c8?9MX=Dm_T0#(hZHsoco>} zERH<*b?kyZc;@=--oV8>2IeG1nFGQV1vB6JiDhJDw03+AhA7DvX<|Z=@$vB!JTzDk z1Yh}oj8t8Xk71@`%gL(Ew|W|O>l@-p+1YV&rbTUS83WMq#2O|gbv07fD2uht`RQD<;9}L8ud_3L z0wGIsxMJgCt0!S)#}fsOI&Yv^!(3UK)>(GKDV=j02oCcsr%h;PxHS#|Sl}Ek1XBk-_9JG@aCLg>;#cbFyWp%MsfRu$M zU3E78H1;5M686MTDT1wQh$DPuf$&HL;uZ7cy2qfdNSHc+Y}w9q|HatRFIs z4fu8aaohGHvrP1_?Zt;B4fM{zjKa}b0`vTU0tet(K}3ZydEiUV?zO@25>SLSw5a&2>)EN?Fm`d?dVzA=4g_x>{hSFSsskjYWE@eRQum%o1aJ=&qgj)~ z8PicfW1hm%P5A(YhNRF$bma2;9#clrT<_Ov;H0BZqH1MehMk9%3xLN>@Rf%ye(^oB!|6<6fY z6Q$^+)`(?*O1+W$akEJAx!HCVB0nd*7}tu;&4X1!PqwWOc;WeU7FuSz}{BlKsB z-D;uVBS!?tU!d~)*Lgs7zhbjx-(1BDw2bq%zfiUa-f?`q1v+6~VUbvux9iG9%q+=@ zdU7|3WQMl)T9%#|4JTnnl?t)!1A40^bvu0LniqhDM$<}&&Ko*DJ;DqbA`ru8mpjs^ z7Y5gt5r@AgcI=FovviRL)km?pnEiQPwgkT}xdS90N{r;|V~ncyvUTs~k10fOgb+mJ z4LTlZFj-a-N5M~1Mw5MYB72UF^q#ACFi;c}rV^)x%sYt?#G=%G=TWl5+73uU5X(UA zY8rV-7eY||O8;_9!2ZFEgRuo~yGj%>;dVBXCd&v5-9U;=EPL3)j8Fd!?ZEBRHsDuT z>9?aAEk|0(M^U#OoESKUUDG`}RF%@K0`?ujj5vZBO8xUOKaO@118?Yw&u!!AZKki# z;Y%kyVIngkjr8|YepUvzc3E-j%YDj{`f{!?7)Jw>`EmB5phM58iu0M}H4jm(X3TjqNaVS%)78Q7Bx`z)^ zm`C@9Ti_N5RVByyl;u&fR|OHUc@SiNe<_8}!EaEtSU{paEp0V5J);REDU9}MIB&dT zecA(L8Lsv=ldkQIO15@|@}FD`rHW%Qm{aG35zt~W0)aqdOe{uY@_QR5V`>pvMj9fe zq8k>nk%g9GZboBD@3!d`C0W@wO8K&0xC6a!MwC5-`_=cP-)0Q2;#C@b@qi0A#tJk> z@P+95HwBsEtq)v5+3?FsJ<`$);L{*xNMvu8bi^f)Cbm_(H?~jiQ2T*ffg4Y@U%2Aka5=jy0i)yHeA*74PlE*`j%q8i=Xqn2mw( z^!O=VeU2}SQuf7FGT=Doji(E%D18|hB+gVT2CHMW!wo5FEu*_!@=+UnH1@e<|SoQ=8;*fb#KnEGzlU z(JdzSiGF7ywX2EBokZNa(9cJB(WkSyHf-%*Qb1r^GD6@V_-E{R(p-&CKD4`^Ad^(A znzz!55)6!S!bk&5(v{9oB@{Xdw>Q;Y}@^Ip^cy1^6yzA%QU#NA(0Ejjo z7rz`Fq?lY|aS8{?7Lk5nNiZl*?TLz7sBTL4a-J<|z0eR!-`NTEVgY&y|7Q!I3zGsS z-?i{KQ{v+blZOz%OtW$BQ>*2NsxqL>FQeOITIfR+rQ2v`Q$&S6YGRnYy*S@+c&XWR z)jx6Imea7TE6?9K!LI7Qc=%(5@5XKP*jEaDdyL;Nb!a!18D*iv+toz|Kfyo+p^Rxk zZ+JAL(=rNMu0PIXv##S+zCZ8F??;`MJLw#ge8Nz6o9UXk>$*ST;XrgenCewOMNxhbw{+2Ix4= zE6zxO2O2YB^0&jHAE%b<57-OY6pNvGX(!oP;qLz4yNIb&?Y9D=m-;>{egf|YAy)>$ zr@g#%x7kYvy0ZpPM7RSqeb9^UFajDrb;YQ(J?KrZ0f9$EO1(Un1kb5I>Q=_}UxfBB3Pq9|({rC=!R= z;lm9u#r=!u`K}%;QX11Z8r3*De7x>O9#DvC|cZW9I+dAJ~ zlQoX3e(5REie$HD*_O&V>+QX$%mQ^>`azkD$wlxOB@n$uT&TsYai|5O1AEYszX!`} zAwn1;?qgn~_8Y$K(&PACaPfXi3K4dSYpo5UTKe=DNO)aV-U3R+XFZl%q5Eo{%dN@i z-k(C%-@MgQ&R(<;Ab>MYF3ng~>|gp2!_*M|>UFHi z%G#=Ar6eY1GwK)6*d0pAqAN~Eqbj%TmNdc`_3A4>5p-%7Ge`l_Pjf&f>Z9lzr^9{J zeojfZkf&lu(Y3m%_iXI+J}aiM3oN+b!~dxusLnij^0*Qq@-th0pfwNR%%wY z_%@Z1yE|fzB|%$*C83Fj>&M&^ztpj*=Z?xKQ`c98XT|cr-Q=W+iBoZ_12^C8Z^Wtd zHENDm8!auT9rVQ(1Eh2Vntff9+dl-YAK+oU;>c#IF4h2^5g(FcwsU(atKC|)zT}^g zT)2gIi(imA50N3jT8qoBY7rv5A|jST?Q#!S`vbOfeXo>|f$Y_HvdU4zj8ACCTD&;S3q$ z*X=zRym+e;C~GW>*cnc?)bCt=UDy^o2d8s>srZ*_gOzMBoktc%2GzR=4_NeFrbJ7OFY1 zm~rgYiH^dKT_vJxeMjBaeBHz);;KC|wd1B_Q22Z&5VR<@Vmf64!k z2#S^&nV7!TE{^m8fy_TOfWXy1f1MJOlV5ahbU~n2_z7wV$tyNGLb6rJBxwTf@4c|( ziQ+RdEdP_K={_H32S79!5{LixZaO5U{FLOJf;@fsvI#gpJ6`n=8~yjEv|*5~Ee9gc z%QcJh6okoSW@VjuWR{ed^J_%}NJnHVz3X~`g^h*a;JsL`8GwzA{nzi`I34bg>>Vg{ zN|GHrtS4glFW(WXZ^FQ|ikvB0UmwQkr>OyiZG#cOprIl45hL;nCNDb{KKz$~mp3U0 zAFWAGt%1mP)>W>+LM_6jwCYDdWTcY55F#~2F!k}{#~Rn=xzp2JMFvDn*4V3v*b44L zz_bKj0*ZD=2wxHNBm(uxsDT~xUyS0u!iTBx^UI%<_2g^(i#A0voM7cZSy`{>`DF=m z9R`GZKs;~W{V(}{5JA_!GKmpqa=YbPE7xX4B?g_KAZgmLoTDQ*AQ)r!tYtTk_vkfn z$`!>4-JvUlbaKNjDLGm3>(|%BDJe7a|`1tsM)!cxA?XP{2>s1$p)X-H~ z+2+F}^_%#d9GSHZAF9}$Uk4-kGGU&GVhL2gC~h*F08`ZEmTovzQuW(66oIVwa|=70 z<*xQMU$@~3Ke6D4z^6MA+VY0sFPygUXIjgqqX6GJ0-r zd$=3t%FK!g_c2q>^FeN0#toQ1*o2w;^-Y!*Ld~WySd#}a=PJB|b<_zBkROgiQvO*i zuhXG6zyCuI7J;Q1JoSb}^f$!HctTbSiH?;qh5Olqu&1&)b>?+X!G5bNfAVkd9AB=d z-232W4@5B}>A1#udH3S>JVco2rIY;Zrxx8WZIDdxJk92GP4O=gm?)9b_k1nAQONou z{@BQGhl~N*AB!i11jx0TQju`3-$$@}tZwZu#6X2vEwN})#| zGs!x#fGsGAn*iVR1T@ZSn&NGPk|FzojS1t@P_9r$YCn4H(G*8F(8-BZeZ=UlBaVA* z#uw`oZNRwp8;+56W1AR8quM~;ELh=(lAF4-&Gyi}hN-d=a1mb1dCB^uf47O1&ho;( zoq(3Rg@Y4tTa)TmS67?&`GSwOj7JbZB2NmSKk@r-3Ftk3=B-%R%^IW ztAwT(fgH)(L5hkD!Vub5zUE=QV~lZ+$s;AZH7%2r|4Kx9 zm;z?zWQ5amdcJ5+4FZS~5fUYm4+qWCQuEsMY?h0QW41(5h~E%%sPP5J>Y4_b4@_s&im?{cY+SjHZV)hh7)w zu-*aMoM}@M8lFq9_<}{|5_Dlc?tZf`hlo094yz5ZH?xTR(SEQu+O9vCu(iYIL?u zZD*GL)r-?n>={k-PmHrM{n+NQko}pUgOnNmdm_cH-$m#oZ6q7(T-VtX%8RSU1WHrW zp&QqN&aIzl4SyH-j4J?4m~x*a12y7V;TA0rrLCf&lb^r;lEE@2<6i8!0=dGw<~1+W z?bVWd$L88*`>_=+Bg^#(aCj(hP~%=Z*27;gEW8$6r6pEr7A?wtGAtVJ%t-9v_V>Oi z?OlJmSx2K|pB&rRk#%+j^AH{p3B{Ne+_XOyLv*(Gw5>TFRfb(}1m3qXbX9iAh4lp) zaA9#0pNt61(ZJZu1x(1WE81E?!=t)#op_iH$GES}xG@<)5o(!_`+l{F{h8ZD<@YE6 zdcC2|zdP#vDMp2zrl^TMatMf&x}tFXh$31@q+rC$GA5mJ@B{-%g&GC?aqOx_{5 zb} zpiMB!iI*!b*V>(J=`}{nH%2Bi1Cg*w((9)dD>cJ`*K)tXL(Bbw{?!qTTt{6;mi%6- zLF0|Dy-T_42($67;Ei}rJq7Lyu1#Y;WH2{+(8b|SrN38b$FVqiY<_g6mECk8jo6iO zjoMIrK55N>JDH(W?N0fd*W3v~&S2hO4n!r3!UznJxqt9W;^RpE(XvHp8UG2en#Y96 zFiYZ9EYaCY{}}U=s$*#;#uD7997bfph@Ii{tL!rny&yx4odbNmp#wEfghYvK{ zVK?Sd#n|3KG22%L*D3Q=ux+K-S?h$lCNekv`7tV`={0kYmZSlqwmVzOG|2>c&m8Kk za&s>wL`E})j>U*biNqPn6cbw+ZN5BG{Dj{B5Nx;Ij{Kz|?J#C>6GDKyjwlHh!Kx1I z7Ch=91@Jc=tG6`67bE)xh5BH#niz5ZA{=#fN__stahUL}BmRng#Vg^AuNjvp ziRgy?q;|I=hE1CXdP^x072=A{O0r%F?Sf#2&H>5NXB%fF#KfcVpD~Vc6tPKYg-}C! zg6&^+_3j2&f_o|kU#EvRV{~9$gax;}oZ^Y*xgklv(t-Qs-*BzthI9tb8;y~C+cPzD zpxrk*BYZ2lVD{;)#rN0_*q5fC!E5mo+|oxf>-1B_=9?rz9nKh%-imumLU;P0btKd=6=o&zCd zaq*2etkUEs_+r+&vMa;p_x$9{;ommfWd|nFCJ!B#I-3i)r|F|NRe4Fo5_oZU8}aGO z-Ji0}?Kmzn{55;`vv)MQXUl5CqcqKr0_W&t<6#yT@I~*Y@4L^}?tjWkMZ`lPt*xb* z@LX*QKQ42@ki>BkB@#&_JzRHHdpyc~$xG@dZalQ)4fq7@wZ_DYkf2Xd8&BaHH3H1d ztLMvKnfh=1uNjUE+9|PuC3kC~>petDZ_&~x#-}Dv@b4j796@lIXKz__?t~ZBFBpBT zZq25CCFqHK6Pc~Af8*)Lh)wGej*%*1YB$HKUYlmhRSxyf^NkHHW-?Pr?}RTm)}982 zdE7nEO;7B6T~scETy-gUSgtO=hl`z$IF(vkW$=6Q?5bwJk}y+k%NodqQzHgsVe@eL zY}e3kLD~3xQ|ME5@J$|%@{j56=!R#RpIkQOYpcqdrT9`6Ld&k{ZF8H zR}hB-fR514&R#y{?9{e2NZAG_BHKfRZGql#@CC(A*LNH`l;h@>Hc}=IjAKI#yB`kO z_yJf*H|?<~5sQ$X7@9QqAaOQbybRqsQNaZno@cuSSLdq6!AsGI-Vb?L2#+7JaD+J{ z$X9i9YHpao=aStVivCi|?FfQ{p)}0G$v$AGPRBar;iuwmd40c7{|w+LFK1wMd))K_ z5{O!oz1#jSc^?*iN4I(8`GZQL0wuJ^sgO6hzEbXZCT8Erap&fwNKl@ECX}mZHFnrmPJ$xl-o{ugb{+7EkEFFz>oI8>Donb@Gaw4hPOT<2pW{>N4-z70+ zX31fqWzeoX@art=+#O|V327{~-+tQB_)}YEC}9d&?3*q=U${9olK%SIU-y}_vK4O7 zR8jjMUUOLR)O4ure&69|s`DK1d2{9Z;tI^ZTXOlfnkXC<{q;wNpaPZPi~!m9a4LcQ zAl)b9LsAiFK+^9yzhT1uaZL}`Q}{X#%1Df?@h|m|#CWZ_jsf2eoj>{KSpB3#Z_RGs z>gecJFWQ(C6@Op;d36xh*r(w5Vy;lLUE zvz5*LZA5i!@V>xSti=l#vD+$?f-k{^7tD{5mlEc0Ar(|+A)(4afnT=@u75Z_U-ZLI zS2=Y>$jmT#8gW+(yp=r>zG*vjU;1QMmWKBnp3<|l;S*o38)TXj-pnX;>5bO-B|$O@ z<{rBuST`!%cS8UJ`SK(KqG<$h(xxLMH^4vZQ+yd2>+0_ydn+aX3=EmZw z(XZi5_ML$KShCo4+8;n;mSSF{jYeyOul^ubUS{>J0_T<+`O_;oT~F|@@YX<-+gfbWGeq2QB;j2-|Mcqkc3;aun5wnfk+;wE zug%gO{)z3fyq6N2puRr`;#l@&7#rJ{tA5?hM;RGzywxXr9CE(!n+Dr&(_?W3?E^!t z?T&;9dFi>WMLWdF?>xi~w7X+$V`Ho7tYyrvt#<}a@r+Rm3WycCG!MMOx7POaKD8s8 ze)ayBg1k5-fuQ+>@FFpZA$W%!=XRFrL(hq>Mak{=%zE{SCXo7XV(tU)gXFzHA7C{> z6c|;v6>NfcbUK>V#zKD4Og{#L@P3!@dHPPU-HYV2+#d$ z0!~d6*i1+=N|g`H73RGf`=fzec19Jl6F5CW44l%Hi#1drs(eyx?25XqL*pg%_@)k2 zl~~4nOkK@*|3+f6-kBBeH*j}>;N_!n8$_vSe1DWsA7)X*j)aVK+Y^LdLB5XMnZwm} z)*qfDyThxj_I*4iG^5AH$I(+HERInlz^UTGY;KYm9QNU4!S7fuC$PDZ^@}S(goOa+ED}SI&_-8>mlNux zP=H9g_FH)*QaOK9d_jJgf zib(WCyUg@NVw8)IdO7O)g9L1SJ$ds;H#uCczdsx9DYN*7&|iS{FFx$K3ABb;)UG3E1i{cW?HRi0n$g z`}z2AuqI?IG#qL_HgjjfarYXsUu!|}IihZtJ2kPmO!BGCdXM6xb z1te_c0GzOo{1C%;=#lWp@OM8g1#|&|5AWUZ^VV{__5a*3U!J4hx6eN0wIfioujPCi zNPLUEo9H^6HbkBsG`g-RGpWSpHg49d`pu!^bo^r_TDtOBCCSFp-3vzLsF4mpy!4iq z-^hkCmIGs8M@4wziFlCU`GS|SGpAP3#!mtu47%VLgG>U*_kZ&k)57R6>r~GWTVHfJySLQ|p7(^{{0N^?(AqGkAYa#$^14#G=Expj+l>Nco)On91}rD3EG0ww z?VQaLU@Sk)TonRQ)1C1UXbz0HQgXctH~c726hGK!;_Hosa_`%{8Aej$KE$^2Sj>Hx zYG3zEs5H!Mxr=fLx5aO-1L3r|^5h!&hxr~TfyjiCehVM115F(W257zvuKhi*9BWR0 zSdI>Rs6ToV{dwabqfW|Mbm)NfC2fQe79x7*V#__DILmuMtjHi>!(73(_{7Rp%IAgc zi#$`Ts(VS|TJe31b;(=J_?6;bZGMpxGp?xeS>;LuhQ-nab1U)BTLGV-G$Mkj*Op7# zR2+E@CPP*a{PlJ1mwW?p()+*IyTi^7^&%?^l2dJedVpSAI=~whb3IA#Ag0NW=lDJ} zM*YMl(135}f@Ezr#D(^m--C$~(c6@cDku`Mz}tZABy6K+CW=2xal}KykbvHORVw`_ z^ry%;i(B~m*=V|)S7_XlYutvowzAQ#Qmm~cR`9?SS6yKX*-h3@)HS~_FTqiQHiD1!knR?l@#;Yj)7 z&gCoo^BHno#hPsJHylf?QD$GVnG-$oYIVLAaq@??Yuax;UeUAUo&FT!*Br-%(1Mpm zM%a(o86FtH_UZE71Ij|T<#$B()6%buagRtf@L{agBI86#B9k9JM$dCctcXGr<5NIe zt@acCKjNCj?K897e$`uGnliHDG{FXnh>&TIizbs)pr$t)WZB4HruW=;^@{+>!X-JQy0KEo*tI5g7d9P6EToAUdsLzf*;8Y+68YSkyK#a*1D zs|~fx5sW?L`JH7&8nN%ao|1H;v=qvXQeIdQQr1^U$ibDbZB1ksN-c{*7zp6z@-*@c z#Upv=YKPGz2}u!R<|DhBzgd!9%z6lvo~|*U9I?r+7I?5KhX-!fcw!$)W>#tl^CoWh zcuF&|2A{9R8Cnc}dfk=H@WgE5Ne-kxw?!RZqWb{&rY<+Z@u@-X^M=A-!W1^#pZmJ- z;u6K+0`lSyyrHTTiUC(MVvtA*Cho&_)9ChZ60ErP8uts4gx+4woy&a5tr-6Do7tc$ zcVgmkA5=<%qI)Ck3`3p%J?{zz4f09N8b9%R{vi`zOY$E3JYr9lgxd%*E7P?HT#Loj6t z9$F%~d;D|Ry5=j$;|(=K#x)5(K8#~Vlfa&^{m+ksej|6#&+0ATX6ANn`73E-CdD zWTiRWrSW4+)SLjVUk6h%0^6`M5Pbju literal 0 HcmV?d00001 diff --git a/code/datums/ai/learn_ai_images/sequence_example.png b/code/datums/ai/learn_ai_images/sequence_example.png new file mode 100644 index 0000000000000000000000000000000000000000..9eaca19c109a0c61714c584818df5039d7112e70 GIT binary patch literal 14203 zcmeIZWl&r}*DeYPt^tBu7~FMmNeC{1kl^kC26qn@95Ofz8a%kW!vMj8yABfE-OliS z=iXcO)%kJ%ojO%#s-}APuIZlcwY%5zJge6ZSN$Z5jX{QifPjE4FDIpjfbfbR0RfR0 z4F!G&5HC*-FA$y8WF-;GM=5sU6(q|K${!FAs$wx8jFI7WbO$+YX9NVCo|i9TzkQJz z0)oIBd8rRyJq!+)y?x$Z<~%>o{y;;gqahAMLVv}Wv&8MxYCNpj7vU7+MaHL5%+9X0 zc%HwRu0WVyD7BC5pTDC!rNb$Ail&pDXVA_V5$I*oyGRT`3!|f#`}>}Y80`%Q&HcTp zg_|WK&6zJ7obSu zYY+HO8wJ~Z?`+?(pe8Zit{N3GIi6K??}dC++pQ)Rg>zHQKw*1xU`7R7&cSlxC;$)4 zkz+&)u;e@Vv)Je`^StA2ps|Sde#DXUGx+D5EgJDZA6@cLzl$BW4R7xppO$d~Ce*#8 zuMZ(RdQ1QlQm0+)2bjJ;0`I`vV~LS2Qv>T5Z=n?g*-zc@>vpm2{G4$tp&sZYqu;xG ziN&?0j4-)EwMy{cbs!n;bTEnVT`z;vZG}Oe=J7X{7{2PdYA{M}RYCX<8QPlzi3vGt zJtXuVMyJj9q@h=S>w41a>8}JcN6)o2CXxBssv6Gg^y7y+~`vR9`mQR zyy4KC_O4a+%M9{u!N`1at^Op}Qf)(k)RJ76Omisvu-rf4=ZIYdpYcDcrp1C2a=t;y zjWnyqYhjL~Cc73I1D`wb1k^MWT!ifZ{+TKf=I`xgVBlrBxBITqFr!6AW^A;I_KQ|* zD$#a(ad|PlL{5Oq>sOkatml+8J;$$_UwMi`AC)W4p_BI;B4J9T1eJQttDR5dW>Jqg z!2UMSivNPPj|}YwEeREM*DN6+h*M;ZEy-?8%;{%**0TU(Gmc-7}_736#UEF76H(u)l+ z>5k?*SnRISBt{A*&E=L|m+8(5jCLXQwuzqIr+;dH4rR1>hY(F4lCE=IX=}S{7e2_W z=QW10N8Sorn!PgOwRn%j%#D{o_yFcy8FVL7bU&4H7-d&1^51t)f7mGNbjUTL<;d>p znE<|O&TdVQPuju+;-}%v`e43y$qW4_XSti&Crj#2hkLxb!xne6X`AP`vzX4iM`jaH zrnKrz|G+#amy8w$G&qs!n@^HsvKDlW_(MsJ$R*ltt1caQ$kLlgCP?AbdRgc3%QV{4 zyZm%4BDk!6S_5D?@>^Iv{45#!2Z7cF2aQcW1!IEH<5PY}H@lZw4diSlfSYi22gLH(Znd zz%HOp51BtZ1Bl;Eatf(62^i_38w*qQnY&QJ|88`DcJ0z>^v0iIyS~t8$dwXuLa@yF zV`b-J+XZG7Ls^!4>au9p@@|S{+? zStqu;#PCSZYmEZ8Nz6?@Uqa3$!KswzbB@NV^3EOQdZ3}Z5{}}NE8sMAzbHDAe#HPq zc~xe}82fVCy4&}UcrMtY*(oaPE)-q@s?Hn4L^ju<>y zyLnIZBtVYaC+eYgnOQa8SM7hl{^2wdD~oc>Qxif4I;QngeU4*+r++Rkt>sAlW z7pXPneY@%BoxJV9^=&b~eN57uo}m;>5=u+aj{Ct-&vj@HLN6oE_K(w#0mAJ!bKgik zAJ1nDtgaO}NMaEm4uc;*Q@kUdK;@IIqwporL&85M-tr)Z=TJ+zoYZcE0QEjOxy`or zr`F{0YK{H0c<6}6I~$&shJ}&~;9~nW+gbw|Ege8CAuzgeiu3eq1H+Wwp#NNRKq=AB zg*|&CS^CviSRGTVbqzRw4mE@%?GiD5`jUgLz3<52PI1lU!7 zVy2Go85?!+@~pfzv6+@{nb;E#BKJGUf@7G~TTpNQQw`zkQ01PE{T9EG<#>~v16${l zL+=N-(ZvM(A@gTz1K2hc4*~S(EKW^z<&LNy|8-||HD%Wb*tKHPw$= z@)6&seJPX$O>~w4@1}bNkA2eBxvZ9vF$d?){qV30Hepr;Vg2iL!s))i^jkXo-!2*n zjj1{yDJiL5;9HPXo-+QA7_&`vp$FKtf1h~`)!K&0k>ye?HSf0r`cZXhspjhJ>B(5__}`>k|8=arAu@;SU41*E{-xeNAb>Z|i2m?wRJ~X20l@@&7RNWZk)l z&Ta4dmS`1yhmu=@VbnX_AQDMG_0QvN^5;tMC*s! zO1`MPoMscFqEipRDWCI#++UbFT@<`uffI-34a^-$bZ+j`@QZN8SI!`1-6?@i$C@FQ z^;UleB1M67tW>$4cJ>)w*blWP=c(|lL}gzLVG2OTG}Zs>yg7> zFjT?FJaSKlrSF!1zs1<=ZAbDUTDtFlwMTbuj)`_b->CzY_P0M@kpJSy!oCc(61}Li z#YA|VJoq%|IIcty{|RnJ$aO>8J{LW-Ck3&ss7SQ4ei=2rZ-V~S#|TCXghJc|hl{#< zAzz1vV=rLykLbp5xUa^pm?iqsN-_`ixVYGr?fs5tL4R<$Mf%utU?302RL?P||Awgt zEH?yM9p@1>TFu`%e|bbb1F6cOy1FrrpT$j6FaH@Tcgb`^;2ozLmGM9Qgrz4RxqUib z@i`8uK$vT+$eEvPh46aHal#3R&qd4Pe9NuAWtB_ScLV~iiWxFT;^-O zrmOYFpbyW!(`s!TEK?FQZSK0^KuFE@L<4Lov&olZ;&F zg7JT}CFELq`}zwJwBx6Kyu!kiWZT$0iZ+|}lC1u|!j4>=?uP27HJ^PnQhqF2! z*bc7Q#Ct+g|)a`V4h!vsS3iCL$@!sO=QH`UZo5G!Gkoaxd4IAw!34vUC*<@^vV zr>p(!CnY5r5E%W2lCVsb)>gyNKmG0wCM7E^5-BCs9R0E*7Q+ep!((Uj5vAx{;A>m1 z<3{7X78%*I ze~FKWhv@MQB#QlBN{aKS>DtcP`gaB-JgjywWjr@LHr5grN-Z2IB^;sy@+uOTZR-jr zVBB495dee1QZiR(BgYGu`I7K85KNB@p|XCwYhZc;@iKl&M-XiX$hOMbG}*QZv-NR*Vn}1ztzdG`k~FFoy$gSCA|KzNb0#?M{+(*@Cg`3Bo&Wo45sk%OnPBUI!Guil4Xkkc?68;zHW zuTe`f9?n_nYB2b(X2>dLhmtX^`RaNKVeUX&|Z5i2YIKgp|zHP~A^=Lb3J7v^z zyZ>z1)ci|wXv-A!>|jYTnh}-1{^_9Tyx_f=p}|rmM7pFwL@}RSPk^j?V$^v156Owg za1KyLAfY*z?k$_a1L}r}>8V#6-2EU>k&&yseuFn2>3!4&NlUZdu>es1@@3~oG_@I) z^)C+2#;vC|b6wYj#^+&1@n-1Y&l8VkV1a#a_c}i(wKh*S{cvG@?tJVtFs2Njc$_I? zUF!{xzc^Zk3=GHtXuWT@-4E~7a%6w9z%-$v%Y(rcam+vQ}C&+qVVk8@#uDeuHQiZ#|-)akRkg^>?g2c)h}{mjNf^q z7rh2Jo?>ln?QxgNm{AhxQsjnAuAaYcGx{a#Y~u$HOkU~tIE(YjTT|g@6xcXL==p9r zi(li->-xj-?DbK1bj=kWNNr7PV@-@QI^j@~SiivP*PAExOhLyuxWkHQ7KX*W17zgu zcNL&d7Z{t5@7XRrY2j6X*mnMoe^DMyy8E`%Fl?wXJiKb32kKzSDL>^tF4O)~5D`Bb zn6x_=|K{pqQbpC!aIE+Xqo-q^lY*jRdP$f|qZ>Arn?VOkMY4aYL!5-oiGxIHep%Oyf7sVHA9@ndo1hjqz>&CxcB~w^I`lB)BAYj}gGzmo z{9yij=XZ|*>zxZL3mtu48AgGJrib4#I1QXKsWRfY+q-KJ)!^-?`fhWMith_jC9)jV9LPOvcvZ`$*wV`0n*^s?-&5v>WNj z%bM6m{BI-VXsv`@@2h?A*GOd$dz3HIOxi4%{HE;GN8pXf;K4+yS#VIZvdsK3KdCvj za!8hs8A!mQ^)w~XLX>Dxa%4f)=)kGyb%DU7zH`uRT1rx* zk`%+dRs-0p5DZ|B@u&ngzoc?c)l3K(*9=hC?&%OpGpe==m~k|t)U<0Hu#`iF2+JBt zd=r=Ww$g*Q*7X?*iy4dV-^wDVp?CX!8tT^olpuj#2n2T7gO(*Hk3$bN6lFz+FAkf` zNq1g{X8#4{FtY)vPz#Vs-DG$R$zXP+Nh#kCJI+w?c6J`354$^f`^k?b+|)4d@f`_t zQ^0@EA6Zezc+0SXM9#w_|7d(u1vl6@H|Qw4F@)AB93)xhT!KwX=zAYv_QS;t+)(Doz|1KFiQ#^@@? z{|1xUBh$$)l|_voqR0dW(SWG@!jWTaPa=Q)=?Do8owG}$*~KS5h<=^%99UflozA+f zif!dq=qde#-eEM^y=EK)}V2Z_TSW;kea3K`|s5O8c_{?*JG-M}G9{gxb`mpW~3YoC|fkQfcY>^>d z87?1^hk3IPwbwd#b0(~IsqcCeTRM%c+=9sY-70{LG9*2)IdGzlXe}*JQi*y}F3`m2 z=LGjtIfqh8Ca~jX$QI#9eSxuN%dWaSuo)C8#|>@h~WS zkvF@M_6jeGmMdDdA@1Vce*tlKq1vIxDroH$nWKO@LKm9wvf^V~<@7mBbcX;?bre zaNYfY8Lrp9p_AyEir*>HBl%W7zAM2TDG6iHH%i}~iW_KmI5G=67}7+Isoqpy3~8oA zxobOGdFaYf}KgJMJc zU;H;**1RUVIM)B@S~$lGDGz~ew{z<&h!BT*Lg4x=IN_vJ0omw3;E1BzFx3 zg8vI_{3O7a38Po^bRAJsuvt$}K#y`QpTt;q0&)G;F%52_ z20MJ>75$q6N^g&yZ^e$e*A1T`+Rr;54W2!+oC$Vc{RjUNR?>gZ{c*E2CNNI3e$n@` zH5?^>(PuO9KmNa{7BgAs%Kqz!=c=V9HDLnskzSFR>8_<_P96Sxx;)pxYv=+_<5hxO zQS+L2USHN`XTPFn1$F~$FV3`r;6+T2nd@^Z2t*MQlj9R$UTiNZmh$Gd&z5)ftV?#u zNa-DSk;W`$F5yxu*SSiMa2@wsj6e(;+`kBwqdb8h8EOGEAj5J0(>*d+DU<1)E-Q2{ zxPpob#FNr~LQ|a{Df0O!LhS9^w8@D6;r!UssJc=I+_7PI(3G~5fl2hS*ag0(pp2D<*9+m zp4-AT7IT4-ww^0X2k-9fx)MSk_LV#bd>*HVAz)+ChLGISrZJx7(u;-TPbl;n)&6y+ zj-ObbMpY)>w|JqzcAapR(eQuoS`m4ufnF&)QCFQtxnC%&5bZj!d9p5{h5J?f%G^ z-6GV7vXG&|=GjwMLUYG5uCnUEx#%@CDm3ampyJi>c)hp&+T)_#oEhs|`0@U$E{3d~ zWFkkiW5WT+wGGDL&aJ1==E#t-xg*c+m(oj_?kkEjDvvDmhL~JXan&bzH3n{@;4Izv z+unuxJ;{Ptr=u3qFH`{g0}JO2kcGKM^~BN`qx|IHLS@nz0S8~uXa3#RL7o1F_J808 zu;;Rh?WAowHF;wUO>}XHHxrZId!0d+B(2EM73&Abbl#YW*(2#Hi zi?QJ3njhR{*>SZ)$9zv*V-&Kx@iLKrYZ)8B{F4~mR(Yqv<7!{UQj6;)8k<=;Q@IhbtjRQi(opjc z`uZ`K*Qk`fI0*7QG^kE~2<(;Ix6~X=c^6PTxL@9$p<7DSxaX*6_JjV} z(&^|!^TLsRbU!yNTDF9qp~&8-pC;Gb0;zIQbTk=qPXa%QtUW<3+rRJ_d8kHBH?vO(8f+o4Cp?yNaN0s~C@Kovo3*oOg(>6Y zYC+FREwCsjgH1!jA5XgNS5B2S-1)f=#153yxQp61@}9g(xYOtSa!+J1!BD+~VBX~)`4{j-3F2!&PA7FHT&Y&lx6t&erxt1`c2wK#d?>;wQW zLV_mc)v?3BGBXXagZtO%8F5M-QOFi0ePwwJ1#xZP2&Q*6!6T+h$`u#Ks|+Iasb{ty zUa6>tGCy`w%lKr`GA8z>3=Z?_zAwO0wor|!dLrUg+S2@bS8pS4jsG;ORU5%RikZG3 zKA9o~vrI~RE4#;6b7?+mrWQXzL5>WgrUtrxqX?&s%5NW|TeiCuHa&=%j&`wab&h_o z;r{l0)ij_69a{eSuBT$St7lU8eC(rCv)%wQjxhGQc&U!S`0J06T-~UNdDq$mRr$hp)-SYgo*!4C$|;DFcM2h6HN5i z%h)m`Dt++WlR4^ssk;|`sT*v9lUwf&(Quf~$74cuK z>gV3pxT(_|1DW#Ul&wb zUL-dBX%#x8ArLW5sNVR!HjZ)O7WGl+NVh$@R!F1dBaifCl*9u2;8@1{&l9gqqXvH2 zM0xfLCO_b^$PabS?oIaove62?B#o+~ziqNtwXJ(Hqj#QaQYN@|(loBF=wq1Ede=g~ z$O54>B{*5&jW2eqpF>bISd`I0IpESQIdjg+79JuqoqV_zmx zfKu#liqNv|(9iJk{*V=3-QTuaXrCd$b0kzTnfT6csP}ijZh2~HziZsX|gcU=tYwu7jHF`1HU;gTsm3-%(@xQ`oL<9j4YVBRzlDw;{Tg)c|FrI_WPJ zwNN0XO(jiUMYnn7VTrh+pTbQuaDZDhXXVs`;Efq1riK@oA>!gLfCQ=Qju@(R@s$j6 z^1YImb^O>j8Fy4}DCNZ?FvV$13VONtD$4%(5isGqq}mY-B_W4LGQl|T@E`mXp!_8_ z?Ehb}K5E1ZAHjCLOLES(`G{}Q?lv~R?uPC$n?cF+b_K4N1RWl_IjNH2T({Vs@P{Le z=eAJ{4K0EE%hiDPeD7@6Mz{fC-ne2x$9@hIWlN+W{)e>I{zW&DCM+y{DeAL%ua`OUOKXn!$1X+FOgUCs{G!*0Y~E zGdd_b3t-sg!)XK(Mzk)Al4qEwX+REbu_tZl4)@Ur(#`Rh*-8a^w$CvK{_mM;k1Yoa zb_&TH&x)cK^gk)fs!nPeZgHcIq~)!31jiLkQzzyFz794WTEly-ad^~&)3_-lk zT#&w9F|%I9@XH;_-oG_qmB@a`@Px=;FS|@W>m2gH34@wO#w8-{(b^Bi%!WMEed7BO zdx!`J@2mZAb%)6VC6n)G8vy*cC>Z3kbE+kWB^;T(Df-(p`WvfOgK!_xx|wmo?qX2k z`sM?<(_FR{iHz1Nh_B&KpG&Qu!vB<0v)k(hL?M)HusE1+nh#Cw$CH2689r^Lzq9RILukUurfHz{^~q*(gc}f{S>Z^ zb4C7XPcZbfq)n6;SH^57Aqonx1?9 zMtjU~EGHRe%!V{CW3#_~M2c-OSxr!tM)6E(IP@UmK! zB9)aS=_^Ku)^D-rhyRqCZM9OIZEu~(Pq~E)ivee^*Tm%EoeV@}L&piFC@MN!^`h4t&$8NPH z=+A{06I}7e5%_~;Kf5meJ450L z9BMWEBKPyowns-?rSb1U^&f@|0Z4EfoEIU_5YH|{Z?6^;@AJ6p9B`3CFy&aax1M{1 zE;Mpktw#kF3~k8B*dFm&J%!+GL=npE&Cr?-jIu&b%+vQPJni?{7F5XM(+MsRYl^N- z)!0`dvl2C~eXFiw@w=>r)xD1j3L=NrQZHei&&F$CR?d4}po9w)I2z{E`46;<3fabR8T5BKNyM5z%-60Yj z+y?O_lwH5l4&!&+q8CgGa6L=AV>$~)Vy+`5X<} zv>OjiW#>1anmIhHBik7nTK7E1{4(TrI?B>$wXQc|Rp7UXPS3g;T;-hlQ)BlYU@?0~ z1R4FR0DRJT?NknkR`^3{gFM&5^3Dj(7oBS7tgA?&wN?%BV{N6Z^4q`Y4ip8tPXH9t7hYnb*L(f=z*m>ZbP3L_ic4yl`5mi5&o_$z9 z+=K*Fw03&g)*$a=IcX}Hgif1GZVuJ*F3|M~ z{6`)7Gt(6`p28QDoI4^l$=6L^_WMnH(}Co-BkD2I*&&OhOIr?G@(}Xx8OL9ePJGXZ zM8=|2BctV#t0+U6?~NZoFu^wFf5h7+Geqilv+}?BbU_i?Ex+R;%e)4K3m{@pTQ~91 zjbCOKY<@HU+O5?(jB8)T51vfaeQ2rGSv4_rl|D3fyW=;gZ~hEtW!=bmiK649692{= zSI=zoMyhlu(qD{f>|#;)qmLPw?j>%el8luK!_*Qh$w03aFSa440m*#M$R1 zv2@$oY5!0+eHWh?<&UJ{e6&^GZDts${Nck;P*F0d#B77&*}E3g<1lsbtB5Y>JAH*M zE&FS?lrr_(CKc5T*X^3gg9=@P#DjZZ3x)-qZXYUGs$?$msJo2DsA4Izp1oVmrBV*J zM`KxmXm9HWo9GXH8QtP*Z?codlI!icvgBl)d)C^MT)ZDm;(_i!V$D=WuG)W~{V1#g z&Ih4V>mhzi8lyK{BRCd^Jne|}*J=Kx_H~bJwn51)0lx1!Q`Jj>@YFqY=B~Yo$>>pfzyzt6^m|j&%3Q9SD$4aZu&n+FIgLZ@f}96U{$3of#%fLrxmmS$|F=_&qgl7 zB)bC`Jl`$33_ZwZ?me1*_E_VBCA;?%&TE|K;(1nwf-#RI2%14A@LC(CeOfx1&b|!J zBLv)dJ!h7E$C%&}v(Wh@z!0^~QfY|cL}}foUeU!=kL7VtfyXvkMMCF+t6@_d^?bI5 z!90;07fRobsirM(=ypb#toOX=j{|Gc9A<=bHV4EqUdI{k8CdSKPgIs=W_CT*l}70E z&RcZC9ccIHAGGbZh1j^DHlIs5!5MZMd=NY?A8FM6N}&0jlTiIN270iE?>QdE!+<1l=%CkS-q2n>dPV?qJu`Y3F|Eq(I41u+c4>sDZ_ywHlIQfH7 zV`0~;HGS?`Ob?5x=FHAn=0@uDFkTt5`?OglL9XKwlgb(1SJLsb&LA?68WHD3I4^Uz z=c(pYGN? zgJ~wLWUk0PHcyf(aBAa&NSApk4;h3c_x#x~-9K_&RT~yu zHFJ-~5Y0XNn4om;lH*EH&#O4B_5Q<4Xp6BbH_<)k(!M5fk;6GD!iB>dxL5IYK zwX#fpseF9FjM5dSG?d$X1eMA;px;CgEx>3pPT$8QCcn7oH#XbCtzdW+!_`qd&Dx`Sm9CIy69g+x-g_Q6XWt`Hhu@IMudr_%k7S9Jbbp4}Xh zZu&i9UdI|K2d>hK=IvU?E=b4X8sIU#8s6~Jwpb&D`P1#1xATP~`J;(x>iQ2{FGR)8 zp3HPxL@}tQJ~as#kSyT>A3Tq4pS5UR8Z^54I~S}X4QMst@?-FZlWS88HTk$Hb0 z^VDdvIxbOy-rfL*-Zabnj>*!B*X=0RqG;af71x%en#~pRt`i1_FVscLyl#$<*e{TE zzBc+lKvf&vjon$)WR}V<<*67cxUuxEbG~j)E2*^}+*_JFfHJ3#zN<-kj&RNh*3n=2 zJwpL5|C<+hSvPT$74&TL%?LAwK{+i@xm^l|DRT9mz+;$ns9W&{VNo6FL9I5Qc4Q-{vAuqKsJ=vW-59vrk=e22h;{*D<|huSh?a`s zaM|+O&##htF+z?fRbq6E#+P(G1F#Vz>Q^FYjo; z)BPG{LXpVb&BeecIi@O7bgJ;$iwaeRt5E#t(_1`K+0DiW97lW3* zSMzw2l$7wt3bbz^EVs8P68zFqUU6>7kOEN<{1ltxE%=ke;4Kxuzb&PG1P=h&lV(Sr{;}?&kmw#r$Ht3l(pdBXB^+NY;;)l*%hxaNRNv)bpM5^0~;0 zJv|>Ln40NXu~x4JP;M=0&1sW%wQAmfcB)KvbYy>3!3TOtQx9LD1id<*-vCzF*@;3g ztmtgt=iWw7?6+)t-W$l}S)NJ1jXmwKkM&}zi0*8R4y+pXx~O=`(-YIZ4^rm+uWVmp z@D&Cc7vi~f86IAhNQn`wb+TOSpWx7N$S5*kqO>!60|Z7#1IF;+fiw5uxhK5CB#IPe zd;yQDqjl1G%!X1{!UTU!V1@;%v;aw2B=W!?{e#P+|0zc7a&Fa1fXT+%4;e*pMI5840# literal 0 HcmV?d00001 diff --git a/code/datums/ai/learn_ai_images/subplan_example.png b/code/datums/ai/learn_ai_images/subplan_example.png new file mode 100644 index 0000000000000000000000000000000000000000..6e5b6ae13772fc39d253c2821e94bedb555b0e28 GIT binary patch literal 42695 zcmce8by!sYx9)%eh?Iy(qo6bh(lCS|-6<{IAT2S3N=dg2-7ySE3>^vr(mg|mNDd7{ z$K8B?=bq<0_ufCwJ@>ii4`4R3XYbEvul26?UF#jg)l_5&9#B32fj|WEa?%y4iPjqmvJ0q}QdlX^+BXw!8l{3G1=D)>BCdBWtzU zDfO_X`rcQ0aY>Q^RrbW`dc@YV>OU8M6yrYlNbqX^t~8<0^SiN9^mtz+>fhX47nkmA zZVswHi`^MqyYfle@X0JHg?%o$K0XxH-VRrlJ9+^6@Ds}KSSr@yYr9r%G>u?G=@Ed` z^OCE9+XXS>V?6?XloQiqfk2B7{(BET{vmzTGWZ50aVG4snH%R7bpS_|YZ^pR@gX~$`XSoEVL^jO*Pk4Q-|mp8sY^{D?&tzBn3&a}Ecxg+t*43`ST zy74=X!DnU2&0|xmvRWZt%g|55xvl!!9xnYRu)sy^GQDbjI`#k)>!D0MW-bcIGRi7WjxQ;qYW86=Fh*|T23`MPM*wf%| z&!Ql>u^=@(`4p3!G}%ZUSnYPYcES14l^0_3^GSLqNNuHgUTuF6=7VUl8x_>b5Vjd>lWVOgGoEWK)vb#*qvX@-`(o945M=@>Z)>}@UJwRp zAr|-6Cp>6|HpKmn%Ywj{@+8a)Bm%^ERbdVKaZ(@#p@EvT*tKKETc@1gaxK-R_6yT6$b z>#vSYn2Obv${1O!cl}rBM0Ok=<^R3(rePB<2_e`l0dCT6j#KZREFpTawPmmn*DW*q zQ}&~5eW8xLqU59F&Cz;JoqKQRKIy4 z`Do%9y%>~D*HFid4K93FE}N%pKE(ANM)zsJ1UpGxjop}z@*h&hk~ba50v2Jtvgd~( z%Nf2$4}EBpUm2wbbg%e19Nm*&)WAEv8SvTChDY%BeFEXtYx%_F?aPn~=968D{q@5) z3y&;#3q!UBY~bU?#%sABBr=vLACO57bv!DJINY7I&&2PgXyk+u*1s#eS&7UdU%L?p zBFzwYB9C}1zhv5TGY6^buC*Q+qZt^x-mSAgNp^Nvxlcqsv-rF^OSY`rxI$NTR)-8r&Yc1I4U~E|Mhnve13hO_kJZfFl$JC!Uvf{<=E1OM} zTjI-)+Phh8T%MI?Ek-N$vvt0#Rl9I%sqvb532D2=C!-Oe9R5Na9>F3gL7DeHWOCJ1G;5){lr@44vpF8exI&X#Cp`U_>@SP8V=bqmih70*!MEN|b{}l7vlt|> z%P9iVqtZ^>G6_e9{Kt0j34morQpN3F`!`iTP+$DrYS>xlkf)EZzh@#&ss%EnUS*fF zc1e0BfxUva3E}lemTHS+Q;)#+Y=Vw=W}nG_jfrs4(S3LOSe6|Z%881l-1m~7p3K1< zk9lW?o{{fvQ`gtv#!3iL?{izQ*o9P}{8&>vCXsJnIsM|bTW>l)zu4usHQ7vh}E|&L87U&8UW#er;dA60wT@F+UfdpNuzgyj`ES}bz^_YIdG$pvuZ8h5@OS`NDv>q+` zQ*BJyxn-sfD&%3x3*Qfo? z2wrr>bc08=+iOsm4G`$@r7vaqwKt7o?DpzSSP+T$J_ZCnTA#bYrSs*jM}^uA7jGv- z0Jqi0U-EhCT!a+RO)R>! zsj*pm=#|Zj&8U4`h6Pye^zn~FI=Mb&F4*a=5;r0;8jjkgg@tGAt1$fht>?Owep2xx z`E$w-5qBi+o=#Z2mVQY}AM}01bx~QNM(2-DX&u)W3d)n#w={ps5R91OyZ75 zkod0x%_SZSR9HJq;IzqUUiDCsfE=BL-C+{>uZlY8kLEK-8mQbf?9Rdrbf$A5|61qi z!(q*q?kVGh$nWb!DH3<5S`;Fxs}eECtlKU~EWY0rFBRvEpG#ny=7!gxmEj*(?!iyp zD;P^mLpiZpVhLi(`8ID_B|GECIT3GKVk;s2i+c5v=vFcMk|JS@!}E=Ao>y1*ZclTZ zWssM*^XRU1tmmXZ&4z_`z#+)09bW_bNJSlm z(3cuZ_Z`_BPp94A7gSz}3s5I8zBq2}xuGnhm9zKmqp-jN#f5c>~^Kl{?z z)QXR*PvARGGnt^9V`?ccaj!1lXKXfdPn3$!7ew%QXiYb#yh!7w2@{;I4u&j$2gAkZ=3>j@y2;#S zTOZyRG{C({`ht>>&D|RB$j2r_k!-!fwU;r7X=nvS@5#+L=V3B2DDd|mfaU&G(UxFo zHlE1}McL0H2QH@?oW(sFJOkl~qk&LGcN;Gi2|ek?Kjbp1HcR)f5}sNANb(_|+zhF_ zDP5@;MD{EPAAA&z|9i3F_fYHJ!bQ>)kCnIS9+X;jUq}1d4Msdvx2_Ld!_Iw-^=Kjp*tNLtM6ucfPaZ`GG*j?K z5@!3|mD37Uq@ho}gZG2$#vIOHk^E8=8>9dr3bSz1-Pj#WO44w{}wY)j`9VGPmcXWmF?0Tn6Iu7_8i5{|@sJ6$U*9b>AJ`Sp=L z&WVKrbQY}Fkk-3Fz@bV;jS~p9NQo}Za-y5*(*+{4RGWNG1oNz5wO`%Gr*U*_458I= zX{w@OxO+XCCNr1ru}^utPH*0Hy{|8VVpa_};DcD-uXIZ5uR0Q%j^ml8JN|RdY^n*I zS%kiU4{E!5u?2dJOt6ebSG2uuH$NF@HBU`QC>?1vm)9wM!VJEWZ>jBMcRk&yP(80R zBCoh!X(e3#7HVtz!%fTrKh&j#v_=SQw87PnvR54(nQnix?9ujp6xFp*U+vV0(V5$P zcV6qle-yEC6;@SUodjlAw_hkXd_@Jmka^)Wgv6G|*Ktk|F1!~~eq2W}#9bK^eYs3R zq0XU)$1PhUuJwLkx`Kfl@A@nZc|0|>S=qzgJsO1%j@CH!4@L4+#(XMN!VY_nMyziL zXb;<0?LMajYA<}6Rni{u!_%dwcg4OS5e8}b5R86uiap~MU<98%p5v8U*my6QejZtO z^4CMX&r0Lq6-;1g9j^GjU2{pFqmBpFJFx1{2oRi-doaxOlM+SP$C9g8CeDjeEz6Hn zhLN+(y0E?$T&vg-gQqo)wiS61`uu$p8U{~~P; z0ysR|T%+|416h9@)H6pE(98iu#yXyRuWB6$m%DbR^tpX&@(ov#_$-VQiwx0~4Oe9z zZxwd@v7~y*eW(leZV2IL3HI859q!V%b0FCgWLP^40VMpQfA$7=lmak=AKxpcM68cj z3yO-^UR#{&mkcx|OgP5!+Q`wZI3EbG8m{dUD64W66mjKCDhzE^h@#I@lF3Ed2Y{&L z(lBF7xl@}rI?nP^2J%uL{2``TRY|F&WAa)k62C^gBGFURhgrzEZAxmrZgHv2#<4vr zhF|>caHNBHGGGH}66Sg_#A>5=D+@m=6@GJU! zucvis>qnzP@h{@q1)Bno$dR}^JAX#kuQT$A!5=muX>TxODm$5FxuOh)ssX<6@r$@0 z#zQQJeNR_v#^0+N?~m~k!m$vHr1#6(DtkS)7_}r zkYG(un?Zaw8uEG3{BdgYj~`d?-D+x618}FkD-{AcOzdT=Ejnk~OLeR$4@n_2IK~c| zg&Y;Om>r9S+BxbwSve-Md)zbVPmrPtH?8xxyh%^nkB1g~r88DJ9p>pD8T)rb^c_-n%? z?~>t>;AkS?5zXB@bA;Hw(cyDY>#lqTTFj+7jpT9KU`8|>!+*BWv73s86RAT&RU{+6GbUyb_s5LP&9%+{`y0z8 z9mhE)dG4=K;F7BgDexUowlF{f?fBP6lnT;lK6fnx$=0uBWmPz3KKb@U1ydK7&v~MZ zA@U_s5SX^IVs+bW;ZgVx6-LuzchcRGP%(L$=rywG{6t$%*^H{>_ZG{;aTz1c{B*|) z_pMX;&R@`u5uJ5WfA(y|gBVz``q#$$(H@F+x%Jue3Lpst)#*ck-wE_|G}^k|7Ks$r zI!M6W3`V4-R#>-Z4t{s)t!|N=NbB_=Zp@qj8yEoajSc8Ouz!}=qcDM8P$XW0n*XhJ zK5A%tqG1k+*v3*v=xNCFB+d1q7!p2!FvU!8MNuwk%W zv0AC!EtHHO$JiA-DMQ*Dd%7!#T*AcloMlW4|pAGwO%Wo+R<5jMVxy^Zg}8 zJciZFgx1Up-Efyz3mbWA2+cTr9S9#$XCc^YL0lec*jiJ3ws19vieGqpfjK{2Vc4*Xe{%*FObEZ>c|~uRb$n%G7*0b<#bAVF zdzJo#Hbui}F9|d118GM5-h$U)ET(q}@PK}5=IIydLNcs^Js-yaTH^|CoT2{{o2Lkb z78rjHX*Atxvpa)TdmMbs5v5%|;6fK2a;uT~?dq=jCRXlDEPG{+{#`1l=Ee9g1$ z-+Tb={9hW~xt%YdG(cGVPh1LQHaBiuVK3*oMVY{2m;E<+0Ca;u^wZyj4kWHS!+prD zD*#9;mXY~nc&$uh{Wb}BmLQOH{pkJj08t&qwycF=J-?r)FDbJh`jHZV>VQ{T_-JtN zySV*h%j+W_z8&i#%&$F7JKD905z^6(>f@+6pn+rwyv>IQQ23@7FzjWN zVz__7<4}Nn^-2WZU3>y)idtVjm$;AUgLn+Tf`ova_n1j6?@tjsRVh1_Fh z2ei{R&f);3D{V2LW3L=PM`gCWi8&H`^yf@L=t*~LM#ay`tcu$HwPTi*kFrC?w_Km{ z^82;`uVrghle5MwT1V9E!V6#Y$(oDZ$)kd(OykX6^Mg-h zc2~!MR`7DLX4My9+Y_|By1tZMGZ#zohy?0Y9S7PY_0zHARRBjON;KpCPQ zgL-}=#PsSJZ(5!2B~ty|f73xnJ_340X1m?bTToD7i)@sht9@Su=hRgkjck134LRzY z%5(I{Nu2X{J&|77Q5lcITFK+H?{pm1FmI}<(d)Ih6u;jFC?jTHvrl5&Sqy;wpfVWH z(392HN-U<;OfSgeLl=d!vR08rK?}%8MQqc~n&cD}HK)ZN9myU0O~n-2%(3S=t?IuZ z#4mFz?ccwDa3eOBOsk&d0^I+NQ%-cJSGujORrc$?i1eE-e|%b62d8T3hT1j42Ql%y zj*+_y_KtDe@5W1<69SA$mY1pSz*S_D`ZN&;0B z*-@mR4cdINH}Y$FAMPtkOOa(DD&zg*ISQCSNW|WQ)s-;kc>E504bdz&d-Uc=FzI>y zl?6s_vhaoTE!UmOWB}N0!}L13#~@plmT;3Rkyo!ScpaQ}{XwrKH~t`+dA}%nn4eWS zS%+RTMgpL6HV*I@V=_6t-UE`K)9=0bI12&F}fff}sRqHA<%zzlXP-PyLAkth4+ zCrd|jCR-e6el!V(h-iJse<6Lr0#2+48{k(}Apq63ivMa1;o?>0WT37xG@D2ZP(tpH z%#EVnR5U}@t%J%JNsCk%_mFpO_i&=W5DscOX=XL6DiHA}f#*jP5pC+WoB58toZIiP z|8B+w2>QN;O#>WIG&gE2lIT&-+~*5=h(m?F`;E%z@fPvT4OVg3BFm%eVH>XMM$$XJ z3~aRkJAE~BtHcWn6jX=C6+8fuCadUN_^+C$SYWzAS>xJ(WYyL+2y#&qc(H2?N7V{? zzAPEwtJ_2x(DW&1TpgZ&`c*vi%fZsx8eRJZq5p_DH{Qd*v$m?5jzxt1ED<@6n>eGx z6VdV0AU3ed@EY zqtb7d``T8$j++VAA#=7=Ni(gX=1)bA&b`^w8p3k3iLI`hIMkh#-V?#7-RPwTOw+}DXEz7Jc_~*=kk`8=K3Z+t>6 znESLF2OmWK;|XIQVFH=#?UTCRWa49D(k41v6}ZlBZnwa+alO)@)Uk7uEG=)XNc-c~ ziu4+^OSpLEkOpMSV>jswDa@wQM$X^S5O1k{1RcC4`a!)bvat$@3r^r$pf!_0=;T7g zlgng}KlH%|F@`UQ@TngmoP0^C1uX{;@u^)2)aLhB z|CSWDT$v^JK*js#-N)Psm~l{(yPKVY8(r%N=;`B5vHS$)*v*Wzu1`c-lGND z2(NwD3+?6Sqgv-n7tQzZzp(9 z&lc)M!-}um@S}XGlRfBu^TY_CNeGO6e(`7R^1mo&vkw?USY#+PoOV9=alL_aEEHRy z3V!#Gu;eB?cUR{Q>OduLOxKINd_NCs8N6;RjNvchKJ~YrYE(Wxs{#K0*{TOxJo_Z8yCgq53a^ zTjS5Q_T?S*PLf)U55uEZXw07@FJauk_6OuHE#XnU$@_c1a5J^@w9 ziK>6;evI|{G?o8q+5NyUKn{XZG#ZbG!9=PW=wlj2dF#Qn9*VT+W&jaj6lDu_507_u+EN&DO9vDOx;3)k=37<&+n6Z03{KULl8 z3A~f`b>;H3+g@z#*6cE@smd98fBqWZL`0ffri7&Pwl6Gr(_y=pk>_`aay{Nb{bV>B zd?`7Y4u++-;Am+XvdeECCQyy9l)^FwK=funY4}<-ntzt?LGSSBYGY`;S(~coVa!{m zD2aC@xA&cSClmsX6Q^*GsKx_HKfE;`3~Qq>35;Z9=p_~~aB^_iX!hrSQcDu?v zU$n}XO(Pth$)7J?SqcL~t7^RG@3yBs-1S1WIBwcfwEUXYT>TCsyP5Z#LDKX<8!b8z zWNWbNrz}3@b0R97iS>z*w>n`T}y52kZkfL;@eH-2@j<>ZfqDQFvsg%7M!|Jy`~LF z>Sl=P_8WVJW{jzXH++2e+(&5da~rDCcuu&rJBeh0Q71kzt(s%q`(3+N`B>wmK~Z_!gAYaM`19~fKw#NT;!l!J3}rJumS8L<@U`#>lO;oIE0>0LTkKqqs9EZ z;*R~YmdoBeelGZY8t9CazR50&d-TX)y%c>>Vt&Flk*l2+*r>^*YKJ=Bt#R2-Id|%4 z3>228d&6M&=pVaJmojt%n&zzA(umROV{kONDe!bU?*hCox!8d8kRU5IYKk_4^69pG zds#YGAFd$Sf*od-z68pCa9Z)pYu1n!luP{Yo~rDxcl}NAAhDp^Oa+xjdXU<-GX$>x z=UQU_mBG+|+c_Fdrg<_Hw@$9kPClDlCyqwgz(!$X+4rD7_;#=)CgTArre5Fc=IAHn zf&y59VXL>sG+}%!5YVBR(ed(ZRlF0ABMigd6022ItZRd7H_!>eK=jXV4PTG$n{X?& zZC;@RtK5ndv|LMn)fuhB9%PaRTLT$7m@s?lkbG#wZM&u8(6h z{7$E^HGWLIR*|IRgtLySa=iKNOZn0APt?jLSBVc)>F{mvL2+M%0PJs9P9Z`>V2Id*CGH~fzQyR>Ede>8+q1@G2_{`V1&)A#+r8SE}^q0|GmI4 zpbnOPLajp{?0kO1+tAbv*-V_&j(7jKJn|q|wkcloMJc&~t_S6~PWP{J{mjG6!NhbI zeyO_$r?jWESn5$164>PJ=p+<6DP`{|#tu~qPZNHa%$Ong&f$o;(S1iN{Gj#qsu&Wk ztt;+may&5;MvWBufySJRsbP;q(gHrE{^5801`!8T4E+VhNcqpg50er=k|J++V6qC#Xfj&oEJm zYyT-tVH4N+skajHnp_$EZZvPz;cvjlgd{%Ra4W)`R`m?pN!P~_>>}sgnriCz zZm{1lw8VzJGz=!tlKwG_7$Nnxyt2mzu*$$#z10HJ6uHdyV9xHnQ(N1Iwp()$y4s{f z^$I;e67hp5Q=$fF1d$OF(+wo8BJ?K}NVbou=h42@W2L4N8C1uhM$8_+HDB(Jp!hqb zYb8Zi2vTewhJBKeFCe_c?vac2o1PC^${K#P@6gBa6vhc>yV;N;o=1(RzHrp_cg}3W zk#-8QjbP~`K%a2FTk~ILa=yzqz_bv+n%WY^;nOJs>(g$t1)JtNf*q0VKDE;FG=v@?C!{| zi_NO}-kyxw>y|bQ1k!NA)`KLS%9o0)PE`dW%R{f?Pcu*#TRIFH*58fk@IfSun&if->hQk}mUuX(FqDtDg`U(1~8>T^~(l zB^rF6sg#xG7$gtV0Lp&*N2b7q1%Vd0cmRg{>cbXJ&%^jA1AeXLWP%CCsrsQjzUFO= z55{GX={z87a5xkDW@5HjUNC^ZaolOLSUsegSYo3LYm05uLr0;76U$>}&{6yPtQnv; z6?74VEWRh!)2VHWZ>=M=a@~Y?g(}8g6ea1V7twf&)ChY;Jt-R+E&QC;FB0~AdIB>( z!G|fIC{19~+Tuv=Ugq9qa++-jqWa1}xu?u4A#}MQv7Cgmhx_ss?{7B>tqum(0)4~4 zkj#*CkA4Q9)!&R{iGNAxNr0iweb%or`XJVbL!8`)V8Hw32aYv12URut&>NsG1m=vy zWrUmbZ`?LtTYGHyU&mTSJFKx!lm}}jo|RYEY(5^0C;r)=7MN6 zyhC+UbPcXG>-*8TYK3UrbgFxHuzr7_!TRgV`NIHx-*M*6;fwp$Y{2NIlbByE`U6FK za(*U3Ke4xe#JuQ!=CKIX7c&A=y18s2BChc#@8NyU{?bUq>?b`16Gx*GAt?{vFvr`5B6m1yjp=sVrVAd4&*ki|q zj#g^37}oTQk|>6H^a(W zBhvPI_1!jElW&Y@nkEs$HVJ3tH7fAx|obezoNsD;%wU9aNZ@VBbzt6v)r5i<1o8iOS2fi#WF z9RzoE@;Y&4R-SU^rsjs9^p8BKu17nUYvLHFE~XDfi|{{Oxld5Lsy6@nE$OaaiP7Lo zvcKq{n&e2;3}q0=6maoKe|4*%s@pH=Cz~XDkGU&mk+$}ZbK(0by~hV+(uOAd)#uU* zoCAz4^z2-Ua?9ZX2p4b?XX_ia&zvTj^(kgu_!7l94O80_TBKfLsl|J894$~5=hikK zIo$IV!2&IX%$$N&!cKwj_4)NtM0VId^g(j!mUa7BKyd}2L(V1V_I38x|4&}?V$U8* zZayQ5)D233-+F5TizDvJja*YF^<}l59o9o*Rb-mHvwAKh53|6k&I7co?8%JEhOZWM z1zdjm4W#~YFlBb8>#MrDz_UEv^470?->fxw2m-Oj0BWdC5>+ZIS<5r}8q=#_U9eC} z&cnA{(J=VO_2C#F4StP4MOuiKTP#$_CvwNy= z_WK~MXV{Fy12RShiOn=VHe+HhJOq4J0Mm~eu+hQf0E#|9xMyV;k@ZUwd?HFc2Em*;j}_A1ARP{9>%jz0d7;HaGW zeP{w$bxii`#FWm5@Ri-yd7e6>;u$~i@Fc(D7yLol;*e$ABf7MNStFViE z?kLdP57?zcnx?BWMJj9}8)rh7J0R9jVD&{wdcE&_B&IXqkw@%sjq)ep)RM>}CCmIa zc+`N8owV+n3>lxXln=R0AFn(V#?drWY%dXLpZ9Fe%f@EDx3uu5B#kL7vdkvB{QjKpY*W|!M%C7mM6`fdUt(31h zlvRH8&qPyxv@QiWG#YZMB(FWX!^J1mr}r%5-HBJVUrFnm8fE-D#!G}s zYxdWUWB#$gX{ci;A`UyGQZbHC+5SLy9jZS1YiFQlX?t?jo6zryCc*2LhRr@H;}?U04e?3wm?&55kcMu1x#DLI zMd|}&71%VqF^*;@RX%i%RfQYnHL5g+l2{-o0K#5XEmu$h!3P!kGACK;KxDxpHS}6w zThrYBVRCv%&Zzy{q|&7>aF>o5)uwW08zEs5n7UmS?_|qJQWeV}g-+SQ=nE4Lm;=37^L-$F;EPzH8E{~t*LM-?j z2n6s>`dIGq3yE$cXgAym1S$Z?*7sCc=xCSC`l80F7&ZvtRr5y@`!=XTrrDJUtN?Y#YL>2NwA>x*J{x> zO5VnOPa?gNu|d>8{n6saHf(Da;02~cmCetMG>xd1M{1Iz5!?yQV1^O7zC>JI8a3FJL$h(EiDYI|o8_X+ZwZYwLg`&Gu^Tz~yDclZVzhhC8i#jU-FP$cf zqgcv|w4#l?H==cJbDquDOQ)vL)^>5FU0<4KGsz=j*?yUYbq9`WU9G@Jqk!V^!v5E| z$oR80N6UO`RtB&p9UI5T`Q>bN%Cst7=lr&^w*{X22z5WDAVMplzU^rnxf7bM;p0(= z!nv)`Pn+IDpKPz7(T>o>n4arZr5j$~e!pYIAB zPKd_Dc?TZ)A?+2Z^1Nq6Y>u`}t>l_DeL81lr;A2knbWTQx@SJ|dRMdha#y39f0dMI z>?n(WEF?8(2p{wcs8DtjZ4fv--$=>w#=pne z*dS@&j#=7@Q}v@=lV`Ta4o2LfLdQI9oR!9S?kw%;4Agj)1UUWu6WTUzEK4QugpOHl zdET7OFK%G(nkL^POL)~PS{HSEQPU-+qHcQIe z6(;9}lvsY+KFKM7UdBLLyKxRFOV)l>aX!Tkghl}Q-tbiZt&6e+oe{wRE|}#iREUk0 zgC!@fdc9rAS4j$()+u_;5PMR@KcZy02{f1U*K+G5ry-!QP(Ae#80aJ?JUR2_hO}fk zKBilq42t3|atm$b0kA~!mf#8J=WUkFI7%V!gt#ZW)}H%NMY!tNb!luA_N!CAN8kzJ zXh6@7t$L<91Xj)-oSX^`%sv+E2LoBgP%s`OECC50aCt~-I6`YsrMlbHRN9~RbYCJFO%%CVA@Yo z7HJz}>uoFme!pX1;~Bhl24lXsCq(2vTX2tvD~VUP57i^^OH?>Vf~kxENEB~0)K>0C zC%Yn__g+!&E|`BW_o=nPLMPA9Z8&XbYTovWm%TV$AFVd{o zb&n_5dxt&);v^Ni#tV(QpD7p0)691s+FUs8&-4n_fwLb<42%wsskKFWOAk?}=6{M8 z^%kY|X6#vx+wVBVljix~Z3+L6AIAFO~_C45lCj-HiDMumSv+bR?3tMdff5(cg_XD ziZI4o9PNT`LsmY^u$aGNtKRy;KM2u_sQm_$jOR1kq~B1^8wivgOKM~>piC|V+719e zA#de{PM>o!$bL(F6Pe5zBdELezxA0Y_ zMKUZDtpKr7h)~{@S#|T{4DJcS+5|;^Y+4iOE{%2dB z6h*lWm`{#Nga2&#KBzri;zHbVKPVc=s#qk%?n}4J=%*TPN+`dKREmPslgJd_fB0B~ zw#vu5fp)i^ryuhZIzxt4P=SQ_5Iyqmv;RhlroMlG&7awq?|u6MeEKrC)w@cR(tMxMPWUTp~Sxem&%v)z)&=xuZK;1|a3#5dmX<*rY zOj^Hgl#W-|KPM`9b(w?CCM54<8dB0%B_i@ZEs{xmm=H5j-=8UL zx9z-vwWd?>Jp>+QJn~h}QCr2!oW-iOMQ?wuTQRR(Lt#Dznr^AHbv?drS(E&8tmfTq zQa>etyrs+lt_xJzEdtO7769|V$HO}AMNOE?vFOTnzt~W)7i>ZgY|dND?0)-N zmSfRWzHe9fhKAqw9*%q6DmdAE7_k{^j#e%Uq%qk|@-eZ0SkgBc>WmAV2<63CRm+Y) zLsP9V=G*z)5cV!u5X!GnPlO*s_#lbZl`Qtq9fb?t_oq~#4;#ScUsZ1g*C8tPpu4n@ z6sfDCvhu9#T4PNE2mA8Vd?;##`GX)Ub`GtW{b*kSXveHTs%1$PMxuPSpk;e3F^UpFA3+fPRo+g4&M`*3`$jna1;cpB*riA+;$0m3@31QyvDJ+QS>NAk`gY9zo+ zP^rnOLkx1uG``C|ny)Ma^0{Wb{`-B<2U{01K&wYU$UKAf$a}w}D?XhH8OkggIdA$$ z*E|RlGz^Y{2GYubCLEeWj`+DUu|U#yu^9vHUIQ8C#(a)-)A<^(d3N7Q{_hm+_lZfK zw#z6>5Z!t>4FL`U0{tNS$I$toIZ)dM65Xzmhi8n>NzEjx&rgk^QFunsy+tp9N7gp5 zdUh1R45&}FM#f6PCd}nu!jBLn@e1hS&D}SG(cYT^U=66)ZR2bkT=pYRtQkUqu$1vh z))DGsGX-c*@9!=8ZBOKvMd5GQpj;9F7MFBdVgYr-zWqT51X6p@iciJd{q~A;OENE! zyDX`}Rf4W)SHsQSIo=f8Tn((vs6eYs{()aKsPl0}}@x z8VWb2tLGm*9NSdw%l0NAp&^A)Bf~X79QUd>T}YXKU{M(@~tQxW)tcFrV{6Xqeq{Jhx>DucYt+2Os=>s-7A#OZU~dh31T%gWQYvDYu~pUxGTBTz zsza#DH}XioqS>?AsJl*WD3M^fDk`>b=Y&`e1{?ZiOGNoDWQPxS6?PJdXXhxMu#{YU zv?c@5M*<*#o8q(Zx%W5k4ucTCWrg$PdHd%TQL zy}NVD=iR&<$4UPpEMY=lw$YC12hGlqsaLjVHUTj&K=hX%{z+pWGEXh43Zbcz>GH7A<}U5YVmzS7_d)$()*Q-f{~_qMtBa*odD}BExn)M)d^XID>Q^!fcA5E{+rlOG z7o3f`9yl+4b&8~6Noc*trDDdBnq=-zXM7pcQ9$^=|HCXOxfY;_lHHoB!s&5APcdk$N%5 zS=oud9?OpcLlog;gM2#Fn*9(H4q`3gGcZ#zU2y~#lHx?XkVn`}jAqO^p}5MamnNw| z(zHO%t49h;YBUvYAN}BK(`cE-Fv@qkM%3I?cL>+QD+u%1tuzl@S&4? z{Z!@klDde^4Tt;x41ek^R6ct>$-95Lu_CUmF;*K-LAwo(UJ(MW<-I_2h>@P3J+lHn zgiCYyhk_n3H3U6ahG!R|QO}g)-Otx4beBfV29grT_` zT#+FsHy!$~2)pUglha>8y*R&t!I@vjWY0cL`a~FZ2HG7-wC3vZ$BzUY6Q&QyH0pwk z>j1$NSjAVAU<66QAIdHTD^Rl*m^BCNT$Vm5w>HND*c(nrSvLCiIWo!6- z=9n`A-qo*>T!q{LJ}s0pe@!NumOSF~=Uy~vf4u${U?3f(VpO4~FvE5`j5-eRoCH2) z4vY$th8w~eH?D}p$IlMeGL(s3nia~Digge(HZDu(bL)NCRq~;ae&Udu6{$P9AOMp= z3n+?d+L?gDadfTYW0c_EAkKT)!+nr>!fHR2HfZ;y zTBABkS9XwQ^jy=Vbl8Bt=O!um*x1S^orD7rIB&mf1wXvbbb!t4FU7F=7`mNdeEu{( zEO}J7=c2GaDh*5u#^>{zl3E>_t0_r-kl#_z%In-emskzg@FZd&I2TJUJi zzxAH*FL6I1QXFUZjov!AqEjm}bzE5PEnXNueUiozDsUf6q-ep8T&VXDdqpeUp10j* z6@0@@yae3Fy?g2B;Gk!ts6OH&ndN{i)9|g84F~vmM;v_@GYfHy6Sisw zct(*C?`Mk_YBIU}{Cz*P@Y9|11H=F4=KJ8PVEMdXy$N0BbZf%IOW*F`)myY>cj;`V zk%zAz(}|yDlsnq{F$`4B=KkCC|J-L;=5IW&??`uCXN<)7U_X=deKw6jq~ts?ve;G-Zd@4J*av_-%Alf#-5G z;GLMSiy9ia5Shoo8P18R)ky&mEqy;nI;t04yt4oCh)XvL#Ze;er4xJvk&J%ga9Yhz zJG}@P0rooIeqyk@{ckM{1M^>Qdq^G78_k{T8$B6g&E8S!p>PyEimLLuk-C|^hzS%1 zg;@X^sQ5WYTAf;!F(ff1rHJEyu=n0UO@80Hcn}d45fB0CA|M?^dhcC&?;=t|?>(Rt z1u0UMUZwZm6{JY-p@$-b9_b|{xi5ac=lt&Z&7C{vp1J?rnKQ#MlgayL@4a?gd#&er zwsB+0AK9E6Ob(9IIyOi*Uk&BQDsEf`q<`+kf^sQtGW&6u%wP~@WzV z|KA_}S2o~_hO+s#AL+UMbq%LLSacfk(7Eq8H!h-NT}@bT$qlLt^g-TQ-E!_zjzeSj z+)Y>xfav3E;~x5v=wsR(4_PZhs(D?jZ`5RD4roCuY6}gH)5Vr0_wV1Yb3zFKu%{*l zX?*>2JAb{{Wf$f5B3YUltXtv?lD8yDfO3nx==^9RJjC;G&Z*DKF;HABFDFT2D9~XV za=^cmwA%CW-BbJZb%p7MZC~@bP`L?(g(CJP}mnl+(4IAvSV5w6EkZdG`aq z^_N$6W@}}|kN(zG5xy753o$Fp_O3p|1M05A;1ST~lhXV*4U+qzlILh`L*Z|Od0POw z_RVl19G6}cT&yf>835cDfLRc&@*+s}#t{ERC4=vh)>MwOKQPqC`JaXrAqS^c%KB9G z#3oERi-3FP-v2lJf?tYVhEMGiOn44Pd;WF52YwRr1C{5z`|s$K{~s&jKZGANo*w}8 zEixqshffT}YD+C6te_99f2T&FT1GsBZ#oHx(CWruI28O2*@Ge;aSS~_e->GE-x+xh zayc~3Rj;~v`j%;{$7cqT$Bz}`{Yw44uE96UP2PhTou;1cjWuxK; z&%Y`+s!wDfAy*J|$iTbe_=NfJZ8 z8>d$gaqcPu^#}K_A+NihZRa^+$yETU(`uu7`{IYcsJAwx3j;nZAYQWP1bUVH%v3bD9oW(ZrI93)N; zTB0EHW#sL@n@-l!__R@1_)}FD*g!=muAFC5t}v|64EjNK*8zLq-l&oTT%4&KnHo;S zm=sX^>2ne9?JdplJ6PH5fH^cv^*B6Fwlp0t4@9KjKCY3@Lsdg}r$_zM$IbgPtLn>A zLBHg9KQPZERrK@})_E=Yb@kYo3dZY;ikxcEWy=uJ&+S%-5iBKA4Y)VqQ?;(>7~ORA$^NU5 zTetZ3ln_gf>jqhP6_IAyD%g(}>w9~Mz*143LahCM%jpQKY>U>Wl+nhLQO0B^%*6

Ry)p%T;LTPonbFpAhJn8tP z>`KZN7<(peAp-d4mA*qc4!;02U0y9bpXcUJR9O2ByG>veYu!IHB!r9Es1(>E8#lS% z<@Z@StZ}b@tG->41i(B=eK4bkDXMRCy9?wgfr#kmn)9R_wtUpDdoV;ya zlHj{H8sY5Tf7ck<1VTEk&1(g97EdPt>v6Np+#`RiN*A8LbSC@g?=NZ=i^UY&JFsJS z{O1iGV=hbGe-38z|UfG*hfZyD+ArY3;r_B5&jOFJhyXJu%dA zRw?#CNaRz=XM6i|wF;g)cd|fHkul&SXIcUP?shjZ#ap(xn3{x)tk9^wjBd~U$^u5< z_myC!hyj@TM9aQ#gi)hYhO+X=in1n)Y0)JGCz&0JB-8C`B@a$H|j0opgg`DQ77Tkyd3q!|`qb9pn>l&R2T zGXg%(nY>~x)Id}YLkI8`GSaZDo=HA)=;1FZhhb_mdY9%W3`yQ%KJ#2<;Ye(84F-AW zVM#_$F&2LgLPjy*?-L`+?jH@0JQHiYz%9S{_T3OCR2Jy{mImW8v>=P_FOFR#o{E}s zVbC9minOQGP7Kp3fA|bfV(BR`dk-=EAPwxLrOq(x=uYW+(S|N<--&HejNg8^)~opP zkP`{S-eg_l+3iHE9Kal26}zORa+573Qi1M~2Osyf4$bI!!zW%S*)NXYO^6r2-YJq7 z`*p1d;+t$2XwcYfV!OuaYfw>WoJ+fywtE}e-|HCN7mq$iVW%F+SJW~R1}|JZE&B{Iy0~AkR@?;L*&$Qf+5y75H;{ zZoEq%Y0xrjD(wC`6~(Dj>vJgr0f-D`#FaSaRmi;Wb-NvTW=L9XN!zkp5Y1+EX8((OoLsF6tJl>-9bS@p!t_c<^p) z52ruvzOtG!w^F2|fw6NLr^f2Nbv!CUI?iz+UiQkavv9>;)A>y8#{5Uq<rOY)W?P_PK6L!G1BPs*h; zWRMLKr4J7oLmEA$rfg@ca;&cxPJ5M?1(FaUiN01|X(93Xa55*|gJskR=?6)LD6uPe zMGLDaiRgvNS9jZ497XuYO1XqbsL{^!sWEXI9XGQpbT*RgWB*t+w7mCymosb7;VZk; z0;SVOiQYb)M?nT~z*P1+>h@_{(_lAocMgwfEmz+WaLfH~RJ>%|*3S&l;8DgJ$aunT}0H6ch~XXU0mcqw{Sf?29m$ z`KvftGTm&F!r>;OM?33TumQyV_G*$G#AM z?DwADnD}k(Q-@TfUJ~Kqf4#LK9sVXae4T3kw8q`&(mBlD0LpB7+J3UjZj3D(eeq4T z?y4E9SDEBazj;TKrn%E*X59E2MKZe`4Lc{VK(X4~L!1nm_ekziP04rw8;FqLItx>f z?W8?cBPX!eT?*s6>>CPv#u`wotqhqf4G|mJiRrMp_55ALDAVfBx@d&Bc-1T!4bhTX zW=+QK5am1lR7YeNak@<2uOmc$-*Pe<)jPQpr{N|A4B>lmSlyhxgJ5;2>CQ!m`d71AKt3TIXvNraMj=kf~Q~EIMUJ^ zlb+3&8=dDJem_LbR7~?Vh#9+?!JI^Xm&rXA)OgD0{3i2i4=#@Brs(s1sA~K3vi95M z+dSWg<&%H9{OHm=5*Zw44YMufdPa<7L*9pJMfLUy<<omAWMW`_|Fjfh8|tw{eC-uLs-ixK_v(Vt2y88d`Ub5qFRg2qn= zTsrRdBJwxq&t!6H(A5hDAC=-$z1;X&jNjj8`5}k^3Fn?lwG0Wu{MsjINuAspB)`}%AjFq~px7N&ww-M0s?uy5KQ)zI zu=-tmkjH#&Rs+wZxs&ZOKKb$DoA#FPXP{WmhCg3~Fd`__I$m2W17~pq>R@ue zm<}Dt7J4lAB8Z7^db<{WO3PI_z1+?8>D_bc=(Kk!qzvUNw&4m&%2~(t{0D0yRrGSi zsG@r3;YktaVeJvkVy99{(;&ji#mi@+*l#z?@Nz?;Y+}6ocvc(&5pV1T;wij&{pg8t zDJ0Sglv0+g%LMp0uhIdF!a7gzWuTuoOGKs%r(LN__mCv;sMptO_H`M!Q72rOjPmKU zVS=@p^nQNq=#3iQbsOZur@l>HXzN4LZ8TAWxZ z&!kI^;88E|;*tVLJ}7IOqxA?>k1_F#Tzsji&J<~$rY3X!+5@LFqE11q###$9~xX*M?fo5V2E&C;Hr`et@L?&zKEHfZT}UeA0x;!DSpmttY_yzrVQwss&b##&}898fbZCQnf%g< zqCAmIzK)5p^}{db*{=hl-Yr!Aj`YYG$>Fe6sq>5^r+O7YtU5fMG)qO^1NUNe8QX5X_FUef6lx<|=k{))uyfjSHb#x=7$SMzms7P*=jlqAo=Ch=wg^^LBs51e|1B_frB5Tcv8Se8BEpVl+ zJ>ty98kjP&KVWZ_OkomMDbYWEKJsYFz&{!M)t5jKS>rI=LqNCcNzwpit8{L^U)e?T z%n{uzBuf3pz;DNL;)74#!BJm6(XWC=e79YP;cGboC9PGs`qnvScT4)Q3*Div z&zy9na(@W36Xo3Z1X(cr)}4(X8obNqfn&jY&+}n}N*2sH0Azkr!aExf5K!*1 z7#o!9kgD3YGPiNRIzU<@^As6-Ip&XH1R<^-b-a^`456^ z#UNH`w3DIT9yYT!`>^OkoW8~2l2D!eiKJTl#D#Bw@s{Mp?_>03jCv55Gni@vu9%m9ye#ixoL*m)5pgL%UNd8lIg4 z+DBA(ERl*TR5|6+Mk-k%U3mdKQIk*%q?re3SUYUDJOev zPW%_DT0B3w4r^mCP$L!H4>~o8F-UrH6iNA1jlyeU(8Zc=@d9mrhMrVRbw~g8M3DiT z&gs3qq`foT;HRtRnR0cI7*-E(j0bJ-`k+B?01BJ+%(j4l~Lz8q*B+wvM<>2 zLgXoX2Y)Ly(uqrTiObZ+zkj*C1XE4}x*7Yo^FU+97H}!~i8XjMB3UIx4J(F*-I3+b&)rmLr zq^dR6?n~{n;v894jXL$y8E`EFn4Kk+HnoA5$U7z4Ku^q6dF=81=*CmSE<>b^5{NBQ z|DKr(k?BGNR+ygo(U$5Xj7*mb5}(@aUhZBcm~B$^y^o=MrFRe7%@v|As$P^%3MxVwFmV;_MGkC5==V{T9oDDbv;vjwfM@1GXIaPR_fG zb*FxMp(w$!6{?>z*mfLemj#lZr9@a-g9The6)i-uLTspmPV<(Tbd!r7EgQ$}vgX*{ zH>PzphQ`Zyd(*qev{|;BrK7C6=+y&BJxYkP#i}=3^sG+pRPtx$t{m+ZufPVzy}Dy~9mkb-=F19V8h+@@ z=mS>lS(r(e&&vqRd=4^s)fn~#*67~6&T7B#qJg2j(Ub_a0> z8YHVshLAMu<&3QP#BiD6FMBJ3sVnUoAwdPBLE9~Ee!S%__&SBDu9UQ`6}?ICbHq7l zFasCe_$pG3TUhp-PV2pMQ%C>pvCU{?Emuh!{QhOjIcr1`Pu~)=!>T zH5)Yy7>Q5GnlHV#nhMkUd^Tz~Qeraf+hs`8v~SEqTbBeW-KJM@ZrwYD?YloeY!`xLg>*Kq@^s=C z;a!Lc#!Q|ID_;ga$hs`Ac7}W9ob6j)8{etNzeLb<*!u)iRk+@0m)W?IhmT%2yu++x zOY&|N5UCGOe;64#ipoE}t?Fn04gktn0IRSy6L>55?XWRSTIN=lv>~97Gl~8v9tlgx zyTA(hVYjxcomgX;8vgJ_gJ~Wm^^XBww~l2aKDoovPhK*(4B)MA`Pq_hy>k_nFO~iv zmwsk>r$jp708J{7Xl3gYMdx70^LgmviLE@4^{*MaV&6FT%O8SQ{G*N&0f@z2kA1fk zs$RazP@N3F&y`r=5o#|{?T@$7mR3OJ3FYe`Jk2)hC1ZtW zaUti2)3*pjy@(;1->$8G`E3~*uhv@f`sO$;<+!5hHqI$Fwn+TdOxFcim|#W_jY)*M zHoArl`}}lol-D^B?-Xxt^{A;~@1yqh#)w=mocPSz)BniQ@&tS!RO;?E^*^Q83)Uvy~E@;zoV7Mp9hk>3=l5@ zYmpFk4Zb{HT3H@SEwR4cKSIp=%c~17Q0t|7ByqUD)7>6!41Kmp+mx{TNVwyH>~{Bh z!4S?NeyIe!k|34xP0i@|i-lJKVt))|%5kZUzwaJm=)xQ*UqHK4B_3TEy@Q{C?h@Px zXl~E@To>_=P!@k8BU3Rd(|;bV)0_W!;LFLk`+m_?s@OY453dqlK97bzQyt4UHhQw4 zN>Ti2m}825#vZCdpLtcExB-iG@|DMK=qMsTo+;Y8plIi|%Qw61Z(JX{Bc4~yl6KOY ze`r)UeK))~tvpZ-T9IfDmWB5i)tl{%_0f80FE~O|oXfoTUTN*7O(QlBW31}tkueJ_ zYGSnfBbGAnZfV|3l;!2Sj#;+Vb0Wn$m=~vOANB3GUfL-d!qK0VBDsX~+eeP>f4OvO zaGbrCGEMMP1V7)rpiHbsWJKKZ*II~vIf5h*!LE@pM3%k4Al&Cr6So#L>KOPA_<8~e z{AB&6$NlZ83)kEA;_F;Kk%p$e(ZH621n-GTCD3eDn9e9UOr%Y4j9iMg^R5-Q7Mzz8 z=c`B^OVs4&SR1gGs>LVf(k(%gdANQ{shX1tj#@R#yRPer;2BX zLCnI}V_6b`m1Uan68>(H@6d{#@J<_z6Y*ox9^|I?7f8PP zC#}%{Ob~WBGbmIn59*xr(t5F+p)L-seU*6n_i`L zKT1b(pfYJ35x49EP-5a3PPLA$wI(=b#G^AOzd7bpg9VgWP|<23A?-KL)Pzg2-N9o8S_pF}$; ztu%5JStS7@&YR+aIIf7cADv}%LNR8RX~s74r6RQ*xz11o=Qo0>g%FGXkPvlffu=I56|>V}maz!WuW=zSydN%*3X9 zTiN?dSCS_?RZp18@e#skpUkbid@+Q`W{zl4@}bs*MybAQq+jooV`EZU!TjF#Z@150 z9*E`>@880H5!@dq>{ZeGJYZ#a<2a&AvY5lTh}k)%cGkL8{Ot5?46h$YAbk6%N0Bws zF-H%pz*@c7v9mJZZTe&<`&I>;hzfQ~{a7!_MqofiXwBwak}g?fJF1^D=@EckCx04r zHpLkmv5sBE>V^c-8rI7RByTcpe1~AaP{(XO55`#l1LE+34eO6By2gk6foxukQ0JTd^#^k3-F#WsO-j3owjt@;#nve zj+n|Byglt>64a@&=zuBSyO9K<_6?lZBrQE8XA%cYq*f$qbP z9{2R!-urb@^Sp9EQ|tRy3U9z>1j(+PzgPKL17!i-Va_H|aswdkE?FsKWVw4$l{PC@ zka+$msak#4Cpy@!Q_^N_H!rcSZ(PLQm!IuI8JyKu-}X{wVif&q@OW}+blRlx=*c8N z`SuWh*b%1T8HENlBjToZp$&Aqs}y4@^Yt^S7&k?8aWT_Yt>;K4-PR23=Hs}>FHWx0 z!%tpDP~D8OISDJFU3Q@X{Z#n{44@G5{Hyz$MPCyF{4blx#1?@{4W*@WiFEi>eMaX} zvKWgLL?;T<{Oi!~H)4r>gE({OkMQVttNqZ@uWB)g;6Jryu+4sNqTvz}aK2eWw8fex zhmM9aK|Zi}de1bDsm@#Aguv8yEO!jC}*S$xU%A4-b3d#BUJ7dvbSi?IQy0EMoi}nBDA_ zJ3ofJYYV;z+PFrq{v$cT#a9Uxz5NeM!`Pa%#PizMy!G9)VskqTB++VxXP~Jd&)E}! zoN+mIiB20g7I}_RD=jOu7iwSWu0lXV_u1dG1TEhw}F9f`rAY zgHj=CdCr|e^Q}yB^KYqTSQc)#Wm0j_zE0oTb1Xkgg`L1^q(`h4dg+iV*> z{-P71I$#&Cvbx>Rahn-HV!zGSfRDN0#dS0%aB}Lrq88D&^ykB8W;2p7<&id(GL0D! zg-pA9+*X`M(+L-8Gi1MCT}!+0s|df4?|4w)oiCgH8NiWu+u1vC)e39xS*1L|dbtK5 z90#6C-nkD%G0-3(lXdIGeFBv}=xoK8j>Mt$q6Ro_m%8rl{;ezkQ&M#23@I0*BVQjT za4cA|e;KkR8(gLryGsY44?vQkf3xoY2Z(V{HU&PkiQJtpx3+e%AY_yo@VRX4w)gf$ z)W`D4M{R>^hvoPW0a;9cl#mTKArXLC3AV2@sC-3HDQBFq0jO(}@mWqF>IMuCCIPpi zA4vKn z%^9X4H;EAWuT1jAHbKd*kNjs0?QW)dlU4$m@F3RIlTF+mB4P*Hu0Yy>4BDXxz#|O! zIoq7d+o1n}MihDF5`Dbi?IZeT&&28?yx!mLr}m6E86cvJ6fZk!rWl`KXfWSYk$TIQ91QgJTH|0lSC5tB11H6oC8!i0o-YR;m-fM5uRWwo+cc_rDx6 zAM+NwE*GBcrE=@tLduPVnPR)jZzE#5k_w(4x;Dl@Ypx-XVugNxJ4iJ`v35amRJ zFpw_|EUTp>6w4KteE6kGL!dq(-lhd#Nu2KV*yO6w7ToivIMF#tvA5nEHDBdaizp zZmW`BT#BH}zXef``bLb(!Ct8*tff^Hd z>yJ2C(#qhzRg&3v=ho)UM;hfh8OFrl3BBt8Y)mpK8;brlydK|2d9{IsR0B|&tD{3F zpO5@0e2Rbi2+mj1dglxbSp z&drhdmX9N$(lHX!7N~`bDe#v@U2&R{LAn!xvqIf)H*0tfEj?X#t z@pKYU7jej9e|Q1-jGxX4=E9T8izRpKnZePiOK+0SXEYZG&;GUVw%lyHIqnEQ z4!~KOFsbTP{p}A~8^h$~XJ_Wb?1|BGM(kHj)`tI{y`R2ZS!}l(=Hm=t^so zZ0x_(V?AP-ou=wf@q8g6C*#mP(QUJwn=c0Ds~r;7e&uayiBBDyUKzCSR@dVjb}}NP zRjJ@0Gc;_g9_D4d3|jRVjfdt-*AACGab?G2ivgiq{4E43Ca>GhILS+>FiD2r30bpId18o<( zwW(tZOb?UuaDWC6;_Em3!v?yADb-Y)%%?YA5DB%`HEI)0b5M~!CvhC1GT6o40-mE= zIF5-=Hk~c^`jq3b+jOP4aVnYGy(=>W)!ZC*$wGbIEO%Z7x0co6`QHe06*3F?D`M#| zss~8tLJfV2ecLzb{$VoCzpT|LUmr!zlRQ>J<(_uW_h0($2HE`>2f1+<1YKIC(G<@P1pJ55Z+Sd!@LJ6~FZ{;cjyJ_RDI+mC(s021dG$A0t zJI2FCsdGhTw0=YPOz8!TKuihwK~l>PJX}F7dEDwcSQrx*ynAx80_0ls!gxOVsYj==yu$a(ofD`DO2#}w_lyw zuL2MhP>H0$9mQi#*^xJZkTM6wC0ulVQ=sL|nY8jm=b|5-`BrrQ>*dx5AQz=D!{7%2 zY=Y+dje>mzymb&tOeDwM7A?1lFT^Y?l7$+NR_w=}aG6ppSsEx*eq-VRn_9J-bN zD}Z*k4VsP{L9yCG^Rf5+TD#S#FMDh`yk?MOQ=CY*g-OLY=*1|(Z+TGD?=Bw6RK#68 zJ?IVRqM#v)k0zbVORrhmZGud4!OZ%ASaUsDd?H#bzl?9@GUT@JIDSCyh1quD^Q}Tl z_)Sd6Ch95n^F$Q&6!vrWsiu$-fY!7OW>raQUuF`Y0(=Ox)m*?OhA(q6{l=e={Z87; zUnv1+_Bhy;QGHU~WD}U$Y|(rk2rczKY8|oji>RDqlEd8uqbl*XkRY-oKJ; z)jHsxr!vnIxN&i)LRf(4arFN5)a>9XX1D0Z-|+tgaWitBi3x9*LU61=Z|#3X1Qjq87dzr*Q)DnffG%! zE(hG+p$iwiz7GV8-W7qv2;>E#t(TJMc%7QJwrKSGhf`hcrtEGPPdaQqYk&^f5{7*{g<(d4)+ZjqBm4SVfw6@Nz%M`CG3eUDVH_B#WUY-Ne_x2L6 z+pd=O4EzcODX;1&H?p#9M;xhc!e59B*(gR1H2gWssrb9>Rw!kj2ha%^-`-!$i%9{>dC&CwY~MnNUNTd%id1UeR(p{XS);&nL_?%;7+!? zfQVQOzRM+OKU;4!+nMN9q@MW;8)++Gw~9Bs+g8}E6KDuCGE|kR=DeTE zZ7B9;f3(%qTv+;OnNL2}KD%eWx-d@*S6Pe8^s8Hcr)e>gb69iVj87jd?!vz7mX41) zdus>e1uUE4u1W+usX8|vg8EvQdSc!|i*>%cOkoP2uWe2E!~XbiVd=MH?x2t^CP`s` z0#N{*?PGqIgaU=&VgKCzHqb{A8Jo_H9$&AhL5;3Rx8Kgg)tbPxWAaAQIqm@lP&A~n zm`LA!LP#^**WnpTSF6vd(h}I)b>H+ypeBBRJUQy&Hh!(b$-3Cc9gA$FBGW?Z z54hYydxMv8lWh&H+bl9&!2)iy&%gpND>{-rj!t~r|7Z1ownayWr? zij--mGfhq$b*^FKNWv+7_e-g$OD?4~8i zh^cifWDu*jZWA};(8*n*y+HZ=Q3bS7h$C^zA)$jWHqQ-N@srRC)c2>}YL#h#MOjmy zjbR?nTXCg9oHeY424UK1Xcd?8ea~|26_ChlzTo^9E%#e>uo^F|MBgF;5?p7{ zc;cE&Z$Er7ym2+{$bo%>!)4qQm=@%$6Y||@LmwApq;?)@NDAe?M|kLk5F4u!i($Dn z^$x#q-d(RXrByGHt+Yxy!27_-$0nI8-%_kjj9SPj_q3!vrg5W77l%)S5tyQ(Fr>%# zmb(LA+8D+PAKn`WHEN_curzlBf$ ziJb4zC3oX2yovA$2SVt3jL>sffyXl1<#VK^5@^RlGxOBaQT|OL8sK366A%h!HgUxR zNuC1C9m%?tT9V)aH(&JM3qtNRn)AnGU7n%Qt|uwclM|oo8vYe@c0)2P1p?YlR zg5r_S5Lf0Q<^)iOcWO;bRRS&0`&32 zM4rEQz3q59AanB?5mtvQ4{0W%657V*i;|!RD3sM}&iX(8FyCSTvc8X)WhA+PkQ$7Q zocS$X@CP!gJA*rp9u+A7o*C<@k)S%dL+L2~_vb!Kj^4B|jALk67~?!Cj6Qa}*7k(c zuS{E}|2T~D1ZWCyM^z%%Yvu)WBV_`H;MA^VY;pq{6FnNhTYoy>Bbw6zn_0%T+x=LV zoQ=jKB~AWdvIl|2bo%Cnp~^Ix|1ghZf4mFG8Lz~r0bh5vr%FZ1m`BRui{n5mxS*4d6P8Up-4 zPVWC*8@tQ^hK#e_qM&>R9qoJ% z!M^G~u>n|tpp|F91AFC$wMK&}ot5tJ5g~igRMf;P1#&?4a28lC@0LQZWhAbv6yT#- z=SgD7McOAVcYrW1uuKr$kVsO{f+{v6>7N-mOs^^$03r_{z6~N^n1P!O4dl6cmriBKO)#ikWV$tG!gOVkUl1WATxYw{( z5R(S;&a6(6Y~V+AIBqd?jgW9qZk1#9O3pZTC~@Fw$^el=&Obvnnvgt!fzJsnnq2pK z+CofJpl=SiLQ3cJJU%7gfD7rZX}eWR(<#PZci#&)m77>CV0%fO$LJ0B!G$fRbYn6Lv=`>-p#B}fhZXfIH@s`IyzH`NI$3Wsl zLYt91*`Kw4xfSOjk3bq?T>_Hx?<}_ioLPD)@2kji37px!xvtF|IL4RLAE@5%h|o7( z=DktF52(hNro4N44>Zm8mk!~h{h=6QX3EK?x%4Nxtw|CRcrD!1H!a{txb)Cmq@J94 z+Abdx2+sWsuTgGQiu7;^oPo-?|AY*g1P!TXu2*q?lq?-T9MCz5_L7x1X;pazm zk3gKof9}VP(19!Uol!?kcf5GDEPp5!n}2J^%Kg;{Y16F-`!AXXno`u_9`#sB*p7(Y z?zh!wP3CQ?J((R9y;^B)@$+zf^&N+g#x(>VWWs8}_jK<;>^j740N@0m9@2_zu(!Nc zh*A@YBFs@9Q9(F=rZL-^?lf`l6%?42XYYb1ya35|-Vg(1pz`X!sh37)2MG5Y_JpiO zZ5L+@H`D->ymP^v``98=Zg2AYi3LirmUg5apvE#nEv5Y$i0#PC-tG^+u&t;AdoAll z+BRAb(ydEV1q?nZ%rHPa@iz+gF}jj84mv-y@iGH-!a7@sRn zK)B{}_mTHA$~CZE#Ib^-xY%#KeX)GYz6KLumC-!jW`mOQ=)~sf0Y7~Cv z=3TPR9v}STl5}`n!<#qcH9i!DaGI{g6F_>0e;8|k1-6LQ$CIhKb^!4w&Y@cjh}$oy zL|pXhr`&cjdQ0w=ecK{FQ8((|Z~XW$o?R7~iY9^h#;S3)PkqN9rYRuD?0IzUxO6>uI(Z zMlztLCLnj!bqnwepDFm@Tj={I;t*Ef)PRxL4D0Bb;gPZ3v2%e$qt}c{Q&ckokEo*0 zllv7lxNT-MLJ$y+{NAHMe~Y1HGz&uftpALAfIqfA2@4ebK`OXSvJoYQ5iciV2LM|&2=_1TDHvLN)P;P4kn_x`=5+8v#N z-D+oTJ%1qxE`WqJk2kBIj?mgcaQsBPYAukvf4$W2^{L=saBZ>B_ftw*Ct@93u$zqA zp#}%bdffFTiUzJOq~Od2#!;F4siYYgQ2pm!6PQ4~e5d4Y+{{qi4pCcrWJAc>4YZdk zj+J%gVjchw--6W(T+wn}I=;@>t7Hm&xzLy_?j7Fve8O@ApF8E)-@TwM@bojk{t9dr zO$SgnL7WW~Bu~9L1;W!>+gYhoh~n?mCD)m^j8H`$00#4|ZG17qJ>?fr-l^LEdV;B{ zam?OW50RJQ8DRbewnpa9`^RT$>Onx@P?h&9zvX}!u9!KqSZ$$w03Lj&b_s2{%tHTN z)$?Gmq70U1*f{BM)l(|(yq4fRYZiMM&^d2D(!qebZ29ik0(1~*ZiI&{?K&KQW*#}; zZ2bn&_v0c6z7U+LMC_AT6D(T0`wl&zOP167Qq75GE+`xoXms}8*pxNuJAZ!}P1P6s zM8cd8ViYhsDeE^RYHfqaHTB)+YTQ{sLU6I#KNqmT&o`BYkTsiHL5E7On@!6&m@7+u zB4~ka>=jVdu<=)dvTVBT2-L(BWi*XoDb!`$X+b{T41e7HvXU;`E>LQ8*&S+hbsToP zv}V@tae2YeijkSWKO(a^aXLzW88IAvbmlp`m$X0MZO-qh_&QFaV+K}Bu>->eiiidb z%|-j>%*Al^jrVKGmb0L|Wub`imcrb?hszcUmDvn-<&b`_P{*|F=0LKo>5a`}pbUW& zSK6OP^AeXI$v-)Y#R2>Rj^fvUIQGx2z4uXcP79XeStwG8f_<=V%;6*y@7mZ-R{1rh z($zG)cXU>qZ=D8EbkK@t~L@vwA;Ea=Mgo9-Uun&h=)A8IUbhiN2mV1 zIu{SPkP8ri_^)3R|8KW}{NwKZ(E{`;W&ZqC*R8j5%P|rVLV)#Mw02UqVVrL~|4I7Z zgf7!PQ^x*$h3$lxxpthk55}L4)ows>{52+1R5eGRfW;IBaRE@KOr6pkx2m^lfKc}H zmFCDAKIg3`9Q=a{;NEx7l31*9tLtApgx2-d3LGzxT^`9`P6-FQOfAIJvJ;z+ zPRDs|Mmnd#d2MuQ9q-Lz-lul#5p7Si@%dW(K}f9Biy!7BJs;b5@7;XE z(iwXLK^Q&Xsqp_jJF<2y#(V+$(dWl$<2&vDA6L(RM@-xTamG-LV++0Rl8&a_VsAI_ z1M=j5F$1Ea83q!ne?hJ{*;JGUF`EHd*?&OHnn5_L{DAn=uTjmQO3e@?fDaJ6L8#n$ zvhdK9(UfOm`TM`)Wrw_Ops?EU-y-b6EUbxI&QmTnm&AFAxzffVL=|;I4T!mjDd*|N z7!=&L*|1YoLN(7u+_uu=#!MR}=9!lwRB>fFmC8`jRR?`x7y|O0*YN{wfzX-b`P<7g z$^drsI+gHu(yhq*1!l2{MhbA4EbM5a$w~X_URUyJGJ)ku*JD2VXg#5#ciFv*E z8q6dkR(YK79qbzm$|tDo_izjjUfC<;tQm(rEZd-tQXir#XIbaN0%>S!(mus~Wl)qh zucXw-uJiBE1Zk_G7b*A9UwHm@mWWh>HTzV4bvpw-RIqgElMOF`bf{f~EzielF8>pc zuU4I25BVOLQc{b$WB_t45Qm2K`txiccWYc7*n0Sr`E&>(;qjCWX!}PA0O-q#{B!WW z<@sq`gD5yOBmV#ms=dfc%Qw@$e}PA%Gk77r zaXl$C#9;@|$$skKqcMqFHx}-#LMaZ>fabHFh_e;!p;Y%WbKBFDMMxuTYTx=r*FEo6-oJzQhOkRdX??E1jf`o{(#zVLcdzGC zTpa%M;pL0N;{=nn;|{o3UqzzmRVPBIk97Aja#^~#Rp$WiNb&Bt;^l};A8t`)q{W2? z)AOv6H#HK$E(jC0-`9eELtzmpEDwU8p?{k8lLf6o*h*x}_ilBs6KHg(MshptxquSP z$v?GQlLfi12#U5qXR@|MSoNCK=^kZB|4gC&P&Qvv-ZPS*{Xw=+L*I+oa;FM9ADr>^ zo&{?i_*QWwg%ySW){EuShqpRdWNW^Ck_&N56jPxufMrHiNG)=TFvsq~)}^2O8So`z zTa`{;EHhIP6vP=i>X+krWOE9KDV|JGh0MPh677dUbPOq3N^J=jzE$y6m}jwOz#l); zL`}Ruwnd7jFy51umEF_}2#w|DFR|p5RfKS=@1Z*UX`YRNbiD# z-hx3OfS@Qf$MLMC1Aieh{z4y@W<~wuF%)R%ovnDgyGubUG>wSOE^X|!7C9OZ3 zDD{{>^2Er!<1#4DCn%bdM`tjZfpWdUpO6bEOmy3T^UfVZoIsJ!)EU3uQ^b$qO`6#Y z;LmrRXx^kBME%E)cT%}AHaxF+Ukr@y;cCiU#Wk2hI||qmqW4cY@WhXVZ-|FHsnR0V zzZYAYm^wmWI5dv+qJYLm5DlqxNxea3Z3j4b7kXWC&sR5Jj?y?H=z0r-gr7Hyzh!6$ zADb&RX;y*3UKgF*cv5znbjQzN}-n@TzEj0T#D6`4X$JCI7?7+Ir}XY}uPV@=62Hl5`%F!{k#ki+9#5 zc7#;Ly+=I<=|b<=yECz^5``iUA}H2w48eWV1W9Z_y+$E>YMm#l>qVqlY6^v|Uu388 z%9cA`9ko8qi2bW6OgC&oI`ixOim$?qJPamWl>v_VL*e=A@394FznN>wLUH?&LNb)t z48ECu#|%y;5q9T|YUzdJ0!~*WwQaX`^;x5wm7nGOnXp04Izly5!vabD@?CxX;NK)lbT3`OR0d{fqjMSkqt zL~v+t4H$49o+Z+tQ$3_>=AK|v`JLYsH#^kXNqNi|g}6o7dXxzwj^!KmUUtoIpF$+4 z+Qq^Krg<5Js(-LR8j^%P>zxfKLtof{l4a839X&km)7fKqEu9Msvn@T zO54&g{25J+bXkkG^LC8|H^Ldhh}+HMa`N&8c{I)O`=&M2#^YE2y1aYOaBhK#gQ4jA zB4^8DAkb!Y|Jm`U49KxkO;%lx-L8wZrEk89ApceXwXRDBeoJE=NzEMQYgshz+4JIPDh z@rRRk8Kti8X<%u=M1St^EnJ{r{+TRO)M{c(Lvt!Swh*3GX}MLt z6IwH!PwR;Kp%@Q}dC_5uPyP~ARDH}|?7Nc6^nVsb^y`3G>YT4Y40^+A)O0-4%E?F@ zxS;G`G@3Ns($kj(*HL>T*KjDbSMa9r%?+<7sGw8HASI)k$7z1_k(XSY0&tyQ)MO?t zOK~+7`sK`N)Mbz#sSNC9=N8Wu{qEDvUbzz~Lo=+}LAG+$@3UUribL~cC!pRs|02eu zYI@x_YGPmuM}DTqzeiQ)_RprFwh2rDNB}VKd%TZGpzUEnK)k?UaPz=>P$)E`RzQ}T zii%3x?0R=3@&P}2%=O3d^uUFH)(HmA1+*oEX2uJLeR!BB)s84d6I?8?r)bsq^T(~n zJ&D#f#uclKQv6J4YbCJ^AvZEq=q2MA?jm*eXpF_?wvNXy@8PO`&h^KW10gE`z5?rG zlQF417rA&;BLu!rjTS8vR?{X}Fv#Bxs&iB9yq%T9`N$LIP9JG-5;Av%PLZ&GLO3+D z0#_K(8ihNO9yAPh-agLo`Gw8g8ihX}-_DJ>C-`@YJnfxO+oPIYGfhjBqaCmK-J|tr zsiytW_8qubGbf^;(M!`r-gC)%e{B-MW1f~9n14ln?O0k zUnFSIuI72HWQe9;N6~@DW_vi_VOT=R zlc-%~CW!zKr5PxRNsG@PFM}nIEO^uND(g!UOzlS(E2h%tWR^7Zi z$y=Ye=pC(v0E^xolnT}ArD%-K)M?nF93(z#*;Ea%Wj3$5eYoj3TAxij5gl%6C~|jj znt-!qb>oP`E5vW!f6Xo(SBulSv(cMu{CS*mNvI?3_sQs?I`-5@UE1g99l_LTZhjoc z@{5V|ar*gp3Bbx_C#({)gptu_)RxOdR4HZ>P62s|)!liHd{LuIY71rw5c%Jx9Oe`t zBYQ1rsk;P9hdmK__K>RIOgnjUC5PLtc;PdNY7;b6+JeTrc#pD@83N-1-3=e`_br{DY2{&>Ov~vD_=AS60>0HW zahleGV|QdRBeR;@OgE=n9)~L6k+%eHIzPKxL@{B_7ru~!&uo0)gT`oYmx9|#@?;X4 zN=Ll3yu|`tR@h-Z-d9wE?OjB*~@T8hBz1V_XnoK2ARG%vas#noQ@rg z8_m)t)IB|m7--43N5rxcl0ackp6JNxZ5Crc1A%GPjxnZ z?aZXM&nUs~MrkA`V&l8#t~y&w+dtV4!ZmEM)7-~g@H49cE>E82zN1rhU^%Nd>ct7g z#t~iYR%5E-!vEhl94=KOIvr|zEVArk|7;}$9(roDMb;q(V=c;7Mg5=RMy&Ktd%{PUZ^JQl$w- zOtlFe%NG7hAKk3N3HU1>fA9~v>U$TTH_X;%4++m5W;3c>${t&i<>u4pGbYb2ZM96U z3tvXNJl=ay`1Mi3u;@{HYRAMkV|-i8A3=JB_q$17SXfO#>Qer}KcYG-Q`zUedu`Rl zpipl4R(dvj3CK~FAcPuiGSYp?VBi?MY0DISFkxl!U6YxU+50Qc2OjNej;K52+;(p= z>hMAps%GPJO59`Ct%*UBjUGeR;P)s%xg5edh>Bjd?cPe>X|^38G!&{-uXY!EvL)I6 zC*ci621-9EPiWK#Fa9j6bBcD2`;}x=;Z4_>Y>^OByf2fVQFwbr#EQi)u086xSuBa*H2}4)E+>cuw;bH6lSMg5YIrvQF37JSajW%Sf zavfc?U9;=z)E$*AucC<)wPik3WW&DA_r3cJl~I=c$__gEp4}{vjDeQ#5gYr_1EHGkkX@6mLVxvL2G79bbiT@J=?NS zYf&FS-RJq$%UJ@BQei|(4A)FgU6%_-+O*C_wd|L~i#2!>o$XNmNuPi|qzf+CFW8jz zn&4dkBh7{_Q0m3B|ADZbO4@^k_P#CrFPkzb2@yXMbkQYIpmph=8>F8*;be4La|ZI? z7gRewJ5gnQW=hc{>0$MG@mh-UO;OHZfgUbr<&{dBRx^Ol@>&KqUYq8gha5}a%6F3G z9JP{Pb{P3)18Fm8r-?U$#);nD!TY`-KvrIUfTP|^*O28c&cGnUt{Hrb^50PX@0gqb zC5iE8-rNc_Y+#&UAnhI>JbQ7JBx7B2GyR)!1H1{I6n1M){MO96j$Pdc<+rN-rdzgM zh24P-%c$(DoI)I{MOugA$st9erKw_>_ZdPcv8bCq-jV@z-yUZDn%h1=WgoF2+CBOr zO^&%|MY7OFdV=LGQD}EJ{lNlwrpIJP2+(ynU_v${%uK|VNvdOteS^IAM374y^|Oq+ zpidVhjjM7ewMldPlt2J!QHr})GJ3I|Jzohp;^_3n1r8xi{|M`RoyyfE&S_LMIl{A? z?w+r=br2v)!|rG1%)%eCHrFuL_XL@S`Q6vtsV(4;SRv%Y>K+mF=qDxetEE%+F9V!E zwtQWSn>{C}>LJ^_5R_O`!{%k3Vrj3*yd)@tcl3PwA27QTD;t>`xj1|Cc24&?w=+8u zh51J;3Q36O;#jO5hb(m~{kZ9Er|3SwQo=ru3*;OeFh=#h(Wm2Ru)Uj7ut z;WzY%Q9`mBof;4g{t7c0>-DDduoCMF$Wk0ymmK#E>A>9nf&U;dcBXZ=wqUaM1`~!v zb4>l*~RF$6oMAa2Ctly4`WTSuy~TPQTjc}z+u#&w21 z3zzX(s`tw+{`jO?dxmf0&eoQKA29QAazvHN_&XBwA^7S<=&H?OX;}QipeLKJ3s}ol zSz63FZcQj@laqD9rtr>Fit&Q*Gv$l?M99ZBNY2lcCh1MDh|-6$WqW+j zH%fh?Sq^*Ku6Jpi@v1pu&|3P0HNczA7t6Ius+wfky?5xHFPUnRl;X$e;AzIep97fV z4ivDy^p%oCDyM%wnfJ$gk8o@c?JkF$UT3Vijek9`v#i;pTVe*Z+bxIAFX^-EmafT}51Dg?V-~V`d<}%9iAe zZS!`cY(L@~117miT0KdCg{67NcFs?HFkEE2RQk$;%zA-6@J-_rWfaLoFCIdDsVv-h2zo-oP!z6KKkG+z`ye=;ZZQu=^wa!bMRoA$-iOyr(P=3^{^anPtj=+qS{I$dsM`Z8gn># zPoakFB}r82Izo&=6CC1#*BBGG$3v#A&V}|>cfIhX-)ugin_4%}nbE#m>^>Rk!#9;E z1N6)ulkzra;~83D6fllM!XDB7Ot+W&{?ZH^VXbMQ-8?}*wNN8Sjbf?%k6*pd39N2+ zOHa_euGjbfJS>ZoCY~1vwiubayK59EdE8W67@IsMDoXQ|GY z7x|HniLMmwq#B^VUn|FfI%TB!P-XW`y*fnaSDxy%73@klXQ^F^YehercLMGkr*%WG z?034JP^K5{%V*yK+n zU$$Dvl{HgOS9bmOOrwIa$Qn?O_-U`DP54&~QP{uXHhXHhVE0|$xKe4uO;s0TT~Yzf zGqK{WZ_8%Ew(=GliqJj1QmXN0d(o31hIh>6=%G_p{PViFvT?9^YuJ44+NydkeEExB z6FLzdj}e*T1#cjl?Tn@z)OIgAGKCHD_cn}}(blf)C zP13Zp&1U2#eaR+Z<;T=-CLQO?_lRX5jc_@zjZXm4c=vSv^EY-Bo~x?y%q_C&RxT%7 zW07w~vSu~+EA#kwf-p`xekg49?}VCGP4Z?7Bzcus6TE?bD$~|MCfg)8v!E0Zp8cpz zlz2ve{JYO^HS>J#5D$hyvn2eG|u%OQ}emX1y^HRu6X&`$IY zKNS@(B~gds|I!`R74{F)TA#d(g)9MbM4@{1oERfrd^?|K=9fi5K$reLf@^R!`&>7+ z`?28-`c#3Tf(uj}!@iL|F}>C-lrL?PcZQk-w4)Yq)sDTk^VfQWK1L{);zOSH(9kbM z4V=?SO0WDYz~LL20e=3V^V;kinbrPFe*C8dz*PYH^@0M@XZB|``ketCW&2P@Yjdno&%r2${_vO|Uh7Vo&J8I%)1 z9U!waLICW`(*@$XR6MC#j6R@%!E!I&Q%}YO$Oy;*3aBEOB<4-SWq{Nr&G$}OnBsQv zOZ>~Fg!^8XIn)OiXPO7yfvwv#P^ghO;qE=ARZ`*k+ax!$sV*;a(ys3KUu@ke$vfab z8AuD_=Nyut)-F9+jea(JdJ6VEJ}K_?vK67ex=1{k{YriM^0dL1nu4k?gF6zG=bxOu zX1zKyqH4x=5^>dvO1oez{yL-vp4Wx+JrnxnA8_tYT7S9ql;P)v<*7CT7jP&;L)AC` z;0wss1kvTMQ8~z0#}ygJnStPFmyKJ?pk|Gl0OaQ`tw!LW{@tMpIII1>a?QPhAH I$-fHzA1*1=3IG5A literal 0 HcmV?d00001 diff --git a/code/datums/ai/monkey/monkey.bt.json b/code/datums/ai/monkey/monkey.bt.json new file mode 100644 index 00000000000..0bf3ac811e6 --- /dev/null +++ b/code/datums/ai/monkey/monkey.bt.json @@ -0,0 +1,245 @@ +{ + "dm_type": "/datum/ai_controller/monkey", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "1 SECONDS", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/monkey_combat" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/monkey_serve_food" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/generic_hunger" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/generic_play_instrument" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/monkey_shenanigans" + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/monkey_idle" + } + ] + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CURRENT_TARGET", + "invert": true + }, + "child": { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/monkey_set_combat_target", + "vars": { + "attack_target_key": "BB_CURRENT_TARGET", + "enemies_key": "BB_monkey_enemies" + } + }, + { + "type": "sequence", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_true", + "vars": { + "key": "BB_MONKEY_WANNA_PRESS_SOME_SHIT" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_monkey_current_press_target", + "targeting_strategy": "/datum/targeting_strategy/anything", + "target_source": "/datum/target_source/monkey_press_target", + "vision_range": 2 + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_true", + "vars": { + "invert": true, + "key": "BB_MONKEY_WANNA_PRESS_SOME_SHIT" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/random_chance", + "vars": { + "chance": 0.2 + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/set_bb_key", + "vars": { + "target_key": "BB_MONKEY_WANNA_PRESS_SOME_SHIT", + "value": true + } + } + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/succeed" + } + ] + }, + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/target_is_holding_item", + "vars": { + "key": "BB_MY_PAWN" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_monkey_current_give_target", + "targeting_strategy": "/datum/targeting_strategy/anything", + "target_source": "/datum/target_source/oview_single_type/human_mob", + "vision_range": 2 + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/target_is_holding_item", + "vars": { + "invert": true, + "key": "BB_MY_PAWN" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/clear_key", + "vars": { + "key": "BB_monkey_current_give_target" + } + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/succeed" + } + ] + }, + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/is_holding_target", + "vars": { + "key": "BB_SONG_INSTRUMENT" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/succeed" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_monkey_tamed" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_SONG_INSTRUMENT", + "targeting_strategy": "/datum/targeting_strategy/pickup_item", + "target_source": "/datum/target_source/held_items_typed/instrument" + } + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/succeed" + } + ] + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/pawn_nutrition_below", + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/key_off_cooldown", + "vars": { + "cooldown_key": "BB_NEXT_HUNGRY" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "bb_food_target", + "targeting_strategy": "/datum/targeting_strategy/pickup_item/food_or_drink", + "target_source": "/datum/target_source/held_items_then_oview", + "vision_range": 2 + } + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "invert": true, + "key": "BB_monkey_tamed" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target/monkey_find_weapon", + "vars": { + "target_key": "BB_monkey_pickuptarget", + "target_source": "/datum/target_source/monkey_weapon_upgrade", + "targeting_strategy": "/datum/targeting_strategy/monkey_weapon_upgrade", + "vision_range": 5 + } + } + } + ] + } + ] + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target/monkey_find_weapon", + "vars": { + "target_key": "BB_monkey_pickuptarget", + "target_source": "/datum/target_source/monkey_weapon_upgrade", + "targeting_strategy": "/datum/targeting_strategy/monkey_weapon_upgrade", + "vision_range": 5 + } + } + ] +} diff --git a/code/datums/ai/monkey/monkey_behaviors.dm b/code/datums/ai/monkey/monkey_behaviors.dm deleted file mode 100644 index 865f6a069ea..00000000000 --- a/code/datums/ai/monkey/monkey_behaviors.dm +++ /dev/null @@ -1,305 +0,0 @@ -/datum/ai_behavior/battle_screech/monkey - screeches = list("roar","screech") - -/datum/ai_behavior/monkey_equip - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH - -/datum/ai_behavior/monkey_equip/setup(datum/ai_controller/controller, target_key) - . = ..() - var/obj/target = controller.blackboard[target_key] - if(QDELETED(target)) - return FALSE - set_movement_target(controller, target) - -/datum/ai_behavior/monkey_equip/finish_action(datum/ai_controller/controller, success, target_key) - . = ..() - - if(!success) //Don't try again on this item if we failed - controller.set_blackboard_key_assoc(BB_MONKEY_BLACKLISTITEMS, controller.blackboard[target_key], TRUE) - - controller.clear_blackboard_key(BB_MONKEY_PICKUPTARGET) - -/// Equips an item on the monkey -/// Returns TRUE if it works out, FALSE otherwise -/datum/ai_behavior/monkey_equip/proc/equip_item(datum/ai_controller/controller) - var/mob/living/living_pawn = controller.pawn - - var/obj/item/target = controller.blackboard[BB_MONKEY_PICKUPTARGET] - var/best_force = controller.blackboard[BB_MONKEY_BEST_FORCE_FOUND] - if(!isturf(living_pawn.loc)) - return FALSE - - if(!target) - return FALSE - - if(target.anchored) //Can't pick it up, so stop trying. - return FALSE - - // Strong weapon - else if(target.force > best_force) - living_pawn.drop_all_held_items() - living_pawn.put_in_hands(target) - controller.set_blackboard_key(BB_MONKEY_BEST_FORCE_FOUND, target.force) - return TRUE - - else if(target.slot_flags) //Clothing == top priority - living_pawn.dropItemToGround(target, TRUE) - living_pawn.update_icons() - if(!living_pawn.equip_to_appropriate_slot(target)) - return FALSE //Already wearing something, in the future this should probably replace the current item but the code didn't actually do that, and I dont want to support it right now. - return TRUE - - // EVERYTHING ELSE - else if(living_pawn.get_empty_held_indexes()) - living_pawn.put_in_hands(target) - return TRUE - - return FALSE - -/datum/ai_behavior/monkey_equip/ground - -/datum/ai_behavior/monkey_equip/ground/perform(seconds_per_tick, datum/ai_controller/controller, target_key) - . = ..() - if(equip_item(controller)) - return . | AI_BEHAVIOR_SUCCEEDED - return . | AI_BEHAVIOR_FAILED - -/datum/ai_behavior/monkey_equip/pickpocket - -/datum/ai_behavior/monkey_equip/pickpocket/perform(seconds_per_tick, datum/ai_controller/controller, target_key) - . = ..() - if(controller.blackboard[BB_MONKEY_PICKPOCKETING]) //We are pickpocketing, don't do ANYTHING!!!! - return - INVOKE_ASYNC(src, PROC_REF(attempt_pickpocket), controller) - -/datum/ai_behavior/monkey_equip/pickpocket/proc/attempt_pickpocket(datum/ai_controller/controller) - var/obj/item/target = controller.blackboard[BB_MONKEY_PICKUPTARGET] - var/mob/living/victim = target.loc - var/mob/living/living_pawn = controller.pawn - - if(!istype(victim)) - finish_action(controller, FALSE) - return - - victim.visible_message(span_warning("[living_pawn] starts trying to take [target] from [victim]!"), span_danger("[living_pawn] tries to take [target]!")) - - controller.set_blackboard_key(BB_MONKEY_PICKPOCKETING, TRUE) - - var/success = FALSE - - if(do_after(living_pawn, MONKEY_ITEM_SNATCH_DELAY, victim) && target && victim.IsReachableBy(living_pawn)) - - for(var/obj/item/I in victim.held_items) - if(I == target) - victim.visible_message(span_danger("[living_pawn] snatches [target] from [victim]."), span_userdanger("[living_pawn] snatched [target]!")) - if(victim.temporarilyRemoveItemFromInventory(target)) - if(!QDELETED(target) && !equip_item(controller)) - target.forceMove(living_pawn.drop_location()) - success = TRUE - break - else - victim.visible_message(span_danger("[living_pawn] tried to snatch [target] from [victim], but failed!"), span_userdanger("[living_pawn] tried to grab [target]!")) - - finish_action(controller, success) //We either fucked up or got the item. - -/datum/ai_behavior/monkey_equip/pickpocket/finish_action(datum/ai_controller/controller, success) - . = ..() - controller.set_blackboard_key(BB_MONKEY_PICKPOCKETING, FALSE) - controller.clear_blackboard_key(BB_MONKEY_PICKUPTARGET) - -/datum/ai_behavior/monkey_flee - -/datum/ai_behavior/monkey_flee/perform(seconds_per_tick, datum/ai_controller/controller) - var/mob/living/living_pawn = controller.pawn - - if(living_pawn.health >= MONKEY_FLEE_HEALTH) //we're back in bussiness - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - - var/mob/living/target = null - - // flee from anyone who attacked us and we didn't beat down - for(var/mob/living/L in view(living_pawn, MONKEY_FLEE_VISION)) - if(controller.blackboard[BB_MONKEY_ENEMIES][L] && L.stat == CONSCIOUS) - target = L - break - - if(target) - GLOB.move_manager.move_away(living_pawn, target, max_dist=MONKEY_ENEMY_VISION, delay=5) - return AI_BEHAVIOR_DELAY - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -/datum/ai_behavior/monkey_attack_mob - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_MOVE_AND_PERFORM | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION //performs to increase frustration - -/datum/ai_behavior/monkey_attack_mob/setup(datum/ai_controller/controller, target_key) - . = ..() - set_movement_target(controller, controller.blackboard[target_key]) - -/datum/ai_behavior/monkey_attack_mob/perform(seconds_per_tick, datum/ai_controller/controller, target_key) - var/mob/living/target = controller.blackboard[target_key] - var/mob/living/living_pawn = controller.pawn - var/datum/targeting_strategy/strategy = GET_TARGETING_STRATEGY(controller.blackboard[BB_TARGETING_STRATEGY]) - - if(QDELETED(target) || !strategy.can_attack(living_pawn, target)) //Target == owned - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - - // check if target has a weapon - var/holding_weapon - for(var/obj/item/potential_weapon in target.held_items) - if(!(potential_weapon.item_flags & ABSTRACT)) - holding_weapon = potential_weapon - break - - var/attack_results = monkey_attack(controller, target, seconds_per_tick, holding_weapon && SPT_PROB(MONKEY_ATTACK_DISARM_PROB, seconds_per_tick), holding_weapon) - - if(!attack_results || controller.blackboard[BB_MONKEY_AGGRESSIVE]) - return AI_BEHAVIOR_DELAY - - //check if we can de-aggro on the enemy... - var/hatred_value = controller.blackboard[BB_MONKEY_ENEMIES][target] - - if(isnull(hatred_value)) - hatred_value = 1 - controller.set_blackboard_key_assoc(BB_MONKEY_ENEMIES, target, hatred_value) - - if(!SPT_PROB(MONKEY_HATRED_REDUCTION_PROB, seconds_per_tick)) - return AI_BEHAVIOR_DELAY - - //we decrease our hatred value to them by 1 - hatred_value-- - if(hatred_value <= 0) - controller.remove_thing_from_blackboard_key(BB_MONKEY_ENEMIES, target) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - - controller.set_blackboard_key_assoc(BB_MONKEY_ENEMIES, target, hatred_value) - return AI_BEHAVIOR_DELAY - -/datum/ai_behavior/monkey_attack_mob/finish_action(datum/ai_controller/controller, succeeded, target_key) - . = ..() - controller.clear_blackboard_key(target_key) - -/// attack using a held weapon otherwise bite the enemy, then if we are angry there is a chance we might calm down a little -/datum/ai_behavior/monkey_attack_mob/proc/monkey_attack(datum/ai_controller/controller, mob/living/target, seconds_per_tick, disarm, holding_weapon) - var/mob/living/living_pawn = controller.pawn - - if(living_pawn.next_move > world.time) - return FALSE - - //are we holding a gun? can we shoot it? if so, FIRE - var/obj/item/gun/gun_to_shoot = locate() in living_pawn.held_items - if(gun_to_shoot?.can_shoot()) - if(gun_to_shoot != living_pawn.get_active_held_item()) - living_pawn.swap_hand(living_pawn.get_inactive_hand_index()) - controller.ai_interact(target = target, combat_mode = TRUE) - return TRUE - - //look for any potential weapons we're holding - var/obj/item/potential_weapon = locate() in living_pawn.held_items - if(!target.IsReachableBy(living_pawn, potential_weapon?.reach)) - return FALSE - - if(isnull(potential_weapon)) - controller.ai_interact(target = target, modifiers = disarm ? list(RIGHT_CLICK = TRUE) : null, combat_mode = TRUE) - if(disarm && !isnull(holding_weapon) && controller.blackboard[BB_MONKEY_BLACKLISTITEMS][holding_weapon]) - controller.remove_thing_from_blackboard_key(BB_MONKEY_BLACKLISTITEMS, holding_weapon) //lets try to pickpocket it again! - return TRUE - - if(potential_weapon != living_pawn.get_active_held_item()) - living_pawn.swap_hand(living_pawn.get_inactive_hand_index()) - controller.ai_interact(target = target, combat_mode = TRUE) - return TRUE - -/datum/ai_behavior/disposal_mob - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_MOVE_AND_PERFORM //performs to increase frustration - -/datum/ai_behavior/disposal_mob/setup(datum/ai_controller/controller, attack_target_key, disposal_target_key) - . = ..() - set_movement_target(controller, controller.blackboard[attack_target_key]) - -/datum/ai_behavior/disposal_mob/finish_action(datum/ai_controller/controller, succeeded, attack_target_key, disposal_target_key) - . = ..() - controller.clear_blackboard_key(attack_target_key) //Reset attack target - controller.set_blackboard_key(BB_MONKEY_DISPOSING, FALSE) //No longer disposing - controller.clear_blackboard_key(disposal_target_key) //No target disposal - -/datum/ai_behavior/disposal_mob/perform(seconds_per_tick, datum/ai_controller/controller, attack_target_key, disposal_target_key) - if(controller.blackboard[BB_MONKEY_DISPOSING]) //We are disposing, don't do ANYTHING!!!! - return AI_BEHAVIOR_DELAY - - var/mob/living/target = controller.blackboard[attack_target_key] - var/mob/living/living_pawn = controller.pawn - - set_movement_target(controller, target) - - if(!target) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - if(target.pulledby != living_pawn && !HAS_AI_CONTROLLER_TYPE(target.pulledby, /datum/ai_controller/monkey)) //Dont steal from my fellow monkeys. - if(living_pawn.Adjacent(target) && isturf(target.loc)) - target.grabbedby(living_pawn) - return AI_BEHAVIOR_DELAY //Do the rest next turn - - var/obj/machinery/disposal/disposal = controller.blackboard[disposal_target_key] - set_movement_target(controller, disposal) - - if(!disposal) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - if(living_pawn.Adjacent(disposal)) - INVOKE_ASYNC(src, PROC_REF(try_disposal_mob), controller, attack_target_key, disposal_target_key) //put him in! - return AI_BEHAVIOR_DELAY - //This means we might be getting pissed! - return AI_BEHAVIOR_DELAY - -/datum/ai_behavior/disposal_mob/proc/try_disposal_mob(datum/ai_controller/controller, attack_target_key, disposal_target_key) - var/mob/living/living_pawn = controller.pawn - var/mob/living/target = controller.blackboard[attack_target_key] - var/obj/machinery/disposal/disposal = controller.blackboard[disposal_target_key] - - controller.set_blackboard_key(BB_MONKEY_DISPOSING, TRUE) - - if(target && disposal?.stuff_mob_in(target, living_pawn)) - disposal.flush() - finish_action(controller, TRUE, attack_target_key, disposal_target_key) - - -/datum/ai_behavior/recruit_monkeys/perform(seconds_per_tick, datum/ai_controller/controller) - controller.set_blackboard_key(BB_MONKEY_RECRUIT_COOLDOWN, world.time + MONKEY_RECRUIT_COOLDOWN) - var/mob/living/living_pawn = controller.pawn - - for(var/mob/living/nearby_monkey in view(living_pawn, MONKEY_ENEMY_VISION)) - if(QDELETED(nearby_monkey) || !HAS_AI_CONTROLLER_TYPE(nearby_monkey, /datum/ai_controller/monkey)) - continue - if(!SPT_PROB(MONKEY_RECRUIT_PROB, seconds_per_tick)) - continue - // Recruited a monkey to our side - controller.set_blackboard_key(BB_MONKEY_RECRUIT_COOLDOWN, world.time + MONKEY_RECRUIT_COOLDOWN) - // Other monkeys now also hate the guy we're currently targeting - nearby_monkey.ai_controller.add_blackboard_key_assoc(BB_MONKEY_ENEMIES, controller.blackboard[BB_MONKEY_CURRENT_ATTACK_TARGET], MONKEY_RECRUIT_HATED_AMOUNT) - - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -/datum/ai_behavior/monkey_set_combat_target/perform(seconds_per_tick, datum/ai_controller/controller, set_key, enemies_key) - var/list/enemies = controller.blackboard[enemies_key] - var/list/valids = list() - for(var/mob/living/possible_enemy in view(MONKEY_ENEMY_VISION, controller.pawn)) - if(possible_enemy == controller.pawn) - continue // don't target ourselves - if(!enemies[possible_enemy]) //We don't hate this creature! But we might still attack it! - if(!controller.blackboard[BB_MONKEY_AGGRESSIVE]) //We are not aggressive either, so we won't attack! - continue - if(possible_enemy.has_faction(list(FACTION_MONKEY, FACTION_JUNGLE)) && !controller.blackboard[BB_MONKEY_TARGET_MONKEYS]) // do not target your team. includes monkys gorillas etc. - continue - // Weighted list, so the closer they are the more likely they are to be chosen as the enemy - valids[possible_enemy] = CEILING(100 / (get_dist(controller.pawn, possible_enemy) || 1), 1) - - if(!length(valids)) - return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED - - var/mob/living/target = pick_weight(valids) - - EVLOG_MAPTEXT(controller, EVLOG_CATEGORY_AI_TARGETING, "[controller.pawn] has selected [target] as a target for blackboard key [set_key]! Behavior: [src]", get_turf(target), "Target: [target]") - EVLOG_LINES(controller, EVLOG_CATEGORY_AI_TARGETING, "Line to target", get_turf(controller.pawn), get_turf(target)) - - controller.set_blackboard_key(set_key, target) - return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED diff --git a/code/datums/ai/monkey/monkey_bt_nodes.dm b/code/datums/ai/monkey/monkey_bt_nodes.dm new file mode 100644 index 00000000000..44b898a7b5b --- /dev/null +++ b/code/datums/ai/monkey/monkey_bt_nodes.dm @@ -0,0 +1,402 @@ + +/// Monkey's battle screech variant louder and on a 5 second cooldown +/datum/bt_node/ai_behavior/battle_screech/monkey + time_between_perform = 5 SECONDS + screeches = list("roar", "screech") + + +/// Base equip behavior; handles blacklist updates and key cleanup on finish +/datum/bt_node/ai_behavior/monkey_equip + var/target_key + +/datum/bt_node/ai_behavior/monkey_equip/finish_action(datum/ai_controller/controller, success) + . = ..() + if(!success) // Don't try to pick this item up again + controller.set_blackboard_key_assoc(BB_MONKEY_BLACKLISTITEMS, controller.blackboard[target_key], TRUE) + controller.clear_blackboard_key(BB_MONKEY_PICKUPTARGET) + controller.clear_blackboard_key(BB_MONKEY_PICKUP_IS_PICKPOCKET) + +/// Equip a weapon off the ground +/datum/bt_node/ai_behavior/monkey_equip/ground + +/datum/bt_node/ai_behavior/monkey_equip/ground/setup(datum/ai_controller/controller) + var/obj/item/target = controller.blackboard[target_key] + return !QDELETED(target) + +/datum/bt_node/ai_behavior/monkey_equip/ground/perform(seconds_per_tick, datum/ai_controller/controller) + var/async_flags = handle_async() + if(async_flags) + return async_flags + + return start_async() + +/datum/bt_node/ai_behavior/monkey_equip/ground/perform_async(datum/ai_controller/controller) + var/result = equip_item(controller) + if(!async_still_valid()) + return + finish_async(result ? AI_BEHAVIOR_SUCCEEDED : AI_BEHAVIOR_FAILED) + +/// Pickpocket a weapon from a mob +/datum/bt_node/ai_behavior/monkey_equip/pickpocket + +/datum/bt_node/ai_behavior/monkey_equip/pickpocket/setup(datum/ai_controller/controller) + var/obj/item/target = controller.blackboard[target_key] + return !QDELETED(target) + +/datum/bt_node/ai_behavior/monkey_equip/pickpocket/perform(seconds_per_tick, datum/ai_controller/controller) + if(controller.blackboard[BB_MONKEY_PICKPOCKETING]) // mid-snatch; wait + return AI_BEHAVIOR_DELAY + INVOKE_ASYNC(src, PROC_REF(attempt_pickpocket), controller) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + +/datum/bt_node/ai_behavior/monkey_equip/pickpocket/proc/attempt_pickpocket(datum/ai_controller/controller) + var/obj/item/target = controller.blackboard[BB_MONKEY_PICKUPTARGET] + var/mob/living/victim = target?.loc + var/mob/living/living_pawn = controller.pawn + + if(!istype(victim)) + finish_action(controller, FALSE) + return + + victim.visible_message(span_warning("[living_pawn] starts trying to take [target] from [victim]!"), span_danger("[living_pawn] tries to take [target]!")) + controller.set_blackboard_key(BB_MONKEY_PICKPOCKETING, TRUE) + + var/success = FALSE + + if(do_after(living_pawn, MONKEY_ITEM_SNATCH_DELAY, victim) && target && victim.IsReachableBy(living_pawn)) + for(var/obj/item/I in victim.held_items) + if(I == target) + victim.visible_message(span_danger("[living_pawn] snatches [target] from [victim]."), span_userdanger("[living_pawn] snatched [target]!")) + if(victim.temporarilyRemoveItemFromInventory(target)) + if(!QDELETED(target) && !equip_item(controller)) + target.forceMove(living_pawn.drop_location()) + success = TRUE + break + else + victim.visible_message(span_danger("[living_pawn] tried to snatch [target] from [victim], but failed!"), span_userdanger("[living_pawn] tried to grab [target]!")) + + finish_action(controller, success) + +/datum/bt_node/ai_behavior/monkey_equip/pickpocket/finish_action(datum/ai_controller/controller, success) + . = ..() + controller.set_blackboard_key(BB_MONKEY_PICKPOCKETING, FALSE) + +/// Shared item equip proc +/datum/bt_node/ai_behavior/monkey_equip/proc/equip_item(datum/ai_controller/controller) + var/mob/living/living_pawn = controller.pawn + var/obj/item/target = controller.blackboard[BB_MONKEY_PICKUPTARGET] + var/best_force = controller.blackboard[BB_MONKEY_BEST_FORCE_FOUND] + + if(!isturf(living_pawn.loc)) + return FALSE + if(!target) + return FALSE + if(target.anchored) + return FALSE + + if(target.force > best_force) // better weapon + living_pawn.drop_all_held_items() + living_pawn.put_in_hands(target) + controller.set_blackboard_key(BB_MONKEY_BEST_FORCE_FOUND, target.force) + return TRUE + + if(target.slot_flags) // wearable + living_pawn.dropItemToGround(target, TRUE) + living_pawn.update_icons() + if(!living_pawn.equip_to_appropriate_slot(target)) + return FALSE + return TRUE + + if(living_pawn.get_empty_held_indexes()) // any free hand + living_pawn.put_in_hands(target) + return TRUE + + return FALSE + +/// Gathers weapon upgrade candidates: nearby ground items, then held items of nearby humans. +/datum/target_source/monkey_weapon_upgrade + +/datum/target_source/monkey_weapon_upgrade/collect_candidates(mob/living/pawn, datum/ai_controller/controller, range) + var/list/candidates = list() + for(var/obj/item/ground_item in oview(range, pawn)) + candidates += ground_item + for(var/mob/living/carbon/human/nearby_human in oview(range, pawn)) + candidates += nearby_human.held_items + return candidates + +/// Weapon upgrade candidate: not two-handed, not blacklisted, and hits harder than our bite. +/datum/targeting_strategy/monkey_weapon_upgrade + +/datum/targeting_strategy/monkey_weapon_upgrade/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + var/obj/item/candidate = target + if(HAS_TRAIT(candidate, TRAIT_NEEDS_TWO_HANDS) || controller.blackboard[BB_MONKEY_BLACKLISTITEMS][candidate]) + return FALSE + if(candidate.force < 2) // our bite already does ~2 damage + return FALSE + return TRUE + +/// Scans nearby items and mobs for a better weapon and sets BB_MONKEY_PICKUPTARGET +/datum/bt_node/ai_behavior/acquire_target/update_interaction_target/monkey_find_weapon + +/datum/bt_node/ai_behavior/acquire_target/update_interaction_target/monkey_find_weapon/can_search(datum/ai_controller/controller) + var/mob/living/living_pawn = controller.pawn + if(!(locate(/obj/item) in living_pawn.held_items)) + controller.set_blackboard_key(BB_MONKEY_BEST_FORCE_FOUND, 0) + if(controller.blackboard[BB_MONKEY_GUN_NEURONS_ACTIVATED] && (locate(/obj/item/gun) in living_pawn.held_items)) + return FALSE // already have a gun + return ..() + +/// Prefers any gun once gun neurons are activated, else the strongest candidate that beats our current best held item. +/datum/bt_node/ai_behavior/acquire_target/update_interaction_target/monkey_find_weapon/pick_final_target(datum/ai_controller/controller, list/filtered_targets) + var/mob/living/living_pawn = controller.pawn + + if(controller.blackboard[BB_MONKEY_GUN_NEURONS_ACTIVATED]) + for(var/obj/item/candidate as anything in filtered_targets) + if(isgun(candidate)) + return candidate + + var/top_force = 0 + for(var/obj/item/held in living_pawn.held_items) + if(HAS_TRAIT(held, TRAIT_NEEDS_TWO_HANDS) || controller.blackboard[BB_MONKEY_BLACKLISTITEMS][held]) + continue + top_force = max(top_force, held.force) + + var/obj/item/best + for(var/obj/item/candidate as anything in filtered_targets) + if(candidate.force <= top_force) + continue + best = candidate + top_force = candidate.force + return best + +/datum/bt_node/ai_behavior/acquire_target/update_interaction_target/monkey_find_weapon/on_target_found(datum/ai_controller/controller, atom/target, datum/targeting_strategy/strategy) + controller.set_blackboard_key(BB_MONKEY_PICKUP_IS_PICKPOCKET, ismob(target.loc) ? TRUE : null) + + +/// Selects a target from BB_MONKEY_ENEMIES or picks any visible mob if aggressive. This should be ported to new targetting but its so fkn bespoke +/datum/bt_node/ai_behavior/monkey_set_combat_target + var/attack_target_key + var/enemies_key + +/datum/bt_node/ai_behavior/monkey_set_combat_target/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/living_pawn = controller.pawn + var/list/enemies = controller.blackboard[enemies_key] + + if(HAS_TRAIT(living_pawn, TRAIT_PACIFISM) || (!length(enemies) && !controller.blackboard[BB_MONKEY_AGGRESSIVE])) + living_pawn.set_combat_mode(FALSE) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + + var/list/valids = list() + for(var/mob/living/possible_enemy in view(MONKEY_ENEMY_VISION, living_pawn)) + if(possible_enemy == living_pawn) + continue + if(!enemies[possible_enemy]) + if(!controller.blackboard[BB_MONKEY_AGGRESSIVE]) + continue + if(possible_enemy.has_faction(list(FACTION_MONKEY, FACTION_JUNGLE)) && !controller.blackboard[BB_MONKEY_TARGET_MONKEYS]) + continue + if(IS_DEAD_OR_INCAP(possible_enemy)) // Dont bother, theyre fucked. + continue + valids[possible_enemy] = CEILING(100 / (get_dist(living_pawn, possible_enemy) || 1), 1) + + if(!length(valids)) + living_pawn.set_combat_mode(FALSE) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + + var/mob/living/target = pick_weight(valids) + + EVLOG_MAPTEXT(controller, EVLOG_CATEGORY_AI_TARGETING, "[living_pawn] has selected [target] as a target for blackboard key [attack_target_key]! Behavior: [src]", get_turf(target), "Target: [target]") + EVLOG_LINES(controller, EVLOG_CATEGORY_AI_TARGETING, "Line to target", get_turf(living_pawn), get_turf(target)) + + living_pawn.set_combat_mode(TRUE) + controller.set_blackboard_key(attack_target_key, target) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED + + +/// Attacks the target mob; SUCCEEDED when target is gone, FAILED when target goes down (Which lets us flush the fucker instead) +/datum/bt_node/ai_behavior/monkey_attack_mob + var/target_key + time_between_perform = CLICK_CD_MELEE + /// seconds_per_tick from the perform() that kicked off the current async attack. + VAR_PRIVATE/attack_seconds_per_tick = 0 + /// Weapon snapshot from the perform() that kicked off the current async attack. + VAR_PRIVATE/obj/item/attack_holding_weapon + +/datum/bt_node/ai_behavior/monkey_attack_mob/perform(seconds_per_tick, datum/ai_controller/controller) + var/async_flags = handle_async() + if(async_flags) + return async_flags + + var/mob/living/target = controller.blackboard[target_key] + var/mob/living/living_pawn = controller.pawn + var/datum/targeting_strategy/strategy = GET_TARGETING_STRATEGY(controller.blackboard[BB_TARGETING_STRATEGY]) + + if(QDELETED(target) || !strategy.is_valid_target(living_pawn, target)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + var/obj/item/holding_weapon + for(var/obj/item/potential_weapon in target.held_items) + if(!(potential_weapon.item_flags & ABSTRACT)) + holding_weapon = potential_weapon + break + + attack_seconds_per_tick = seconds_per_tick + attack_holding_weapon = holding_weapon + return start_async() + +/datum/bt_node/ai_behavior/monkey_attack_mob/perform_async(datum/ai_controller/controller) + var/mob/living/target = controller.blackboard[target_key] + var/seconds_per_tick = attack_seconds_per_tick + var/obj/item/holding_weapon = attack_holding_weapon + var/attack_results = monkey_attack(controller, target, seconds_per_tick, holding_weapon && SPT_PROB(MONKEY_ATTACK_DISARM_PROB, seconds_per_tick), holding_weapon) + if(!async_still_valid()) + return + var/succeeded = FALSE + if(attack_results && !controller.blackboard[BB_MONKEY_AGGRESSIVE]) + succeeded = TRUE + if(prob(MONKEY_HATRED_REDUCTION_PROB)) + var/hatred_value = controller.blackboard[BB_MONKEY_ENEMIES][target] - 1 + if(hatred_value <= 0) + controller.remove_thing_from_blackboard_key(BB_MONKEY_ENEMIES, target) + else + controller.set_blackboard_key_assoc(BB_MONKEY_ENEMIES, target, hatred_value) + finish_async(succeeded ? AI_BEHAVIOR_SUCCEEDED : AI_BEHAVIOR_FAILED) + +/datum/bt_node/ai_behavior/monkey_attack_mob/finish_action(datum/ai_controller/controller, succeeded) + . = ..() + attack_seconds_per_tick = 0 + attack_holding_weapon = null + +/// Attack with held weapon or bite; try to disarm if target is holding something +/datum/bt_node/ai_behavior/monkey_attack_mob/proc/monkey_attack(datum/ai_controller/controller, mob/living/target, seconds_per_tick, disarm, holding_weapon) + var/mob/living/living_pawn = controller.pawn + + if(living_pawn.next_move > world.time) + return FALSE + + living_pawn.face_atom(target) + + var/obj/item/potential_weapon = locate(/obj/item) in living_pawn.held_items + + if(target.IsReachableBy(living_pawn, potential_weapon?.reach)) + if(isnull(potential_weapon)) + controller.ai_interact(target = target, modifiers = disarm ? list(RIGHT_CLICK = TRUE) : null, combat_mode = TRUE) + if(disarm && !isnull(holding_weapon) && controller.blackboard[BB_MONKEY_BLACKLISTITEMS][holding_weapon]) + controller.remove_thing_from_blackboard_key(BB_MONKEY_BLACKLISTITEMS, holding_weapon) + else + if(potential_weapon != living_pawn.get_active_held_item()) + living_pawn.swap_hand(living_pawn.get_inactive_hand_index()) + controller.ai_interact(target = target, combat_mode = TRUE) + controller.override_blackboard_key(BB_MONKEY_GUN_WORKED, TRUE) + return TRUE + + if(!potential_weapon) + return FALSE + + // target out of melee reach — try ranged or throw + var/atom/real_target = target + if(prob(10)) // artificial miss + real_target = pick(oview(2, target)) + + var/obj/item/gun/gun = locate(/obj/item/gun) in living_pawn.held_items + var/can_shoot = gun?.can_shoot() || FALSE + if(gun && controller.blackboard[BB_MONKEY_GUN_WORKED] && prob(95)) + if(gun != living_pawn.get_active_held_item()) + living_pawn.swap_hand(living_pawn.get_inactive_hand_index()) + controller.ai_interact(target = real_target, combat_mode = TRUE) + controller.override_blackboard_key(BB_MONKEY_GUN_WORKED, can_shoot ? TRUE : prob(80)) + if(can_shoot) + controller.override_blackboard_key(BB_MONKEY_GUN_NEURONS_ACTIVATED, TRUE) + else + living_pawn.throw_item(real_target) + controller.override_blackboard_key(BB_MONKEY_GUN_WORKED, TRUE) + return TRUE + +/// Rallies nearby monkeys against the current attack target +/datum/bt_node/ai_behavior/recruit_monkeys + var/target_key + +/datum/bt_node/ai_behavior/recruit_monkeys/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/living_pawn = controller.pawn + var/mob/living/attack_target = controller.blackboard[target_key] + + for(var/mob/living/nearby_monkey in view(living_pawn, MONKEY_ENEMY_VISION)) + if(QDELETED(nearby_monkey) || !HAS_AI_CONTROLLER_TYPE(nearby_monkey, /datum/ai_controller/monkey)) + continue + if(prob(MONKEY_RECRUIT_PROB)) + continue + nearby_monkey.ai_controller.add_blackboard_key_assoc(BB_MONKEY_ENEMIES, attack_target, MONKEY_RECRUIT_HATED_AMOUNT) + + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + + +/// Scans nearby humans for patrons to serve; fails if bartender present or fewer than 1 patron found +/datum/bt_node/ai_behavior/monkey_find_patrons + var/patrons_key + var/give_target_key + +/datum/bt_node/ai_behavior/monkey_find_patrons/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/living_pawn = controller.pawn + var/list/nearby_patrons = list() + + for(var/mob/living/carbon/human/human_mob in oview(5, living_pawn)) + if(istype(human_mob.mind?.assigned_role, /datum/job/bartender)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED // my boss is on duty! + if(human_mob.stat != CONSCIOUS || ismonkey(human_mob)) + continue + if(!human_mob.get_empty_held_indexes()) + continue + nearby_patrons += human_mob + + if(!length(nearby_patrons)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + controller.override_blackboard_key(patrons_key, nearby_patrons) + controller.blackboard[give_target_key] ||= pick(nearby_patrons) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + + +/// Gathers press targets: filtered to BB_MONKEY_PRESS_TYPEPATH's type if set, else any nearby obj. +/datum/target_source/monkey_press_target + +/datum/target_source/monkey_press_target/collect_candidates(mob/living/pawn, datum/ai_controller/controller, range) + var/locate_path = controller.blackboard[BB_MONKEY_PRESS_TYPEPATH] + var/list/candidates = list() + for(var/obj/potential_candidate in oview(range, pawn)) + if(locate_path) + if(istype(potential_candidate, locate_path)) + candidates += potential_candidate + break //found a bell fuck everything else + candidates += potential_candidate + return candidates + +/// Idle wander/emote behavior. Reads emote lists from BB_MONKEY_IDLE_COMMON_EMOTES and BB_MONKEY_IDLE_RARE_EMOTES. +/datum/bt_node/ai_behavior/monkey_idle + +/datum/bt_node/ai_behavior/monkey_idle/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/living_pawn = controller.pawn + if(SPT_PROB(25, seconds_per_tick) && (living_pawn.mobility_flags & MOBILITY_MOVE) && isturf(living_pawn.loc) && !living_pawn.pulledby) + var/move_dir = pick(GLOB.alldirs) + living_pawn.Move(get_step(living_pawn, move_dir), move_dir) + else if(SPT_PROB(5, seconds_per_tick)) + INVOKE_ASYNC(living_pawn, TYPE_PROC_REF(/mob, emote), pick(controller.blackboard[BB_MONKEY_IDLE_COMMON_EMOTES])) + else if(SPT_PROB(1, seconds_per_tick)) + INVOKE_ASYNC(living_pawn, TYPE_PROC_REF(/mob, emote), pick(controller.blackboard[BB_MONKEY_IDLE_RARE_EMOTES])) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED + +///monkey trees + +/datum/bt_node/subtree/monkey_combat + behavior_tree_json = "code/datums/ai/monkey/monkey_combat.bt.json" + +/datum/bt_node/subtree/monkey_find_weapon + behavior_tree_json = "code/datums/ai/monkey/monkey_find_weapon.bt.json" + +/datum/bt_node/subtree/monkey_shenanigans + behavior_tree_json = "code/datums/ai/monkey/monkey_shenanigans.bt.json" + +/datum/bt_node/subtree/monkey_serve_food + behavior_tree_json = "code/datums/ai/monkey/monkey_serve_food.bt.json" diff --git a/code/datums/ai/monkey/monkey_combat.bt.json b/code/datums/ai/monkey/monkey_combat.bt.json new file mode 100644 index 00000000000..697210653e0 --- /dev/null +++ b/code/datums/ai/monkey/monkey_combat.bt.json @@ -0,0 +1,228 @@ +{ + "dm_type": "/datum/bt_node/subtree/monkey_combat", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CURRENT_TARGET", + "observer_abort": "BT_ABORT_LOWER_PRIORITY" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "0.2 SECONDS", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/is_at_distance", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "invert": true, + "target_key": "BB_CURRENT_TARGET", + "maximum_distance": 10, + "require_reach": false + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/set_bb_key", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/pawn_health_below", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "health_threshold": 40 + }, + "child": { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/run_away_from_target" + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/monkey_find_weapon" + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/mob_stat_at_least", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "invert": false, + "key": "BB_CURRENT_TARGET", + "min_stat": "SOFT_CRIT" + }, + "child": { + "type": "selector", + "children": [ + { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_monkey_target_disposal", + "target_source": "/datum/target_source/oview_single_type/disposal_unit", + "targeting_strategy": "/datum/targeting_strategy/anything", + "vision_range": 9 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": true + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/grab_target", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_monkey_target_disposal", + "required_dist": 1, + "finish_on_arrival": true + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/stuff_in_disposal", + "vars": { + "attack_target_key": "BB_CURRENT_TARGET", + "disposal_target_key": "BB_monkey_target_disposal" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/set_bb_key", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/set_bb_key", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + } + ] + } + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/monkey_attack_mob", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + ] + }, + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "loop_delay": "5 SECONDS", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/cooldown", + "vars": { + "cooldown_key": "BB_monkey_recruit_cooldown", + "cooldown_duration": 10 + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/recruit_monkeys", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + } + } + ] + }, + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "loop_delay": "1 SECONDS", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/cooldown", + "vars": { + "cooldown_key": "BB_BATTLE_SCREECH_COOLDOWN", + "cooldown_duration": 5 + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/random_chance", + "vars": { + "chance": 0.25 + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/battle_screech/monkey" + } + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target/monkey_find_weapon", + "vars": { + "time_between_perform": 0, + "target_key": "BB_monkey_pickuptarget", + "targeting_strategy": "/datum/targeting_strategy/monkey_weapon_upgrade", + "target_source": "/datum/target_source/monkey_weapon_upgrade", + "vision_range": 5 + } + } + ] + } +} diff --git a/code/datums/ai/monkey/monkey_controller.dm b/code/datums/ai/monkey/monkey_controller.dm index 18bdb0be26b..ede9341e88d 100644 --- a/code/datums/ai/monkey/monkey_controller.dm +++ b/code/datums/ai/monkey/monkey_controller.dm @@ -7,13 +7,7 @@ have ways of interacting with a specific mob and control it. /datum/ai_controller/monkey ai_movement = /datum/ai_movement/basic_avoidance movement_delay = 0.4 SECONDS - planning_subtrees = list( - /datum/ai_planning_subtree/generic_resist, - /datum/ai_planning_subtree/monkey_combat, - /datum/ai_planning_subtree/generic_hunger, - /datum/ai_planning_subtree/generic_play_instrument, - /datum/ai_planning_subtree/monkey_shenanigans, - ) + behavior_tree_json = "code/datums/ai/monkey/monkey.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/monkey, BB_MONKEY_AGGRESSIVE = FALSE, @@ -23,35 +17,26 @@ have ways of interacting with a specific mob and control it. BB_MONKEY_PICKPOCKETING = FALSE, BB_MONKEY_DISPOSING = FALSE, BB_MONKEY_GUN_NEURONS_ACTIVATED = FALSE, + BB_MONKEY_GUN_WORKED = TRUE, BB_SONG_LINES = MONKEY_SONG, - BB_RESISTING = FALSE, BB_MONKEY_GIVE_CHANCE = 5, + BB_MONKEY_PICKUP_IS_PICKPOCKET = null, + BB_MONKEY_IDLE_COMMON_EMOTES = list("screech", "roar"), + BB_MONKEY_IDLE_RARE_EMOTES = list("scratch", "jump", "roll", "tail"), ) - idle_behavior = /datum/idle_behavior/idle_monkey /datum/targeting_strategy/basic/monkey + custom_faction_check = TRUE /datum/targeting_strategy/basic/monkey/faction_check(datum/ai_controller/controller, mob/living/living_mob, mob/living/the_target) // if they wronged us, all bets are off if(controller.blackboard[BB_MONKEY_ENEMIES][the_target]) return FALSE // target was forcibly set, all bets are off again - if(controller.blackboard[BB_MONKEY_CURRENT_ATTACK_TARGET] == the_target) + if(controller.blackboard[BB_CURRENT_TARGET] == the_target) return FALSE return ..() -/datum/ai_controller/monkey/process(seconds_per_tick) - - var/mob/living/living_pawn = src.pawn - - if(!length(living_pawn.do_afters) && living_pawn.ai_controller.blackboard[BB_RESISTING]) - living_pawn.ai_controller.set_blackboard_key(BB_RESISTING, FALSE) - - if(living_pawn.ai_controller.blackboard[BB_RESISTING]) - return - - . = ..() - /datum/ai_controller/monkey/New(atom/new_pawn) var/static/list/control_examine = list( ORGAN_SLOT_EYES = span_monkey("%PRONOUN_They stare%PRONOUN_s around with wild, primal eyes."), @@ -59,18 +44,6 @@ have ways of interacting with a specific mob and control it. AddElement(/datum/element/ai_control_examine, control_examine) return ..() -/datum/ai_controller/monkey/pun_pun - movement_delay = 0.7 SECONDS //pun pun moves slower so the bartender can keep track of them - planning_subtrees = list( - /datum/ai_planning_subtree/generic_resist, - /datum/ai_planning_subtree/monkey_combat, - /datum/ai_planning_subtree/serve_food, - /datum/ai_planning_subtree/generic_hunger, - /datum/ai_planning_subtree/generic_play_instrument, - /datum/ai_planning_subtree/monkey_shenanigans, - ) - idle_behavior = /datum/idle_behavior/idle_monkey/pun_pun - /datum/ai_controller/monkey/pun_pun/TryPossessPawn(atom/new_pawn) . = ..() if(. & AI_CONTROLLER_INCOMPATIBLE) @@ -80,6 +53,8 @@ have ways of interacting with a specific mob and control it. set_blackboard_key(BB_MONKEY_TAMED, TRUE) set_blackboard_key(BB_MONKEY_GIVE_CHANCE, 30) set_blackboard_key(BB_MONKEY_PRESS_TYPEPATH, /obj/structure/desk_bell) + override_blackboard_key(BB_MONKEY_IDLE_COMMON_EMOTES, list("tunesing", "dance", "bow")) + override_blackboard_key(BB_MONKEY_IDLE_RARE_EMOTES, list("clear", "sign", "tail")) set_trip_mode(mode = FALSE) /datum/ai_controller/monkey/angry @@ -151,49 +126,6 @@ have ways of interacting with a specific mob and control it. var/obj/item/organ/brain/primate/monkeybrain = brain monkeybrain.tripping = mode -///re-used behavior pattern by monkeys for finding a weapon -/datum/ai_controller/monkey/proc/TryFindWeapon() - var/mob/living/living_pawn = pawn - - if(!(locate(/obj/item) in living_pawn.held_items)) - set_blackboard_key(BB_MONKEY_BEST_FORCE_FOUND, 0) - - if(blackboard[BB_MONKEY_GUN_NEURONS_ACTIVATED] && (locate(/obj/item/gun) in living_pawn.held_items)) - // We have a gun, what could we possibly want? - return FALSE - - var/obj/item/weapon - var/list/nearby_items = list() - for(var/obj/item/item in oview(2, living_pawn)) - nearby_items += item - - for(var/obj/item/item in living_pawn.held_items) // If we've got some garbage in out hands that's going to stop us from effectively attacking, we should get rid of it. - if(item.force < 2) - living_pawn.dropItemToGround(item) - - weapon = GetBestWeapon(src, nearby_items, living_pawn.held_items) - - var/pickpocket = FALSE - for(var/mob/living/carbon/human/human in oview(5, living_pawn)) - var/obj/item/held_weapon = GetBestWeapon(src, human.held_items + weapon, living_pawn.held_items) - if(held_weapon == weapon) // It's just the same one, not a held one - continue - pickpocket = TRUE - weapon = held_weapon - - if(!weapon || (weapon in living_pawn.held_items)) - return FALSE - - if(weapon.force < 2) // our bite does 2 damage on average, no point in settling for anything less - return FALSE - - set_blackboard_key(BB_MONKEY_PICKUPTARGET, weapon) - if(pickpocket) - queue_behavior(/datum/ai_behavior/monkey_equip/pickpocket, BB_MONKEY_PICKUPTARGET) - else - queue_behavior(/datum/ai_behavior/monkey_equip/ground, BB_MONKEY_PICKUPTARGET) - return TRUE - ///Reactive events to being hit /datum/ai_controller/monkey/proc/retaliate(mob/living/living_mob) // just to be safe diff --git a/code/datums/ai/monkey/monkey_find_weapon.bt.json b/code/datums/ai/monkey/monkey_find_weapon.bt.json new file mode 100644 index 00000000000..4d8970916fe --- /dev/null +++ b/code/datums/ai/monkey/monkey_find_weapon.bt.json @@ -0,0 +1,50 @@ +{ + "dm_type": "/datum/bt_node/subtree/monkey_find_weapon", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_monkey_pickuptarget" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_monkey_pickuptarget", + "required_dist": 1, + "finish_on_arrival": true, + "movement_type": "/datum/ai_movement/jps" + } + }, + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_monkey_pickup_is_pickpocket" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/monkey_equip/pickpocket", + "vars": { + "target_key": "BB_monkey_pickuptarget" + } + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/monkey_equip/ground", + "vars": { + "target_key": "BB_monkey_pickuptarget" + } + } + ] + } + ] + } +} diff --git a/code/datums/ai/monkey/monkey_serve_food.bt.json b/code/datums/ai/monkey/monkey_serve_food.bt.json new file mode 100644 index 00000000000..4706b73c4be --- /dev/null +++ b/code/datums/ai/monkey/monkey_serve_food.bt.json @@ -0,0 +1,132 @@ +{ + "dm_type": "/datum/bt_node/subtree/monkey_serve_food", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_monkey_tamed" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/cooldown", + "vars": { + "cooldown_key": "BB_MONKEY_PATRON_FIND_COOLDOWN", + "cooldown_duration": "1 SECONDS", + "lock_on_succeed": false + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/monkey_find_patrons", + "vars": { + "patrons_key": "BB_monkey_patrons_nearby", + "give_target_key": "BB_monkey_current_give_target" + } + } + }, + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/is_holding_target", + "vars": { + "key": "BB_monkey_current_served_item" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_monkey_current_give_target", + "required_dist": 1, + "finish_on_arrival": true, + "movement_type": "/datum/ai_movement/jps" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/give", + "vars": { + "target_key": "BB_monkey_current_give_target" + } + } + ] + } + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "1 SECONDS", + "finish_on_primary": true, + "children": [ + { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_monkey_current_served_item", + "targeting_strategy": "/datum/targeting_strategy/pickup_item/food_or_drink/include_drinks", + "target_source": "/datum/target_source/held_items_then_oview", + "vision_range": 2, + "must_be_reachable": true + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "invert": false, + "key": "BB_monkey_current_served_item" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_monkey_current_served_item", + "required_dist": 1, + "finish_on_arrival": true, + "movement_type": "/datum/ai_movement/jps" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/pick_up", + "vars": { + "target_key": "BB_monkey_current_served_item", + "drop_held": true + } + } + ] + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_monkey_current_served_item", + "targeting_strategy": "/datum/targeting_strategy/pickup_item/food_or_drink/include_drinks", + "target_source": "/datum/target_source/held_items_then_oview", + "vision_range": 2, + "must_be_reachable": true + } + } + ] + } + ] + } + ] + } +} diff --git a/code/datums/ai/monkey/monkey_shenanigans.bt.json b/code/datums/ai/monkey/monkey_shenanigans.bt.json new file mode 100644 index 00000000000..b4c7ea92e39 --- /dev/null +++ b/code/datums/ai/monkey/monkey_shenanigans.bt.json @@ -0,0 +1,114 @@ +{ + "dm_type": "/datum/bt_node/subtree/monkey_shenanigans", + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/target_is_holding_item", + "vars": { + "key": "BB_MY_PAWN" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/random_chance", + "vars": { + "chance": 0.05 + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/use_in_hand" + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_monkey_current_press_target" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_monkey_current_press_target", + "required_dist": 1, + "finish_on_arrival": true, + "movement_type": "/datum/ai_movement/jps" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/use_on_object", + "vars": { + "target_key": "BB_monkey_current_press_target" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/set_bb_key", + "vars": { + "target_key": "BB_monkey_current_press_target" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/clear_key", + "vars": { + "key": "BB_MONKEY_WANNA_PRESS_SOME_SHIT" + } + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_monkey_current_give_target" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/random_chance_from_key", + "vars": { + "chance_key": "BB_monkey_give_chance" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_monkey_current_give_target", + "required_dist": 1, + "finish_on_arrival": true, + "movement_type": "/datum/ai_movement/jps" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/give", + "vars": { + "target_key": "BB_monkey_current_give_target" + } + } + ] + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_monkey_tamed" + }, + "child": { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/monkey_find_weapon" + } + } + ] +} diff --git a/code/datums/ai/monkey/monkey_subtrees.dm b/code/datums/ai/monkey/monkey_subtrees.dm index dc8793ffdfe..8b137891791 100644 --- a/code/datums/ai/monkey/monkey_subtrees.dm +++ b/code/datums/ai/monkey/monkey_subtrees.dm @@ -1,112 +1 @@ -/datum/ai_planning_subtree/monkey_shenanigans/SelectBehaviors(datum/ai_controller/monkey/controller, seconds_per_tick) - if(prob(5)) - controller.queue_behavior(/datum/ai_behavior/use_in_hand) - - if(!SPT_PROB(MONKEY_SHENANIGAN_PROB, seconds_per_tick)) - return - - if(!controller.blackboard[BB_MONKEY_CURRENT_PRESS_TARGET]) - if(controller.blackboard[BB_MONKEY_PRESS_TYPEPATH]) - controller.queue_behavior(/datum/ai_behavior/find_and_set, BB_MONKEY_CURRENT_PRESS_TARGET, controller.blackboard[BB_MONKEY_PRESS_TYPEPATH], 2) - else - controller.queue_behavior(/datum/ai_behavior/find_nearby, BB_MONKEY_CURRENT_PRESS_TARGET) - else if(prob(50)) - controller.queue_behavior(/datum/ai_behavior/use_on_object, BB_MONKEY_CURRENT_PRESS_TARGET) - return SUBTREE_RETURN_FINISH_PLANNING - - if(!controller.blackboard[BB_MONKEY_CURRENT_GIVE_TARGET]) - controller.queue_behavior(/datum/ai_behavior/find_and_set/pawn_must_hold_item, BB_MONKEY_CURRENT_GIVE_TARGET, /mob/living/carbon/human, 2) - else if(prob(controller.blackboard[BB_MONKEY_GIVE_CHANCE])) - controller.queue_behavior(/datum/ai_behavior/give, BB_MONKEY_CURRENT_GIVE_TARGET) - return SUBTREE_RETURN_FINISH_PLANNING - - if(!controller.blackboard[BB_MONKEY_TAMED]) - controller.TryFindWeapon() - -///monkey combat subtree. -/datum/ai_planning_subtree/monkey_combat/SelectBehaviors(datum/ai_controller/monkey/controller, seconds_per_tick) - var/mob/living/living_pawn = controller.pawn - var/list/enemies = controller.blackboard[BB_MONKEY_ENEMIES] - - if((HAS_TRAIT(controller.pawn, TRAIT_PACIFISM)) || (!length(enemies) && !controller.blackboard[BB_MONKEY_AGGRESSIVE])) //Pacifist, or we have no enemies and we're not pissed - living_pawn.set_combat_mode(FALSE) - return - - if(!controller.blackboard[BB_MONKEY_CURRENT_ATTACK_TARGET]) - controller.queue_behavior(/datum/ai_behavior/monkey_set_combat_target, BB_MONKEY_CURRENT_ATTACK_TARGET, BB_MONKEY_ENEMIES) - living_pawn.set_combat_mode(FALSE) - return SUBTREE_RETURN_FINISH_PLANNING - - var/mob/living/selected_enemy = controller.blackboard[BB_MONKEY_CURRENT_ATTACK_TARGET] - - if(QDELETED(selected_enemy)) - living_pawn.set_combat_mode(FALSE) - return - - if(!selected_enemy.stat) //He's up, get him! - if(living_pawn.health < MONKEY_FLEE_HEALTH) //Time to skeddadle - controller.queue_behavior(/datum/ai_behavior/monkey_flee) - return SUBTREE_RETURN_FINISH_PLANNING //I'm running fuck you guys - - if(controller.TryFindWeapon()) //Getting a weapon is higher priority if im not fleeing. - return SUBTREE_RETURN_FINISH_PLANNING - - if(controller.blackboard[BB_MONKEY_RECRUIT_COOLDOWN] < world.time) - controller.queue_behavior(/datum/ai_behavior/recruit_monkeys, BB_MONKEY_CURRENT_ATTACK_TARGET) - return - - if(SPT_PROB(ismonkey(living_pawn) ? 25 : 10, seconds_per_tick)) - controller.queue_behavior(/datum/ai_behavior/battle_screech/monkey) - controller.queue_behavior(/datum/ai_behavior/monkey_attack_mob, BB_MONKEY_CURRENT_ATTACK_TARGET) - return SUBTREE_RETURN_FINISH_PLANNING - - //by this point we have a target but they're down, let's try dumpstering this loser - - living_pawn.set_combat_mode(FALSE) - - if(!controller.blackboard[BB_MONKEY_TARGET_DISPOSAL]) - controller.queue_behavior(/datum/ai_behavior/find_and_set, BB_MONKEY_TARGET_DISPOSAL, /obj/machinery/disposal, MONKEY_ENEMY_VISION) - return - - controller.queue_behavior(/datum/ai_behavior/disposal_mob, BB_MONKEY_CURRENT_ATTACK_TARGET, BB_MONKEY_TARGET_DISPOSAL) - return SUBTREE_RETURN_FINISH_PLANNING - -/// Finds food or drinks, picks them up, then gives them to nearby humans -/datum/ai_planning_subtree/serve_food - -/datum/ai_planning_subtree/serve_food/SelectBehaviors(datum/ai_controller/monkey/controller, seconds_per_tick) - var/mob/living/living_pawn = controller.pawn - var/list/nearby_patrons = list() - for(var/mob/living/carbon/human/human_mob in oview(5, living_pawn)) - if(istype(human_mob.mind?.assigned_role, /datum/job/bartender)) - return // my boss is on duty! - if(human_mob.stat != CONSCIOUS || ismonkey(human_mob)) - continue - if(!human_mob.get_empty_held_indexes()) - continue - nearby_patrons += human_mob - - // Need at least 2 patrons to bother serving (bearing in mind the - if(length(nearby_patrons) < 1) - return - - var/obj/item/serving = controller.blackboard[BB_MONKEY_CURRENT_SERVED_ITEM] - if(QDELETED(serving) || serving.reagents.total_volume <= 0) - controller.queue_behavior(/datum/ai_behavior/find_and_set/food_or_drink/to_serve, BB_MONKEY_CURRENT_SERVED_ITEM, /obj/item, 2) - return - - // we have something to serve, pick a patron and go hand it over - if(living_pawn.is_holding(serving)) - controller.blackboard[BB_MONKEY_CURRENT_GIVE_TARGET] ||= pick(nearby_patrons) - controller.queue_behavior(/datum/ai_behavior/give, BB_MONKEY_CURRENT_GIVE_TARGET) - return SUBTREE_RETURN_FINISH_PLANNING - - // we have something to serve but aren't holding it yet - if(isturf(serving.loc)) - // fetch the drink - controller.queue_behavior(/datum/ai_behavior/navigate_to_and_pick_up, BB_MONKEY_CURRENT_SERVED_ITEM, TRUE) - else - // give up on the dream - controller.clear_blackboard_key(BB_MONKEY_CURRENT_SERVED_ITEM) - return SUBTREE_RETURN_FINISH_PLANNING diff --git a/code/datums/ai/movement/_ai_movement.dm b/code/datums/ai/movement/_ai_movement.dm index 7c4df64e6e6..dcf3947e859 100644 --- a/code/datums/ai/movement/_ai_movement.dm +++ b/code/datums/ai/movement/_ai_movement.dm @@ -6,24 +6,42 @@ var/max_pathing_attempts //Override this to setup the moveloop you want to use -/datum/ai_movement/proc/start_moving_towards(datum/ai_controller/controller, atom/current_movement_target, min_distance) +/datum/ai_movement/proc/start_moving_towards(datum/ai_controller/controller, atom/current_movement_target, min_distance, delay_override) SHOULD_CALL_PARENT(TRUE) + var/old_movement_target = moving_controllers[controller] + if(old_movement_target) + if(old_movement_target == current_movement_target) + return FALSE + else + update_movement_target(controller, current_movement_target) + return FALSE controller.consecutive_pathing_attempts = 0 controller.set_blackboard_key(BB_CURRENT_MIN_MOVE_DISTANCE, min_distance) moving_controllers[controller] = current_movement_target SEND_SIGNAL(controller.pawn, COMSIG_MOB_AI_MOVEMENT_STARTED, current_movement_target) + controller.set_blackboard_key(BB_CURRENT_MOVEMENT_TARGET, current_movement_target) + return TRUE + +/datum/ai_movement/proc/update_movement_target(datum/ai_controller/controller, atom/new_target) + moving_controllers[controller] = new_target + controller.set_blackboard_key(BB_CURRENT_MOVEMENT_TARGET, new_target) /datum/ai_movement/proc/stop_moving_towards(datum/ai_controller/controller) controller.consecutive_pathing_attempts = 0 moving_controllers -= controller // We got deleted as we finished an action + controller.clear_blackboard_key(BB_CURRENT_MOVEMENT_TARGET) if(!QDELETED(controller.pawn)) GLOB.move_manager.stop_looping(controller.pawn, SSai_movement) /datum/ai_movement/proc/increment_pathing_failures(datum/ai_controller/controller) controller.consecutive_pathing_attempts++ if(controller.consecutive_pathing_attempts >= max_pathing_attempts) - controller.CancelActions() + fail_movement(controller) + +/datum/ai_movement/proc/fail_movement(datum/ai_controller/controller) + stop_moving_towards(controller) + SEND_SIGNAL(controller.pawn, COMSIG_MOB_AI_MOVEMENT_FAILED, moving_controllers[controller]) /datum/ai_movement/proc/reset_pathing_failures(datum/ai_controller/controller) controller.consecutive_pathing_attempts = 0 @@ -67,7 +85,6 @@ // Check if this controller can actually run, so we don't chase people with corpses if(!controller.able_to_run) - controller.CancelActions() qdel(source) //stop moving return MOVELOOP_SKIP_STEP diff --git a/code/datums/ai/movement/ai_movement_basic_avoidance.dm b/code/datums/ai/movement/ai_movement_basic_avoidance.dm index 169301f7047..37cbfb9ebb6 100644 --- a/code/datums/ai/movement/ai_movement_basic_avoidance.dm +++ b/code/datums/ai/movement/ai_movement_basic_avoidance.dm @@ -4,15 +4,24 @@ /// Movement flags to pass to the loop var/move_flags = NONE -/datum/ai_movement/basic_avoidance/start_moving_towards(datum/ai_controller/controller, atom/current_movement_target, min_distance) +/datum/ai_movement/basic_avoidance/start_moving_towards(datum/ai_controller/controller, atom/current_movement_target, min_distance, delay_override) . = ..() + if(!.) + return FALSE var/atom/movable/moving = controller.pawn var/min_dist = controller.blackboard[BB_CURRENT_MIN_MOVE_DISTANCE] - var/delay = controller.movement_delay + var/delay = delay_override || controller.movement_delay var/datum/move_loop/loop = GLOB.move_manager.move_to(moving, current_movement_target, min_dist, delay, flags = move_flags, subsystem = SSai_movement, extra_info = controller) RegisterSignal(loop, COMSIG_MOVELOOP_PREPROCESS_CHECK, PROC_REF(pre_move)) RegisterSignal(loop, COMSIG_MOVELOOP_POSTPROCESS, PROC_REF(post_move)) +/datum/ai_movement/basic_avoidance/update_movement_target(datum/ai_controller/controller, atom/new_target) + . = ..() + + var/datum/move_loop/has_target/loop = GLOB.move_manager.processing_on(controller.pawn, SSai_movement) + if(loop) + loop.target = new_target + /datum/ai_movement/basic_avoidance/allowed_to_move(datum/move_loop/has_target/dist_bound/source) var/turf/target_turf = get_step_towards(source.moving, source.target) if(!target_turf?.can_cross_safely(source.moving)) @@ -21,4 +30,4 @@ /// Move immediately and don't update our facing /datum/ai_movement/basic_avoidance/backstep - move_flags = MOVEMENT_LOOP_START_INSTANT | MOVEMENT_LOOP_NO_DIR_UPDATE + move_flags = MOVEMENT_LOOP_NO_DIR_UPDATE diff --git a/code/datums/ai/movement/ai_movement_complete_stop.dm b/code/datums/ai/movement/ai_movement_complete_stop.dm index e9609a30dda..17787b86d95 100644 --- a/code/datums/ai/movement/ai_movement_complete_stop.dm +++ b/code/datums/ai/movement/ai_movement_complete_stop.dm @@ -2,8 +2,10 @@ /datum/ai_movement/complete_stop max_pathing_attempts = INFINITY // path all you want, you can not escape your fate -/datum/ai_movement/complete_stop/start_moving_towards(datum/ai_controller/controller, atom/current_movement_target, min_distance) +/datum/ai_movement/complete_stop/start_moving_towards(datum/ai_controller/controller, atom/current_movement_target, min_distance, delay_override) . = ..() + if(!.) + return FALSE var/atom/movable/moving = controller.pawn var/stopping_time = controller.blackboard[BB_STATIONARY_SECONDS] var/delay_time = (stopping_time * 0.5) // no real reason to fire any more often than this really @@ -11,5 +13,6 @@ var/datum/move_loop/loop = GLOB.move_manager.freeze(moving, current_movement_target, delay = delay_time, timeout = stopping_time, subsystem = SSai_movement, extra_info = controller) RegisterSignal(loop, COMSIG_MOVELOOP_PREPROCESS_CHECK, PROC_REF(pre_move)) + /datum/ai_movement/complete_stop/allowed_to_move(datum/move_loop/source) return FALSE diff --git a/code/datums/ai/movement/ai_movement_dumb.dm b/code/datums/ai/movement/ai_movement_dumb.dm index 001501bcbd9..f5ed83e2d88 100644 --- a/code/datums/ai/movement/ai_movement_dumb.dm +++ b/code/datums/ai/movement/ai_movement_dumb.dm @@ -3,14 +3,23 @@ max_pathing_attempts = 16 ///Put your movement behavior in here! -/datum/ai_movement/dumb/start_moving_towards(datum/ai_controller/controller, atom/current_movement_target, min_distance) +/datum/ai_movement/dumb/start_moving_towards(datum/ai_controller/controller, atom/current_movement_target, min_distance, delay_override) . = ..() + if(.) + return FALSE var/atom/movable/moving = controller.pawn - var/delay = controller.movement_delay + var/delay = delay_override || controller.movement_delay var/datum/move_loop/loop = GLOB.move_manager.move_towards_legacy(moving, current_movement_target, delay, subsystem = SSai_movement, extra_info = controller) RegisterSignal(loop, COMSIG_MOVELOOP_PREPROCESS_CHECK, PROC_REF(pre_move)) RegisterSignal(loop, COMSIG_MOVELOOP_POSTPROCESS, PROC_REF(post_move)) + +/datum/ai_movement/dumb/update_movement_target(datum/ai_controller/controller, atom/new_target) + . = ..() + var/datum/move_loop/has_target/loop = GLOB.move_manager.processing_on(controller.pawn, SSai_movement) + if(loop) + loop.target = new_target + /datum/ai_movement/dumb/allowed_to_move(datum/move_loop/has_target/source) var/turf/target_turf = get_step_towards(source.moving, source.target) if(!target_turf?.can_cross_safely(source.moving)) diff --git a/code/datums/ai/movement/ai_movement_jps.dm b/code/datums/ai/movement/ai_movement_jps.dm index a8980d15b85..3e7e4b6c725 100644 --- a/code/datums/ai/movement/ai_movement_jps.dm +++ b/code/datums/ai/movement/ai_movement_jps.dm @@ -7,12 +7,14 @@ ///how we deal with diagonal movement, whether we try to avoid them or follow through with them var/diagonal_flags = DIAGONAL_REMOVE_CLUNKY -/datum/ai_movement/jps/start_moving_towards(datum/ai_controller/controller, atom/current_movement_target, min_distance) +/datum/ai_movement/jps/start_moving_towards(datum/ai_controller/controller, atom/current_movement_target, min_distance, delay_override) . = ..() + if(!.) + return FALSE var/atom/movable/moving = controller.pawn - var/delay = controller.movement_delay + var/delay = delay_override || controller.movement_delay - var/datum/move_loop/loop = setup_moveloop(controller, current_movement_target, moving, delay) + var/datum/move_loop/loop = setup_moveloop(controller, current_movement_target, moving, delay, min_distance) RegisterSignal(loop, COMSIG_MOVELOOP_PREPROCESS_CHECK, PROC_REF(pre_move)) RegisterSignal(loop, COMSIG_MOVELOOP_POSTPROCESS, PROC_REF(post_move)) @@ -21,14 +23,14 @@ return loop -/datum/ai_movement/jps/proc/setup_moveloop(datum/ai_controller/controller, atom/current_movement_target, atom/movable/moving, delay) +/datum/ai_movement/jps/proc/setup_moveloop(datum/ai_controller/controller, atom/current_movement_target, atom/movable/moving, delay, min_distance) var/datum/move_loop/has_target/jps/loop = GLOB.move_manager.jps_move(moving, current_movement_target, delay, repath_delay = 0.5 SECONDS, simulated_only = !HAS_TRAIT(controller.pawn, TRAIT_SPACEWALK), max_path_length = maximum_length, - minimum_distance = controller.get_minimum_distance(), + minimum_distance = min_distance, access = controller.get_access(), subsystem = SSai_movement, diagonal_handling = diagonal_flags, @@ -36,19 +38,25 @@ ) return loop +/datum/ai_movement/jps/update_movement_target(datum/ai_controller/controller, atom/new_target) + . = ..() + var/datum/move_loop/has_target/jps/loop = GLOB.move_manager.processing_on(controller.pawn, SSai_movement) + if(loop) + INVOKE_ASYNC(loop, TYPE_PROC_REF(/datum/move_loop/has_target/jps, recalculate_path)) + /datum/ai_movement/jps/proc/repath_incoming(datum/move_loop/has_target/jps/source) SIGNAL_HANDLER var/datum/ai_controller/controller = source.extra_info source.access = controller.get_access() - source.minimum_distance = controller.get_minimum_distance() + // minimum_distance was set at loop creation; no need to update it on repath /datum/ai_movement/jps/bot max_pathing_attempts = 8 maximum_length = 25 diagonal_flags = DIAGONAL_REMOVE_ALL -/datum/ai_movement/jps/bot/start_moving_towards(datum/ai_controller/controller, atom/current_movement_target, min_distance) +/datum/ai_movement/jps/bot/start_moving_towards(datum/ai_controller/controller, atom/current_movement_target, min_distance, delay_override) var/datum/move_loop/loop = ..() var/atom/our_pawn = controller.pawn if(isnull(our_pawn)) @@ -65,14 +73,14 @@ max_pathing_attempts = 10 maximum_length = AI_MULEBOT_PATH_LENGTH -/datum/ai_movement/jps/bot/mulebot/setup_moveloop(datum/ai_controller/controller, atom/current_movement_target, atom/movable/moving, delay) +/datum/ai_movement/jps/bot/mulebot/setup_moveloop(datum/ai_controller/controller, atom/current_movement_target, atom/movable/moving, delay, min_distance) var/datum/move_loop/has_target/jps/frustrations/loop = GLOB.move_manager.frustrations_move(moving, current_movement_target, delay, repath_delay = 0.5 SECONDS, simulated_only = !HAS_TRAIT(controller.pawn, TRAIT_SPACEWALK), max_path_length = maximum_length, - minimum_distance = controller.get_minimum_distance(), + minimum_distance = min_distance, access = controller.get_access(), subsystem = SSai_movement, diagonal_handling = diagonal_flags, diff --git a/code/datums/ai/objects/vending_machines/vending_machine.bt.json b/code/datums/ai/objects/vending_machines/vending_machine.bt.json new file mode 100644 index 00000000000..725e206a584 --- /dev/null +++ b/code/datums/ai/objects/vending_machines/vending_machine.bt.json @@ -0,0 +1,64 @@ +{ + "dm_type": "/datum/ai_controller/vending_machine", + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/vending_is_tilted", + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/key_off_cooldown", + "vars": { + "cooldown_key": "BB_VENDING_UNTILT_COOLDOWN" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/vendor_rise_up" + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/vending_is_tilted", + "vars": { + "invert": true + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/key_off_cooldown", + "vars": { + "cooldown_key": "BB_VENDING_TILT_COOLDOWN" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/find_vendor_target", + "vars": { + "target_key": "BB_VENDING_CURRENT_TARGET", + "vision_range": 7 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_VENDING_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": true + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/vendor_crush", + "vars": { + "target_key": "BB_VENDING_CURRENT_TARGET" + } + } + ] + } + } + } + ] +} diff --git a/code/datums/ai/objects/vending_machines/vending_machine_behaviors.dm b/code/datums/ai/objects/vending_machines/vending_machine_behaviors.dm index 1aa936e124d..6bfc8ec9fc4 100644 --- a/code/datums/ai/objects/vending_machines/vending_machine_behaviors.dm +++ b/code/datums/ai/objects/vending_machines/vending_machine_behaviors.dm @@ -1,28 +1,51 @@ -/datum/ai_behavior/vendor_crush - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT - ///Time before machine can untilt itself after tilting - var/untilt_cooldown = 1 SECONDS - ///Time to telegraph and tilt over +/// Returns TRUE if the vending machine pawn is currently tilted. +/datum/bt_node/decorator/vending_is_tilted/check_condition(datum/ai_controller/controller) + var/obj/machinery/vending/vendor_pawn = controller.pawn + return vendor_pawn.tilted + +/// Searches nearby tiles for a valid living target and sets the given BB key. Sets tilt cooldown and fails if none found. +/datum/bt_node/ai_behavior/find_vendor_target + var/target_key + var/vision_range + /// Cooldown applied to BB_VENDING_TILT_COOLDOWN when no valid target is in range + var/search_cooldown = 2 SECONDS + +/datum/bt_node/ai_behavior/find_vendor_target/perform(seconds_per_tick, datum/ai_controller/controller) + for(var/mob/living/living_target in oview(vision_range, controller.pawn)) + if(living_target.stat || living_target.incorporeal_move) + continue + controller.set_blackboard_key(target_key, living_target) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + controller.clear_blackboard_key(target_key) + controller.set_blackboard_key(BB_VENDING_TILT_COOLDOWN, world.time + search_cooldown) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + +/// Telegraphs and tilts onto the target. Returns success once the machine is tilted. +/datum/bt_node/ai_behavior/vendor_crush + var/target_key + /// Time to telegraph before tilting var/time_to_tilt = 0.8 SECONDS + /// Time before machine can untilt after a tilt attempt + var/untilt_cooldown = 1 SECONDS -/datum/ai_behavior/vendor_crush/setup(datum/ai_controller/controller, target_key) - . = ..() - set_movement_target(controller, controller.blackboard[target_key]) - -/datum/ai_behavior/vendor_crush/perform(seconds_per_tick, datum/ai_controller/controller) +/datum/bt_node/ai_behavior/vendor_crush/perform(seconds_per_tick, datum/ai_controller/controller) + var/obj/machinery/vending/vendor_pawn = controller.pawn + if(vendor_pawn.tilted) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED if(controller.blackboard[BB_VENDING_BUSY_TILTING]) return AI_BEHAVIOR_DELAY - controller.ai_movement.stop_moving_towards(controller) controller.set_blackboard_key(BB_VENDING_BUSY_TILTING, TRUE) - var/turf/target_turf = get_turf(controller.blackboard[BB_VENDING_CURRENT_TARGET]) + var/turf/target_turf = get_turf(controller.blackboard[target_key]) new /obj/effect/temp_visual/telegraphing/vending_machine_tilt(target_turf) addtimer(CALLBACK(src, PROC_REF(tiltonmob), controller, target_turf), time_to_tilt) return AI_BEHAVIOR_DELAY -/datum/ai_behavior/vendor_crush/proc/tiltonmob(datum/ai_controller/controller, turf/target_turf) +/datum/bt_node/ai_behavior/vendor_crush/proc/tiltonmob(datum/ai_controller/controller, turf/target_turf) + if(QDELETED(controller) || QDELETED(controller.pawn)) + return var/obj/machinery/vending/vendor_pawn = controller.pawn - if(vendor_pawn.tilt(target_turf, 0) & SUCCESSFULLY_CRUSHED_MOB) //We hit something + if(vendor_pawn.tilt(target_turf, 0) & SUCCESSFULLY_CRUSHED_MOB) vendor_pawn.say(pick("Supersize this!", "Eat my shiny metal ass!", "Want to consume some of my products?", "SMASH!", "Don't you love these smashing prices!")) controller.set_blackboard_key(BB_VENDING_LAST_HIT_SUCCESSFUL, TRUE) else @@ -30,21 +53,22 @@ flick(vendor_pawn.icon_deny, vendor_pawn) vendor_pawn.say(pick("Get back here!", "Don't you want my well priced love?")) controller.set_blackboard_key(BB_VENDING_LAST_HIT_SUCCESSFUL, FALSE) - finish_action(controller, TRUE) + controller.set_blackboard_key(BB_VENDING_UNTILT_COOLDOWN, world.time + untilt_cooldown) + controller.set_blackboard_key(BB_VENDING_BUSY_TILTING, FALSE) -/datum/ai_behavior/vendor_crush/finish_action(datum/ai_controller/controller, succeeded) +/datum/bt_node/ai_behavior/vendor_crush/finish_action(datum/ai_controller/controller, succeeded) . = ..() controller.set_blackboard_key(BB_VENDING_BUSY_TILTING, FALSE) - controller.set_blackboard_key(BB_VENDING_UNTILT_COOLDOWN, world.time + untilt_cooldown) -/datum/ai_behavior/vendor_rise_up //what a gamer - ///Time before machine can tilt again after untilting if last hit was a success - var/succes_tilt_cooldown = 5 SECONDS +/// Untilts the machine. Sets a tilt cooldown if the previous hit was successful. +/datum/bt_node/ai_behavior/vendor_rise_up + /// Time before machine can tilt again after untilting if the last hit landed + var/success_tilt_cooldown = 5 SECONDS -/datum/ai_behavior/vendor_rise_up/perform(seconds_per_tick, datum/ai_controller/controller) +/datum/bt_node/ai_behavior/vendor_rise_up/perform(seconds_per_tick, datum/ai_controller/controller) var/obj/machinery/vending/vendor_pawn = controller.pawn vendor_pawn.visible_message(span_warning("[vendor_pawn] untilts itself!")) if(controller.blackboard[BB_VENDING_LAST_HIT_SUCCESSFUL]) - controller.set_blackboard_key(BB_VENDING_TILT_COOLDOWN, world.time + succes_tilt_cooldown) + controller.set_blackboard_key(BB_VENDING_TILT_COOLDOWN, world.time + success_tilt_cooldown) vendor_pawn.untilt() return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED diff --git a/code/datums/ai/objects/vending_machines/vending_machine_controller.dm b/code/datums/ai/objects/vending_machines/vending_machine_controller.dm index ab888040fb5..5e73a9d7460 100644 --- a/code/datums/ai/objects/vending_machines/vending_machine_controller.dm +++ b/code/datums/ai/objects/vending_machines/vending_machine_controller.dm @@ -1,6 +1,7 @@ ///AI controller for vending machine gone rogue, Don't try using this on anything else, it wont work. /datum/ai_controller/vending_machine movement_delay = 0.4 SECONDS + behavior_tree_json = "code/datums/ai/objects/vending_machines/vending_machine.bt.json" blackboard = list( BB_VENDING_CURRENT_TARGET = null, BB_VENDING_TILT_COOLDOWN = 0, @@ -10,10 +11,6 @@ ) /// If TRUE stops mobs from buying things from active machines var/block_usage = FALSE - /// Range to search for mobs to crunch - var/vision_range = 7 - /// Seconds between attempts to find a new mob to crunch - var/search_for_enemy_cooldown = 2 SECONDS /datum/ai_controller/vending_machine/TryPossessPawn(atom/new_pawn) if(!istype(new_pawn, /obj/machinery/vending)) @@ -35,26 +32,6 @@ UnregisterSignal(vendor_pawn, COMSIG_VENDING_UI_INTERACT) return ..() //Run parent at end -/datum/ai_controller/vending_machine/SelectBehaviors(seconds_per_tick) - current_behaviors = list() - var/obj/machinery/vending/vendor_pawn = pawn - - if(vendor_pawn.tilted) //We're tilted, try to untilt - if(blackboard[BB_VENDING_UNTILT_COOLDOWN] > world.time) - return - queue_behavior(/datum/ai_behavior/vendor_rise_up) - return - else //Not tilted, try to find target to tilt onto. - if(blackboard[BB_VENDING_TILT_COOLDOWN] > world.time) - return - for(var/mob/living/living_target in oview(vision_range, pawn)) - if(living_target.stat || living_target.incorporeal_move) //They're already fucked up or incorporeal - continue - set_blackboard_key(BB_VENDING_CURRENT_TARGET, living_target) - queue_behavior(/datum/ai_behavior/vendor_crush, BB_VENDING_CURRENT_TARGET) - return - set_blackboard_key(BB_VENDING_TILT_COOLDOWN, world.time + search_for_enemy_cooldown) - /datum/ai_controller/vending_machine/proc/deny_vending_interact(obj/machinery/vending/vending_machine, mob/user, datum/tgui/ui) SIGNAL_HANDLER if(!block_usage) diff --git a/code/datums/ai/robot_customer/robot_customer.bt.json b/code/datums/ai/robot_customer/robot_customer.bt.json new file mode 100644 index 00000000000..e055a409302 --- /dev/null +++ b/code/datums/ai/robot_customer/robot_customer.bt.json @@ -0,0 +1,125 @@ +{ + "dm_type": "/datum/ai_controller/robot_customer", + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CUSTOMER_LEAVING", + "observer_abort": "BT_ABORT_LOWER_PRIORITY" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/robot_customer/find_exit_portal" + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CUSTOMER_EXIT_PORTAL", + "required_dist": 0 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/robot_customer/leave_venue" + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CUSTOMER_CURRENT_TARGET", + "observer_abort": "BT_ABORT_LOWER_PRIORITY" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CUSTOMER_CURRENT_TARGET", + "required_dist": 1 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/break_spine", + "vars": { + "target_key": "BB_CUSTOMER_CURRENT_TARGET" + } + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CUSTOMER_MY_SEAT", + "invert": true + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/cooldown", + "vars": { + "cooldown_key": "BB_CUSTOMER_FIND_SEAT_COOLDOWN", + "cooldown_duration": "8 SECONDS", + "lock_on_succeed": false + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/robot_customer/find_seat" + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CUSTOMER_MY_SEAT" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CUSTOMER_MY_SEAT", + "required_dist": 0 + } + }, + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CUSTOMER_CURRENT_ORDER", + "invert": true + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/robot_customer/order_food" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/robot_customer/wait_for_food" + } + ] + } + ] + } + } + ] +} diff --git a/code/datums/ai/robot_customer/robot_customer_behaviors.dm b/code/datums/ai/robot_customer/robot_customer_behaviors.dm index 42c2188bc4c..45b087a58e0 100644 --- a/code/datums/ai/robot_customer/robot_customer_behaviors.dm +++ b/code/datums/ai/robot_customer/robot_customer_behaviors.dm @@ -1,86 +1,94 @@ -/datum/ai_behavior/find_seat - action_cooldown = 8 SECONDS +/// Searches nearby for an unclaimed seat belonging to the customer's venue. +/// Sets BB_CUSTOMER_MY_SEAT and claims it on success. Returns FAILURE if none found. +/datum/bt_node/ai_behavior/robot_customer/find_seat -/datum/ai_behavior/find_seat/perform(seconds_per_tick, datum/ai_controller/controller) +/datum/bt_node/ai_behavior/robot_customer/find_seat/perform(seconds_per_tick, datum/ai_controller/controller) var/mob/living/basic/robot_customer/customer_pawn = controller.pawn var/datum/customer_data/customer_data = controller.blackboard[BB_CUSTOMER_CUSTOMERINFO] var/datum/venue/attending_venue = controller.blackboard[BB_CUSTOMER_ATTENDING_VENUE] var/obj/structure/holosign/robot_seat/found_seat - - for(var/obj/structure/holosign/robot_seat/potential_seat in oview(7, controller.pawn)) - - if(potential_seat.linked_venue != attending_venue) //Incorrect venue + for(var/obj/structure/holosign/robot_seat/potential_seat in oview(7, customer_pawn)) + if(potential_seat.linked_venue != attending_venue) continue - - if(attending_venue.linked_seats[potential_seat]) //Someone called dibs + if(attending_venue.linked_seats[potential_seat]) continue var/turf/seat_turf = get_turf(potential_seat) - - if(seat_turf.is_blocked_turf()) //Someone called dibsies + if(seat_turf.is_blocked_turf()) continue - found_seat = potential_seat break if(found_seat) - customer_pawn.say(pick(customer_data.found_seat_lines)) + INVOKE_ASYNC(customer_pawn, TYPE_PROC_REF(/atom/movable, say), pick(customer_data.found_seat_lines)) controller.set_blackboard_key(BB_CUSTOMER_MY_SEAT, found_seat) attending_venue.linked_seats[found_seat] = customer_pawn - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED - // SPT_PROB 1.5 is about a 60% chance that the tourist will have vocalised at least once every minute. if(!controller.blackboard[BB_CUSTOMER_SAID_CANT_FIND_SEAT_LINE] || SPT_PROB(1.5, seconds_per_tick)) - customer_pawn.say(pick(customer_data.cant_find_seat_lines)) + INVOKE_ASYNC(customer_pawn, TYPE_PROC_REF(/atom/movable, say), pick(customer_data.cant_find_seat_lines)) controller.set_blackboard_key(BB_CUSTOMER_SAID_CANT_FIND_SEAT_LINE, TRUE) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED -/datum/ai_behavior/order_food - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT - required_distance = 0 -/datum/ai_behavior/order_food/perform(seconds_per_tick, datum/ai_controller/controller) +/// Places the customer's food order once they are at their seat. +/datum/bt_node/ai_behavior/robot_customer/order_food + +/datum/bt_node/ai_behavior/robot_customer/order_food/perform(seconds_per_tick, datum/ai_controller/controller) + var/async_flags = handle_async() + if(async_flags) + return async_flags + var/mob/living/basic/robot_customer/customer_pawn = controller.pawn - var/datum/customer_data/customer_data = controller.blackboard[BB_CUSTOMER_CUSTOMERINFO] var/obj/structure/holosign/robot_seat/seat_marker = controller.blackboard[BB_CUSTOMER_MY_SEAT] + if(get_turf(seat_marker) == get_turf(customer_pawn)) var/obj/structure/chair/my_seat = locate(/obj/structure/chair) in get_turf(customer_pawn) if(my_seat) - controller.pawn.setDir(my_seat.dir) //Sit in your seat + customer_pawn.setDir(my_seat.dir) + return start_async() + +/datum/bt_node/ai_behavior/robot_customer/order_food/perform_async(datum/ai_controller/controller) + var/mob/living/basic/robot_customer/customer_pawn = controller.pawn + var/datum/customer_data/customer_data = controller.blackboard[BB_CUSTOMER_CUSTOMERINFO] var/datum/venue/attending_venue = controller.blackboard[BB_CUSTOMER_ATTENDING_VENUE] + var/order + if(!QDELETED(customer_pawn) && !QDELETED(attending_venue)) + order = attending_venue.order_food(customer_pawn, customer_data) + if(!async_still_valid()) + return + if(!isnull(order)) + controller.set_blackboard_key(BB_CUSTOMER_CURRENT_ORDER, order) + finish_async(AI_BEHAVIOR_SUCCEEDED) - controller.set_blackboard_key(BB_CUSTOMER_CURRENT_ORDER, attending_venue.order_food(customer_pawn, customer_data)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED -/datum/ai_behavior/wait_for_food - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_MOVE_AND_PERFORM - required_distance = 0 +/// Waits at the seat for food to arrive. Ticks down patience and checks for food placed in front. +/// Succeeds when BB_CUSTOMER_EATING is set; fails when patience runs out or BB_CUSTOMER_LEAVING is set. +/// On finish, sets BB_CUSTOMER_LEAVING and says the appropriate departure line. +/datum/bt_node/ai_behavior/robot_customer/wait_for_food -/datum/ai_behavior/wait_for_food/perform(seconds_per_tick, datum/ai_controller/controller) +/datum/bt_node/ai_behavior/robot_customer/wait_for_food/perform(seconds_per_tick, datum/ai_controller/controller) if(controller.blackboard[BB_CUSTOMER_EATING]) return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - controller.add_blackboard_key(BB_CUSTOMER_PATIENCE, seconds_per_tick * -1 SECONDS) // Convert seconds_per_tick to a SECONDS equivalent. - if(controller.blackboard[BB_CUSTOMER_PATIENCE] < 0 || controller.blackboard[BB_CUSTOMER_LEAVING]) // Check if we're leaving because something might've forced us to + controller.add_blackboard_key(BB_CUSTOMER_PATIENCE, seconds_per_tick * -1 SECONDS) + if(controller.blackboard[BB_CUSTOMER_PATIENCE] < 0 || controller.blackboard[BB_CUSTOMER_LEAVING]) return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - // SPT_PROB 1.5 is about a 40% chance that the tourist will have vocalised at least once every minute. if(SPT_PROB(0.85, seconds_per_tick)) var/mob/living/basic/robot_customer/customer_pawn = controller.pawn var/datum/customer_data/customer_data = controller.blackboard[BB_CUSTOMER_CUSTOMERINFO] - customer_pawn.say(pick(customer_data.wait_for_food_lines)) + INVOKE_ASYNC(customer_pawn, TYPE_PROC_REF(/atom/movable, say), pick(customer_data.wait_for_food_lines)) var/obj/structure/holosign/robot_seat/seat_marker = controller.blackboard[BB_CUSTOMER_MY_SEAT] if(get_turf(seat_marker) == get_turf(controller.pawn)) var/obj/structure/chair/my_seat = locate(/obj/structure/chair) in get_turf(controller.pawn) if(my_seat) - controller.pawn.setDir(my_seat.dir) //Sit in your seat + controller.pawn.setDir(my_seat.dir) - ///Now check if there's a meal infront of us. var/datum/venue/attending_venue = controller.blackboard[BB_CUSTOMER_ATTENDING_VENUE] - var/turf/infront_turf = get_step(controller.pawn, controller.pawn.dir) for(var/obj/item/I in infront_turf.contents) if(attending_venue.is_correct_order(I, controller.blackboard[BB_CUSTOMER_CURRENT_ORDER])) @@ -90,31 +98,42 @@ return AI_BEHAVIOR_DELAY -/datum/ai_behavior/wait_for_food/finish_action(datum/ai_controller/controller, succeeded) +/datum/bt_node/ai_behavior/robot_customer/wait_for_food/finish_action(datum/ai_controller/controller, succeeded) . = ..() var/mob/living/basic/robot_customer/customer_pawn = controller.pawn var/datum/customer_data/customer_data = controller.blackboard[BB_CUSTOMER_CUSTOMERINFO] var/mob/living/greytider = controller.blackboard[BB_CUSTOMER_CURRENT_TARGET] - //usually if we stop waiting, it's because we're done with the venue. but here we're either beating some dude up - //or are being qdeleted and don't want runtime errors, so don't switch to leaving + // Don't switch to leaving if we're heading to beat someone up or if we're being deleted. if(greytider || QDELETED(src) || QDELETED(customer_pawn)) return controller.set_blackboard_key(BB_CUSTOMER_LEAVING, TRUE) - customer_pawn.update_icon() //They might have a special leaving accessory (French flag) + customer_pawn.update_icon() if(succeeded) - customer_pawn.say(pick(customer_data.leave_happy_lines)) + INVOKE_ASYNC(customer_pawn, TYPE_PROC_REF(/atom/movable, say), pick(customer_data.leave_happy_lines)) else - customer_pawn.say(pick(customer_data.leave_mad_lines)) + INVOKE_ASYNC(customer_pawn, TYPE_PROC_REF(/atom/movable, say), pick(customer_data.leave_mad_lines)) -/datum/ai_behavior/leave_venue - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH +/// Resolves the venue exit portal from attending_venue.current_visitors and stores it in BB_CUSTOMER_EXIT_PORTAL. +/datum/bt_node/ai_behavior/robot_customer/find_exit_portal -/datum/ai_behavior/leave_venue/setup(datum/ai_controller/controller, venue_key) - . = ..() - var/datum/venue/attending_venue = controller.blackboard[venue_key] +/datum/bt_node/ai_behavior/robot_customer/find_exit_portal/perform(seconds_per_tick, datum/ai_controller/controller) + if(!isnull(controller.blackboard[BB_CUSTOMER_EXIT_PORTAL])) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED + var/datum/venue/attending_venue = controller.blackboard[BB_CUSTOMER_ATTENDING_VENUE] + if(isnull(attending_venue)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED var/datum/weakref/portal_ref = attending_venue.current_visitors[controller.pawn] - set_movement_target(controller, portal_ref.resolve()) + var/atom/portal = portal_ref?.resolve() + if(isnull(portal) || QDELETED(portal)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + controller.set_blackboard_key(BB_CUSTOMER_EXIT_PORTAL, portal) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED + + +/// Deletes the pawn once they have reached the exit portal. +/datum/bt_node/ai_behavior/robot_customer/leave_venue + +/datum/bt_node/ai_behavior/robot_customer/leave_venue/perform(seconds_per_tick, datum/ai_controller/controller) + qdel(controller.pawn) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED -/datum/ai_behavior/leave_venue/perform(seconds_per_tick, datum/ai_controller/controller, venue_key) - qdel(controller.pawn) //save the world, my final message, goodbye. - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED diff --git a/code/datums/ai/robot_customer/robot_customer_controller.dm b/code/datums/ai/robot_customer/robot_customer_controller.dm index 0fc9d27eb43..2a28089fc28 100644 --- a/code/datums/ai/robot_customer/robot_customer_controller.dm +++ b/code/datums/ai/robot_customer/robot_customer_controller.dm @@ -1,4 +1,5 @@ /datum/ai_controller/robot_customer + behavior_tree_json = "code/datums/ai/robot_customer/robot_customer.bt.json" ai_movement = /datum/ai_movement/basic_avoidance movement_delay = 0.8 SECONDS blackboard = list( @@ -11,12 +12,11 @@ BB_CUSTOMER_PATIENCE = 999 SECONDS, BB_CUSTOMER_SAID_CANT_FIND_SEAT_LINE = FALSE, ) - planning_subtrees = list(/datum/ai_planning_subtree/robot_customer) /datum/ai_controller/robot_customer/Destroy() - // clear possible datum refs clear_blackboard_key(BB_CUSTOMER_CURRENT_ORDER) clear_blackboard_key(BB_CUSTOMER_CUSTOMERINFO) + clear_blackboard_key(BB_CUSTOMER_EXIT_PORTAL) return ..() /datum/ai_controller/robot_customer/TryPossessPawn(atom/new_pawn) @@ -111,7 +111,7 @@ customer.say(customer_data.self_defense_line) set_blackboard_key(BB_CUSTOMER_CURRENT_TARGET, greytider) - CancelActions() + cancel_current_plan() /datum/ai_controller/robot_customer/proc/on_get_punched(datum/source, mob/living/living_hitter) SIGNAL_HANDLER diff --git a/code/datums/ai/robot_customer/robot_customer_subtrees.dm b/code/datums/ai/robot_customer/robot_customer_subtrees.dm deleted file mode 100644 index 3fd2d8ed547..00000000000 --- a/code/datums/ai/robot_customer/robot_customer_subtrees.dm +++ /dev/null @@ -1,23 +0,0 @@ -/datum/ai_planning_subtree/robot_customer/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(controller.blackboard[BB_CUSTOMER_LEAVING]) - controller.queue_behavior(/datum/ai_behavior/leave_venue, BB_CUSTOMER_ATTENDING_VENUE) - return SUBTREE_RETURN_FINISH_PLANNING - - if(controller.blackboard[BB_CUSTOMER_CURRENT_TARGET]) - - controller.queue_behavior(/datum/ai_behavior/break_spine, BB_CUSTOMER_CURRENT_TARGET) - return SUBTREE_RETURN_FINISH_PLANNING - - var/obj/structure/holosign/robot_seat/seat_marker = controller.blackboard[BB_CUSTOMER_MY_SEAT] - - if(!seat_marker) //We havn't got a seat yet! find one! - controller.queue_behavior(/datum/ai_behavior/find_seat) - return SUBTREE_RETURN_FINISH_PLANNING - - controller.set_movement_target(type, seat_marker) - - if(!controller.blackboard[BB_CUSTOMER_CURRENT_ORDER]) //We haven't ordered yet even ordered yet. go on! go over there and go do it! - controller.queue_behavior(/datum/ai_behavior/order_food) - return SUBTREE_RETURN_FINISH_PLANNING - else - controller.queue_behavior(/datum/ai_behavior/wait_for_food) diff --git a/code/datums/brain_damage/special.dm b/code/datums/brain_damage/special.dm index b4792fbd27c..fbea9e767f2 100644 --- a/code/datums/brain_damage/special.dm +++ b/code/datums/brain_damage/special.dm @@ -515,8 +515,8 @@ owner.ai_controller = new /datum/ai_controller/monkey(owner) owner.ai_controller.continue_processing_when_client = TRUE - owner.ai_controller.can_idle = FALSE - owner.ai_controller.set_ai_status(AI_STATUS_OFF) + owner.ai_controller.ai_traits |= RUN_WHILE_UNWATCHED + owner.ai_controller.force_ai_off() /datum/brain_trauma/special/primal_instincts/on_lose(silent) . = ..() @@ -539,14 +539,14 @@ owner.grant_language(/datum/language/monkey, UNDERSTOOD_LANGUAGE, TRAUMA_TRAIT) owner.ai_controller.set_blackboard_key(BB_MONKEY_AGGRESSIVE, prob(75)) if(owner.ai_controller.ai_status == AI_STATUS_OFF) - owner.ai_controller.set_ai_status(AI_STATUS_ON) + owner.ai_controller.clear_forced_off() owner.log_message("became controlled by monkey instincts ([owner.ai_controller.blackboard[BB_MONKEY_AGGRESSIVE] ? "aggressive" : "docile"])", LOG_ATTACK, color = "orange") to_chat(owner, span_warning("You feel the urge to act on your primal instincts...")) // extend original timer if we roll the effect while it's already ongoing addtimer(CALLBACK(src, PROC_REF(primal_instincts_off)), rand(20 SECONDS, 40 SECONDS), TIMER_UNIQUE|TIMER_NO_HASH_WAIT|TIMER_OVERRIDE|TIMER_DELETE_ME) /datum/brain_trauma/special/primal_instincts/proc/primal_instincts_off() - owner.ai_controller.set_ai_status(AI_STATUS_OFF) + owner.ai_controller.force_ai_off() owner.remove_language(/datum/language/monkey, UNDERSTOOD_LANGUAGE, TRAUMA_TRAIT) to_chat(owner, span_green("The urge subsides.")) diff --git a/code/datums/components/aggro_emote.dm b/code/datums/components/aggro_emote.dm index edf08723c40..ec2d2ef6f8b 100644 --- a/code/datums/components/aggro_emote.dm +++ b/code/datums/components/aggro_emote.dm @@ -22,7 +22,7 @@ var/minimum_chance /datum/component/aggro_emote/Initialize( - target_key = BB_BASIC_MOB_CURRENT_TARGET, + target_key = BB_CURRENT_TARGET, living_only = FALSE, list/emote_list, list/speak_list, // BUBBER EDIT - ADDITION: FLESHMIND diff --git a/code/datums/components/ai_has_target_timer.dm b/code/datums/components/ai_has_target_timer.dm index 5fdc07417f4..5037b4d9815 100644 --- a/code/datums/components/ai_has_target_timer.dm +++ b/code/datums/components/ai_has_target_timer.dm @@ -11,7 +11,7 @@ /// Timer used to see if you var/reset_clock_timer -/datum/component/ai_target_timer/Initialize(increment_key = BB_BASIC_MOB_HAS_TARGET_TIME, target_key = BB_BASIC_MOB_CURRENT_TARGET) +/datum/component/ai_target_timer/Initialize(increment_key = BB_BASIC_MOB_HAS_TARGET_TIME, target_key = BB_CURRENT_TARGET) . = ..() if (!isliving(parent)) return COMPONENT_INCOMPATIBLE diff --git a/code/datums/components/ai_listen_to_weather.dm b/code/datums/components/ai_listen_to_weather.dm index 29bcda3839b..758aaf8d3f5 100644 --- a/code/datums/components/ai_listen_to_weather.dm +++ b/code/datums/components/ai_listen_to_weather.dm @@ -26,7 +26,7 @@ var/mob/living/basic/source = parent if(!source.ai_controller) return - source.ai_controller.CancelActions() + source.ai_controller.cancel_current_plan() source.ai_controller.set_blackboard_key(weather_key, TRUE) /datum/component/ai_listen_to_weather/proc/storm_end() diff --git a/code/datums/components/appearance_on_aggro.dm b/code/datums/components/appearance_on_aggro.dm index 1cc9ef4f210..e78a13a3449 100644 --- a/code/datums/components/appearance_on_aggro.dm +++ b/code/datums/components/appearance_on_aggro.dm @@ -4,7 +4,7 @@ */ /datum/component/appearance_on_aggro /// Blackboardey to search for a target - var/target_key = BB_BASIC_MOB_CURRENT_TARGET + var/target_key = BB_CURRENT_TARGET /// Icon state to use when we have a target var/aggro_state /// path of the overlay to apply diff --git a/code/datums/components/connect_range.dm b/code/datums/components/connect_range.dm index e001261ab12..cb19a98552a 100644 --- a/code/datums/components/connect_range.dm +++ b/code/datums/components/connect_range.dm @@ -114,4 +114,6 @@ /datum/component/connect_range/proc/on_moved(atom/movable/movable, atom/old_loc) SIGNAL_HANDLER + if(QDELETED(src)) //Basically, if a mob moves it will trigger the movable signal on its own field. If the mob finds a target when moving it will qdel this, but because it also moved into the field this will run, crash, and burn. so we're checking qdeleted. + return update_signals(movable, old_loc) diff --git a/code/datums/components/pet_commands/fetch.dm b/code/datums/components/pet_commands/fetch.dm index f3675534fa9..9dfb196b1e7 100644 --- a/code/datums/components/pet_commands/fetch.dm +++ b/code/datums/components/pet_commands/fetch.dm @@ -92,30 +92,6 @@ var/mob/living/parent = weak_parent.resolve() parent.ai_controller.set_blackboard_key(BB_FETCH_DELIVER_TO, pointing_friend) -// Finally, plan our actions +// Install the fetch BT subtree. The subtree itself handles all phases (seek > pick up > deliver). /datum/pet_command/fetch/execute_action(datum/ai_controller/controller) - controller.queue_behavior(/datum/ai_behavior/forget_failed_fetches) - - var/atom/target = controller.blackboard[BB_CURRENT_PET_TARGET] - // We got something to fetch so go fetch it - if (!QDELETED(target)) - if (get_dist(controller.pawn, target) > 1) // We're not there yet - controller.queue_behavior(/datum/ai_behavior/fetch_seek, BB_CURRENT_PET_TARGET, BB_FETCH_DELIVER_TO) - return SUBTREE_RETURN_FINISH_PLANNING - // If mobs could attack food you would branch here to call `eat_fetched_snack`, however that's a task for the future - controller.queue_behavior(/datum/ai_behavior/pick_up_item, BB_CURRENT_PET_TARGET, BB_SIMPLE_CARRY_ITEM) - return SUBTREE_RETURN_FINISH_PLANNING - - var/obj/item/carried_item = controller.blackboard[BB_SIMPLE_CARRY_ITEM] - if (QDELETED(carried_item)) - return - - var/atom/delivery_target = controller.blackboard[BB_FETCH_DELIVER_TO] - if (QDELETED(delivery_target) || !can_see(controller.pawn, delivery_target, sense_radius)) - // We don't know where to return this to so we're just going to keep it - controller.clear_blackboard_key(BB_ACTIVE_PET_COMMAND) - return - - // We got something to deliver and someone to deliver it to - controller.queue_behavior(/datum/ai_behavior/deliver_fetched_item, BB_FETCH_DELIVER_TO, BB_SIMPLE_CARRY_ITEM) - return SUBTREE_RETURN_FINISH_PLANNING + controller.set_behavior_tree_override(SUBPLAN_ID_PET_COMMAND, /datum/bt_node/subtree/pet_command/fetch) diff --git a/code/datums/components/pet_commands/pet_command.dm b/code/datums/components/pet_commands/pet_command.dm index 04dde32d5db..5616de8a6ca 100644 --- a/code/datums/components/pet_commands/pet_command.dm +++ b/code/datums/components/pet_commands/pet_command.dm @@ -148,7 +148,6 @@ if (!can_see(parent, potential_target, sense_radius)) return FALSE - parent.ai_controller.CancelActions() set_command_target(parent, potential_target) return TRUE @@ -156,8 +155,11 @@ /datum/pet_command/proc/set_command_active(mob/living/parent, mob/living/commander, radial_command = FALSE) parent.ai_controller.clear_blackboard_key(BB_CURRENT_PET_TARGET) - parent.ai_controller.CancelActions() // Stop whatever you're doing and do this instead + var/datum/pet_command/previous = parent.ai_controller.blackboard[BB_ACTIVE_PET_COMMAND] + if(previous && previous != src) + previous.command_ended(parent.ai_controller) parent.ai_controller.set_blackboard_key(BB_ACTIVE_PET_COMMAND, src) + execute_action(parent.ai_controller) // Install the BT override subtree for this command if (command_feedback) parent.balloon_alert_to_viewers("[command_feedback]") // If we get a nicer runechat way to do this, refactor this if(!radial_command) @@ -170,6 +172,12 @@ commander.client?.mouse_override_icon = 'icons/effects/mouse_pointers/pet_paw.dmi' commander.update_mouse_pointer() + +/// Called when this command is replaced by another command or otherwise deactivated. Extend to add cleanup logic. +/datum/pet_command/proc/command_ended(datum/ai_controller/controller) + controller.set_behavior_tree_override(SUBPLAN_ID_PET_COMMAND, null) + return + /datum/pet_command/proc/click_on_target(mob/living/source, atom/target, list/modifiers) SIGNAL_HANDLER if(!can_see(source, target, 9)) @@ -215,7 +223,7 @@ if (!parent) return FALSE - parent.ai_controller.CancelActions() + parent.ai_controller.cancel_current_plan() if(!look_for_target(friend, potential_target) || !set_command_target(parent, potential_target)) return FALSE parent.visible_message(span_warning("[parent] follows [friend]'s gesture towards [potential_target] [pointed_reaction]!")) diff --git a/code/datums/components/pet_commands/pet_commands_basic.dm b/code/datums/components/pet_commands/pet_commands_basic.dm index 2269f760ba4..ff1dcf00739 100644 --- a/code/datums/components/pet_commands/pet_commands_basic.dm +++ b/code/datums/components/pet_commands/pet_commands_basic.dm @@ -12,14 +12,14 @@ command_feedback = "sits" /datum/pet_command/idle/execute_action(datum/ai_controller/controller) - return SUBTREE_RETURN_FINISH_PLANNING // This cancels further AI planning + controller.set_behavior_tree_override(SUBPLAN_ID_PET_COMMAND, /datum/bt_node/subtree/pet_command/stay) /datum/pet_command/idle/retrieve_command_text(atom/living_pet, atom/target) return "signals [living_pet] to stay idle!" /** * # Pet Command: Stop - * Tells a pet to exit command mode and resume its normal behaviour, which includes regular target-seeking and what have you + * Tells a pet to exit command mode and resume its normal behaviour */ /datum/pet_command/free command_name = "Loose" @@ -29,8 +29,8 @@ command_feedback = "relaxes" /datum/pet_command/free/execute_action(datum/ai_controller/controller) + controller.set_behavior_tree_override(SUBPLAN_ID_PET_COMMAND, null) controller.clear_blackboard_key(BB_ACTIVE_PET_COMMAND) - return // Just move on to the next planning subtree. /datum/pet_command/free/retrieve_command_text(atom/living_pet, atom/target) return "signals [living_pet] to go free!" @@ -45,10 +45,10 @@ radial_icon_state = "follow" speech_commands = list("heel", "follow") callout_type = /datum/callout_option/move - ///the behavior we use to follow - var/follow_behavior = /datum/ai_behavior/pet_follow_friend ///should we activate immediately if we're doing nothing else and gain a friend? var/activate_on_befriend = FALSE + /// BT subtree installed by execute_action; override to use a mob-specific follow tree. + var/follow_subtree = /datum/bt_node/subtree/pet_command/follow /datum/pet_command/follow/set_command_active(mob/living/parent, mob/living/commander) . = ..() @@ -58,8 +58,7 @@ return "signals [living_pet] to follow!" /datum/pet_command/follow/execute_action(datum/ai_controller/controller) - controller.queue_behavior(follow_behavior, BB_CURRENT_PET_TARGET) - return SUBTREE_RETURN_FINISH_PLANNING + controller.set_behavior_tree_override(SUBPLAN_ID_PET_COMMAND, follow_subtree) /datum/pet_command/follow/add_new_friend(mob/living/tamer) . = ..() @@ -84,8 +83,7 @@ speech_commands = list("play dead") // Don't get too creative here, people talk about dying pretty often /datum/pet_command/play_dead/execute_action(datum/ai_controller/controller) - controller.queue_behavior(/datum/ai_behavior/play_dead) - return SUBTREE_RETURN_FINISH_PLANNING + controller.set_behavior_tree_override(SUBPLAN_ID_PET_COMMAND, /datum/bt_node/subtree/pet_command/play_dead) /datum/pet_command/play_dead/retrieve_command_text(atom/living_pet, atom/target) return "signals [living_pet] to play dead!" @@ -112,14 +110,13 @@ // If we get past this point someone has finally added a non-binary dog /datum/pet_command/good_boy/execute_action(datum/ai_controller/controller) + controller.set_behavior_tree_override(SUBPLAN_ID_PET_COMMAND, null) controller.clear_blackboard_key(BB_ACTIVE_PET_COMMAND) var/mob/living/parent = weak_parent.resolve() if (!parent) - return SUBTREE_RETURN_FINISH_PLANNING - + return new /obj/effect/temp_visual/heart(parent.loc) parent.emote("spin") - return SUBTREE_RETURN_FINISH_PLANNING /** * # Pet Command: Use ability @@ -133,9 +130,9 @@ var/datum/action/cooldown/ability = controller.blackboard[ability_key] if(!ability?.IsAvailable()) return - controller.queue_behavior(/datum/ai_behavior/use_mob_ability, ability_key) + controller.set_blackboard_key(BB_PET_ACTIVE_ABILITY, ability) + controller.set_behavior_tree_override(SUBPLAN_ID_PET_COMMAND, /datum/bt_node/subtree/pet_command/untargeted_ability) controller.clear_blackboard_key(BB_ACTIVE_PET_COMMAND) - return SUBTREE_RETURN_FINISH_PLANNING /datum/pet_command/untargeted_ability/retrieve_command_text(atom/living_pet, atom/target) return "signals [living_pet] to use an ability!" @@ -155,8 +152,7 @@ pointed_reaction = "and growls" /// Balloon alert to display if providing an invalid target var/refuse_reaction = "shakes head" - /// Attack behaviour to use - var/attack_behaviour = /datum/ai_behavior/basic_melee_attack + var/datum/bt_node/subtree/attack_subtree = /datum/bt_node/subtree/pet_command/attack // Refuse to target things we can't target, chiefly other friends /datum/pet_command/attack/set_command_target(mob/living/parent, atom/target) @@ -168,7 +164,7 @@ var/datum/targeting_strategy/targeter = GET_TARGETING_STRATEGY(living_parent.ai_controller.blackboard[targeting_strategy_key]) if (!targeter) return FALSE - if (!targeter.can_attack(living_parent, target)) + if (!targeter.is_valid_target(living_parent, target)) refuse_target(parent, target) return FALSE return ..() @@ -183,8 +179,7 @@ living_parent.visible_message(span_notice("[living_parent] refuses to attack [target].")) /datum/pet_command/attack/execute_action(datum/ai_controller/controller) - controller.queue_behavior(attack_behaviour, BB_CURRENT_PET_TARGET, targeting_strategy_key) - return SUBTREE_RETURN_FINISH_PLANNING + controller.set_behavior_tree_override(SUBPLAN_ID_PET_COMMAND, attack_subtree) /** * # Breed command. breed with a partner! @@ -195,8 +190,6 @@ requires_pointing = TRUE radial_icon_state = "breed" speech_commands = list("breed", "consummate") - ///the behavior we use to make babies - var/datum/ai_behavior/reproduce_behavior = /datum/ai_behavior/make_babies /datum/pet_command/breed/set_command_target(mob/living/parent, atom/target) if(isnull(target) || !isliving(target)) @@ -213,10 +206,7 @@ return ..() /datum/pet_command/breed/execute_action(datum/ai_controller/controller) - if(is_type_in_list(controller.blackboard[BB_CURRENT_PET_TARGET], controller.blackboard[BB_BABIES_PARTNER_TYPES])) - controller.queue_behavior(reproduce_behavior, BB_CURRENT_PET_TARGET) - controller.clear_blackboard_key(BB_ACTIVE_PET_COMMAND) - return SUBTREE_RETURN_FINISH_PLANNING + controller.set_behavior_tree_override(SUBPLAN_ID_PET_COMMAND, /datum/bt_node/subtree/pet_command/breed) /datum/pet_command/breed/retrieve_command_text(atom/living_pet, atom/target) return isnull(target) ? null : "signals [living_pet] to breed with [target]!" @@ -236,8 +226,6 @@ pointed_reaction = "and growls" /// Blackboard key where a reference to some kind of mob ability is stored var/pet_ability_key - /// The AI behavior to use for the ability - var/ability_behavior = /datum/ai_behavior/pet_use_ability /datum/pet_command/use_ability/execute_action(datum/ai_controller/controller) if (!pet_ability_key) @@ -245,10 +233,9 @@ var/datum/action/cooldown/using_action = controller.blackboard[pet_ability_key] if (QDELETED(using_action)) return - // We don't check if the target exists because we want to 'sit attentively' if we've been instructed to attack but not given one yet - // We also don't check if the cooldown is over because there's no way a pet owner can know that, the behaviour will handle it - controller.queue_behavior(ability_behavior, pet_ability_key, BB_CURRENT_PET_TARGET) - return SUBTREE_RETURN_FINISH_PLANNING + // Store the action datum in a fixed key so the targeted_ability subtree can find it + controller.override_blackboard_key(BB_TARGETED_ACTION, using_action) + controller.set_behavior_tree_override(SUBPLAN_ID_PET_COMMAND, /datum/bt_node/subtree/pet_command/targeted_ability) /datum/pet_command/use_ability/retrieve_command_text(atom/living_pet, atom/target) return isnull(target) ? null : "signals [living_pet] to use an ability on [target]!" @@ -260,10 +247,9 @@ callout_type = /datum/callout_option/guard ///the range our owner needs to be in for us to protect him var/protect_range = 9 - ///the behavior we will use when he is attacked - var/protect_behavior = /datum/ai_behavior/basic_melee_attack ///message cooldown to prevent too many people from telling you not to commit suicide COOLDOWN_DECLARE(self_harm_message_cooldown) + var/datum/bt_node/subtree/protect_owner_subtree = /datum/bt_node/subtree/pet_command/protect_owner /datum/pet_command/protect_owner/add_new_friend(mob/living/tamer) RegisterSignal(tamer, COMSIG_ATOM_WAS_ATTACKED, PROC_REF(set_attacking_target)) @@ -274,18 +260,7 @@ UnregisterSignal(unfriended, COMSIG_ATOM_WAS_ATTACKED) /datum/pet_command/protect_owner/execute_action(datum/ai_controller/controller) - var/mob/living/victim = controller.blackboard[BB_CURRENT_PET_TARGET] - if(QDELETED(victim)) - return - var/datum/targeting_strategy/targeter = GET_TARGETING_STRATEGY(controller.blackboard[targeting_strategy_key]) - if(!targeter.can_attack(controller.pawn, victim)) - return - // cancel the action if they're below our given crit stat, OR if we're trying to attack ourselves (this can happen on tamed mobs w/ protect subtree rarely) - if(victim.stat > controller.blackboard[BB_TARGET_MINIMUM_STAT] || victim == controller.pawn) - controller.clear_blackboard_key(BB_ACTIVE_PET_COMMAND) - return - controller.queue_behavior(protect_behavior, BB_CURRENT_PET_TARGET, BB_PET_TARGETING_STRATEGY) - return SUBTREE_RETURN_FINISH_PLANNING + controller.set_behavior_tree_override(SUBPLAN_ID_PET_COMMAND, protect_owner_subtree) /datum/pet_command/protect_owner/set_command_active(mob/living/parent, mob/living/victim) . = ..() @@ -314,7 +289,7 @@ set_command_active(owner, attacker) /** - * # Fish command: command the mob to fish at the next fishing spot you point at. Requires the profound fisher component + * # Fish command: command the mob to fish at the next fishing spot you point at. */ /datum/pet_command/fish command_name = "Fish" @@ -324,9 +299,10 @@ speech_commands = list("fish") /datum/pet_command/fish/execute_action(datum/ai_controller/controller) - if(controller.blackboard_key_exists(BB_CURRENT_PET_TARGET)) - controller.queue_behavior(/datum/ai_behavior/interact_with_target/fishing, BB_CURRENT_PET_TARGET) - return SUBTREE_RETURN_FINISH_PLANNING + if(!controller.blackboard_key_exists(BB_CURRENT_PET_TARGET)) + controller.set_behavior_tree_override(SUBPLAN_ID_PET_COMMAND, /datum/bt_node/subtree/pet_command/stay) + return + controller.set_behavior_tree_override(SUBPLAN_ID_PET_COMMAND, /datum/bt_node/subtree/pet_command/fish) /datum/pet_command/fish/retrieve_command_text(atom/living_pet, atom/target) return "signals [living_pet] to go fish!" @@ -337,8 +313,6 @@ requires_pointing = TRUE radial_icon_state = "move" speech_commands = list("move", "walk") - ///the behavior we use to walk towards targets - var/datum/ai_behavior/walk_behavior = /datum/ai_behavior/travel_towards /datum/pet_command/move/set_command_target(mob/living/parent, atom/target) if(isnull(target) || !can_see(parent, target, 9)) @@ -346,9 +320,7 @@ return ..() /datum/pet_command/move/execute_action(datum/ai_controller/controller) - if(controller.blackboard_key_exists(BB_CURRENT_PET_TARGET)) - controller.queue_behavior(walk_behavior, BB_CURRENT_PET_TARGET) - return SUBTREE_RETURN_FINISH_PLANNING + controller.set_behavior_tree_override(SUBPLAN_ID_PET_COMMAND, /datum/bt_node/subtree/pet_command/move_to) /datum/pet_command/move/retrieve_command_text(atom/living_pet, atom/target) return "signals [living_pet] to move!" diff --git a/code/datums/components/proficient_miner.dm b/code/datums/components/proficient_miner.dm index b37153b3841..2c264dd3a18 100644 --- a/code/datums/components/proficient_miner.dm +++ b/code/datums/components/proficient_miner.dm @@ -15,14 +15,27 @@ /datum/component/proficient_miner/RegisterWithParent() RegisterSignal(parent, COMSIG_MOVABLE_BUMP, PROC_REF(on_bump)) + RegisterSignal(parent, COMSIG_LIVING_UNARMED_ATTACK, PROC_REF(on_unarmed_attack)) /datum/component/proficient_miner/UnregisterFromParent() - UnregisterSignal(parent, COMSIG_MOVABLE_BUMP) + UnregisterSignal(parent, list(COMSIG_MOVABLE_BUMP, COMSIG_LIVING_UNARMED_ATTACK)) + +/datum/component/proficient_miner/proc/on_unarmed_attack(mob/living/source, atom/target, proximity, modifiers) + SIGNAL_HANDLER + + if(!proximity) + return + try_mine(source, target) /datum/component/proficient_miner/proc/on_bump(atom/movable/source, atom/target) SIGNAL_HANDLER - if(!ismineralturf(target) || last_bumpmine_tick == world.time) + if(last_bumpmine_tick == world.time) + return + try_mine(source, target) + +/datum/component/proficient_miner/proc/try_mine(atom/movable/source, atom/target) + if(!ismineralturf(target)) return var/mob/living/user = null diff --git a/code/datums/components/revenge_ability.dm b/code/datums/components/revenge_ability.dm index 2c52b8cd31a..aa0f81ded67 100644 --- a/code/datums/components/revenge_ability.dm +++ b/code/datums/components/revenge_ability.dm @@ -50,7 +50,7 @@ var/distance = get_dist(ability_user, attacker) if (distance < min_range || distance > max_range) return - if (targeting && !targeting.can_attack(victim, attacker)) + if (targeting && !targeting.is_valid_target(victim, attacker)) return INVOKE_ASYNC(ability, TYPE_PROC_REF(/datum/action/cooldown, InterceptClickOn), ability_user, null, (target_self) ? ability_user : attacker) diff --git a/code/datums/components/tameable.dm b/code/datums/components/tameable.dm index 2153d54d51e..9fcdeea36fd 100644 --- a/code/datums/components/tameable.dm +++ b/code/datums/components/tameable.dm @@ -56,6 +56,9 @@ /datum/component/tameable/proc/on_tame(atom/source, mob/living/tamer, obj/item/food, inform_tamer = FALSE) SIGNAL_HANDLER source.tamed(tamer, food)//Run custom behavior if needed + if(isliving(parent)) + var/mob/living/living_parent = parent + living_parent.ai_controller?.set_blackboard_key(BB_TAMED, TRUE) if(isliving(parent) && isliving(tamer)) INVOKE_ASYNC(source, TYPE_PROC_REF(/mob/living, befriend), tamer) if(inform_tamer) diff --git a/code/datums/components/tree_climber.dm b/code/datums/components/tree_climber.dm index 9f506ae516f..6ec21ac41a9 100644 --- a/code/datums/components/tree_climber.dm +++ b/code/datums/components/tree_climber.dm @@ -15,11 +15,11 @@ ADD_TRAIT(parent, TRAIT_SUBTREE_REQUIRED_OPERATIONAL_DATUM, type) /datum/component/tree_climber/RegisterWithParent() - RegisterSignals(parent, list(COMSIG_HOSTILE_PRE_ATTACKINGTARGET, COMSIG_LIVING_CLIMB_TREE), PROC_REF(climb_tree)) + RegisterSignal(parent, COMSIG_HOSTILE_PRE_ATTACKINGTARGET, PROC_REF(climb_tree)) RegisterSignal(parent, COMSIG_ATOM_EXAMINE, PROC_REF(on_examine)) /datum/component/tree_climber/UnregisterFromParent() - UnregisterSignal(parent, list(COMSIG_HOSTILE_PRE_ATTACKINGTARGET, COMSIG_LIVING_CLIMB_TREE, COMSIG_ATOM_EXAMINE)) + UnregisterSignal(parent, list(COMSIG_HOSTILE_PRE_ATTACKINGTARGET, COMSIG_ATOM_EXAMINE)) /datum/component/tree_climber/Destroy() if(current_tree) diff --git a/code/datums/diseases/verminous_plague.dm b/code/datums/diseases/verminous_plague.dm index 8e56f7fcc08..981bfadea01 100644 --- a/code/datums/diseases/verminous_plague.dm +++ b/code/datums/diseases/verminous_plague.dm @@ -66,7 +66,7 @@ GLOBAL_LIST_INIT(cursed_vermin_by_stage, list( if (stage == 1 || !created.ai_controller) return - created.ai_controller.set_blackboard_key(BB_BASIC_MOB_CURRENT_TARGET, affected_mob) + created.ai_controller.set_blackboard_key(BB_CURRENT_TARGET, affected_mob) created.ai_controller.insert_blackboard_key_lazylist(BB_BASIC_MOB_RETALIATE_LIST, affected_mob) /datum/disease/verminous_plague/update_stage(new_stage) diff --git a/code/datums/dog_fashion.dm b/code/datums/dog_fashion.dm index 1c27df60822..ef95b1bce7a 100644 --- a/code/datums/dog_fashion.dm +++ b/code/datums/dog_fashion.dm @@ -45,13 +45,13 @@ dressup_doggy.speak_emote = string_list(speak_emote) ///Applies random speech modifiers to the dog -/datum/dog_fashion/proc/apply_to_speech(datum/ai_planning_subtree/random_speech/speech) +/datum/dog_fashion/proc/apply_to_speech(list/speech_data) if(LAZYLEN(emote_see)) - speech.emote_see = string_list(emote_see) + speech_data[BB_EMOTE_SEE] = string_list(emote_see) if(LAZYLEN(emote_hear)) - speech.emote_hear = string_list(emote_hear) + speech_data[BB_EMOTE_HEAR] = string_list(emote_hear) if(LAZYLEN(speak)) - speech.speak = string_list(speak) + speech_data[BB_EMOTE_SAY] = string_list(speak) /** * Generates the icon overlay for the equipped item diff --git a/code/datums/elements/ai_flee_while_injured.dm b/code/datums/elements/ai_flee_while_injured.dm index eca709dbee5..c478181244e 100644 --- a/code/datums/elements/ai_flee_while_injured.dm +++ b/code/datums/elements/ai_flee_while_injured.dm @@ -36,11 +36,9 @@ if (source.ai_controller.blackboard[BB_BASIC_MOB_STOP_FLEEING]) if (current_health_percentage > start_fleeing_below) return - source.ai_controller.CancelActions() source.ai_controller.set_blackboard_key(BB_BASIC_MOB_STOP_FLEEING, FALSE) return if (current_health_percentage < stop_fleeing_at) return - source.ai_controller.CancelActions() // Stop fleeing go back to whatever you were doing source.ai_controller.set_blackboard_key(BB_BASIC_MOB_STOP_FLEEING, TRUE) diff --git a/code/datums/elements/ai_retaliate.dm b/code/datums/elements/ai_retaliate.dm index 4fcb5158143..c4c4266e2a4 100644 --- a/code/datums/elements/ai_retaliate.dm +++ b/code/datums/elements/ai_retaliate.dm @@ -18,7 +18,7 @@ . = ..() UnregisterSignal(source, COMSIG_ATOM_WAS_ATTACKED) -/// Add an attacking atom to a blackboard list of things which attacked us +/// Add an attacking atom to a blackboard list of things which attacked us. /datum/element/ai_retaliate/proc/on_attacked(mob/victim, atom/attacker) SIGNAL_HANDLER diff --git a/code/datums/elements/ai_target_damagesource.dm b/code/datums/elements/ai_target_damagesource.dm index a1f0ea8b2a5..7ef04b9e4cd 100644 --- a/code/datums/elements/ai_target_damagesource.dm +++ b/code/datums/elements/ai_target_damagesource.dm @@ -22,5 +22,5 @@ if (!victim.ai_controller) return - victim.ai_controller.CancelActions() - victim.ai_controller.set_blackboard_key(BB_BASIC_MOB_CURRENT_TARGET, attacker) + victim.ai_controller.cancel_current_plan() + victim.ai_controller.set_blackboard_key(BB_CURRENT_TARGET, attacker) diff --git a/code/datums/proximity_monitor/fields/ai_target_tracking.dm b/code/datums/proximity_monitor/fields/ai_target_tracking.dm index 46cde22aaff..5c596e1dfd7 100644 --- a/code/datums/proximity_monitor/fields/ai_target_tracking.dm +++ b/code/datums/proximity_monitor/fields/ai_target_tracking.dm @@ -2,13 +2,13 @@ /datum/proximity_monitor/advanced/ai_target_tracking edge_is_a_field = TRUE /// The ai behavior who owns us - var/datum/ai_behavior/find_potential_targets/owning_behavior + var/datum/bt_node/ai_behavior/acquire_target/update_combat_targets/owning_behavior /// The ai controller we're using var/datum/ai_controller/controller /// The target key we're trying to fill var/target_key - /// The targeting strategy KEY we're using - var/targeting_strategy_key + /// The targeting_strategy var value from the owning behavior either a typepath or a BB key string + var/targeting_strategy /// The hiding location key we're using var/hiding_location_key @@ -20,30 +20,31 @@ // Initially, run the check manually // If that fails, set up a field and have it manage the behavior fully -/datum/proximity_monitor/advanced/ai_target_tracking/New(atom/_host, range, _ignore_if_not_on_turf = TRUE, datum/ai_behavior/find_potential_targets/owning_behavior, datum/ai_controller/controller, target_key, targeting_strategy_key, hiding_location_key) +/datum/proximity_monitor/advanced/ai_target_tracking/New(atom/_host, range, _ignore_if_not_on_turf = TRUE, datum/bt_node/ai_behavior/acquire_target/update_combat_targets/owning_behavior, datum/ai_controller/controller, target_key, targeting_strategy, hiding_location_key) . = ..() src.owning_behavior = owning_behavior src.controller = controller src.target_key = target_key - src.targeting_strategy_key = targeting_strategy_key + src.targeting_strategy = targeting_strategy src.hiding_location_key = hiding_location_key - src.filter = GET_TARGETING_STRATEGY(controller.blackboard[targeting_strategy_key]) + + if(ispath(targeting_strategy)) + src.filter = GET_TARGETING_STRATEGY(targeting_strategy) + else + src.filter = GET_TARGETING_STRATEGY(controller.blackboard[targeting_strategy]) + RegisterSignal(controller, COMSIG_AI_BLACKBOARD_KEY_SET(targeting_strategy), PROC_REF(targeting_datum_changed)) + RegisterSignal(controller, COMSIG_AI_BLACKBOARD_KEY_CLEARED(targeting_strategy), PROC_REF(targeting_datum_cleared)) + RegisterSignal(controller, COMSIG_QDELETING, PROC_REF(controller_deleted)) - RegisterSignal(controller, COMSIG_AI_CONTROLLER_PICKED_BEHAVIORS, PROC_REF(controller_think)) RegisterSignal(controller, COMSIG_AI_CONTROLLER_POSSESSED_PAWN, PROC_REF(pawn_changed)) - RegisterSignal(controller, AI_CONTROLLER_BEHAVIOR_QUEUED(owning_behavior.type), PROC_REF(behavior_requeued)) - RegisterSignal(controller, COMSIG_AI_BLACKBOARD_KEY_SET(targeting_strategy_key), PROC_REF(targeting_datum_changed)) - RegisterSignal(controller, COMSIG_AI_BLACKBOARD_KEY_CLEARED(targeting_strategy_key), PROC_REF(targeting_datum_cleared)) recalculate_field(full_recalc = TRUE) /datum/proximity_monitor/advanced/ai_target_tracking/Destroy() . = ..() - if(!QDELETED(controller) && owning_behavior) - controller.modify_cooldown(owning_behavior, owning_behavior.get_cooldown(controller)) owning_behavior = null controller = null target_key = null - targeting_strategy_key = null + targeting_strategy = null hiding_location_key = null filter = null @@ -75,24 +76,13 @@ SIGNAL_HANDLER qdel(src) -/// React to controller planning -/datum/proximity_monitor/advanced/ai_target_tracking/proc/controller_think(datum/ai_controller/source, list/datum/ai_behavior/old_behaviors, list/datum/ai_behavior/new_behaviors) - SIGNAL_HANDLER - // If our parent was forgotten, nuke ourselves - if(!new_behaviors[owning_behavior]) - qdel(src) - -/datum/proximity_monitor/advanced/ai_target_tracking/proc/behavior_requeued(datum/source, list/new_arguments) - SIGNAL_HANDLER - check_new_args(arglist(new_arguments)) - /// Ensure our args and locals are up to date -/datum/proximity_monitor/advanced/ai_target_tracking/proc/check_new_args(target_key, targeting_strategy_key, hiding_location_key) +/datum/proximity_monitor/advanced/ai_target_tracking/proc/check_new_args(target_key, targeting_strategy, hiding_location_key) var/update_filter = FALSE if(src.target_key != target_key) src.target_key = target_key - if(src.targeting_strategy_key != targeting_strategy_key) - src.targeting_strategy_key = targeting_strategy_key + if(src.targeting_strategy != targeting_strategy) + src.targeting_strategy = targeting_strategy update_filter = TRUE if(src.hiding_location_key != hiding_location_key) src.hiding_location_key = hiding_location_key @@ -101,7 +91,10 @@ /datum/proximity_monitor/advanced/ai_target_tracking/proc/targeting_datum_changed(datum/source) SIGNAL_HANDLER - filter = controller.blackboard[targeting_strategy_key] + if(ispath(targeting_strategy)) + filter = GET_TARGETING_STRATEGY(targeting_strategy) + else + filter = GET_TARGETING_STRATEGY(controller.blackboard[targeting_strategy]) // Filter changed, need to do a full reparse // Fucking 9 * 9 out here I stg for(var/turf/in_field as anything in field_turfs + edge_turfs) diff --git a/code/datums/proximity_monitor/fields/timestop.dm b/code/datums/proximity_monitor/fields/timestop.dm index 8411e2c3bb3..92fbf12356e 100644 --- a/code/datums/proximity_monitor/fields/timestop.dm +++ b/code/datums/proximity_monitor/fields/timestop.dm @@ -223,7 +223,7 @@ hostile_victim.LoseTarget() else if(isbasicmob(victim)) var/mob/living/basic/basic_victim = victim - basic_victim.ai_controller?.set_ai_status(AI_STATUS_OFF) + basic_victim.ai_controller?.force_ai_off() /datum/proximity_monitor/advanced/timestop/proc/unfreeze_mob(mob/living/victim) victim.AdjustStun(-20, ignore_canstun = TRUE) @@ -234,7 +234,7 @@ animal_victim.toggle_ai(initial(animal_victim.AIStatus)) else if(isbasicmob(victim)) var/mob/living/basic/basic_victim = victim - basic_victim.ai_controller?.reset_ai_status() + basic_victim.ai_controller?.clear_forced_off() //you don't look quite right, is something the matter? /datum/proximity_monitor/advanced/timestop/proc/into_the_negative_zone(atom/A) diff --git a/code/datums/status_effects/debuffs/slime/slime_leech.dm b/code/datums/status_effects/debuffs/slime/slime_leech.dm index d1125b2d25d..85a8a898b14 100644 --- a/code/datums/status_effects/debuffs/slime/slime_leech.dm +++ b/code/datums/status_effects/debuffs/slime/slime_leech.dm @@ -57,7 +57,8 @@ if(prob(60) && ishuman(owner) && owner.client && !our_slime.ai_controller.blackboard[BB_SLIME_RABID]) our_slime.ai_controller?.set_blackboard_key(BB_SLIME_RABID, TRUE) //we might go rabid after finishing to feed on a human with a client. - our_slime.stop_feeding() + if(our_slime) + our_slime.stop_feeding() return var/totaldamage = 0 //total damage done to this unfortunate soul diff --git a/code/game/machinery/computer/arcade/orion_event.dm b/code/game/machinery/computer/arcade/orion_event.dm index 7c834800f1d..db5061183b5 100644 --- a/code/game/machinery/computer/arcade/orion_event.dm +++ b/code/game/machinery/computer/arcade/orion_event.dm @@ -526,7 +526,7 @@ playsound(game, 'sound/items/weeoo1.ogg', 100, FALSE) for(var/i in 1 to 3) var/mob/living/basic/trooper/syndicate/ranged/smg/orion/spaceport_security = new(get_turf(game)) - spaceport_security.ai_controller.set_blackboard_key(BB_BASIC_MOB_CURRENT_TARGET, usr) + spaceport_security.ai_controller.set_blackboard_key(BB_CURRENT_TARGET, usr) game.fuel += fuel game.food += food diff --git a/code/game/machinery/dna_scanner.dm b/code/game/machinery/dna_scanner.dm index a17300b88db..1e45aa7248d 100644 --- a/code/game/machinery/dna_scanner.dm +++ b/code/game/machinery/dna_scanner.dm @@ -15,7 +15,7 @@ var/scan_level var/precision_coeff = 1 var/message_cooldown - var/breakout_time = 1200 + var/breakout_time = 120 SECONDS var/obj/machinery/computer/dna_console/linked_console = null /obj/machinery/dna_scannernew/RefreshParts() @@ -70,16 +70,19 @@ open_machine() /obj/machinery/dna_scannernew/container_resist_act(mob/living/user) - if(!locked) + if(HAS_TRAIT(user, TRAIT_PRIMITIVE) || user.ai_controller) + if(locked) + return //Your primitive brain cant escape a dna scanner noob + else if(!locked) //Not locked and not primitive? escape immediately open_machine() - return + user.changeNext_move(CLICK_CD_BREAKOUT) user.last_special = world.time + CLICK_CD_BREAKOUT user.visible_message(span_notice("You see [user] kicking against the door of [src]!"), \ span_notice("You lean on the back of [src] and start pushing the door open... (this will take about [DisplayTimeText(breakout_time)].)"), \ span_hear("You hear a metallic creaking from [src].")) if(do_after(user,(breakout_time), target = src)) - if(!user || user.stat != CONSCIOUS || user.loc != src || state_open || !locked) + if(!user || user.stat != CONSCIOUS || user.loc != src || state_open || !locked || HAS_TRAIT(user, TRAIT_PRIMITIVE) || user.ai_controller) return locked = FALSE user.visible_message(span_warning("[user] successfully broke out of [src]!"), \ @@ -109,6 +112,8 @@ /obj/machinery/dna_scannernew/open_machine(drop = TRUE, density_to_set = FALSE) if(state_open) return FALSE + if(locked) //haha bro u cant open it its locked xD + return FALSE ..() diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm index 96a29cb10ba..53870d5556a 100644 --- a/code/modules/admin/admin_verbs.dm +++ b/code/modules/admin/admin_verbs.dm @@ -765,109 +765,12 @@ ADMIN_VERB(clear_smart_asset_cache, R_DEBUG, "Clear Smart Asset Cache", "Clear t cleared++ to_chat(user, span_notice("Cleared [cleared] asset\s.")) -ADMIN_VERB(give_ai_speech, R_FUN, "Give Random AI Speech", ADMIN_VERB_NO_DESCRIPTION, ADMIN_CATEGORY_HIDDEN, mob/living/my_guy) - if (isnull(my_guy.ai_controller)) - var/create_controller = tgui_alert(user, "Target has no AI controller, add one?", "Give AI?", list("Yes", "No")) == "Yes" - if (!create_controller) - return - var/run_with_mind = tgui_alert(user, "Run AI controller while the target has a client?", "Override Client?", list("Yes", "No")) - if (isnull(run_with_mind)) - return - if (QDELETED(my_guy)) - to_chat(user, span_warning("Target ceased to exist.")) - return - my_guy.ai_controller = new /datum/ai_controller/basic_controller/talk(my_guy) - if (run_with_mind == "Yes") - var/datum/ai_controller/guy_controller = my_guy.ai_controller - guy_controller.continue_processing_when_client = TRUE - guy_controller.reset_ai_status() - - var/speech_chance - var/list/spoken_lines - var/list/audible_emotes - var/list/visible_emotes - var/list/sounds - - speech_chance = tgui_input_number(user, "Enter chance per second to say something", "Speech Chance", default = 2, min_value = 0, max_value = 100, round_value = FALSE) - if (isnull(speech_chance)) - return - - var/add_another - var/next_line - - add_another = tgui_alert(user, "Add [length(spoken_lines) ? "another" : "a"] spoken line?", "Spoken Lines", list("Yes", "No")) - while (add_another == "Yes") - next_line = tgui_input_text(user, "Enter [length(spoken_lines) ? "another" : "a"] thing spoken out loud.", "Spoken Lines") - if (isnull(next_line)) - return - LAZYADD(spoken_lines, next_line) - add_another = tgui_alert(user, "Add [length(spoken_lines) ? "another" : "a"] spoken line?", "Spoken Lines", list("Yes", "No")) - if (isnull(add_another)) - return - - add_another = tgui_alert(user, "Add [length(spoken_lines) ? "another" : "a"] emote which people can hear?", "Audible Emotes", list("Yes", "No")) - while (add_another == "Yes") - next_line = tgui_input_text(user, "Enter [length(spoken_lines) ? "another" : "an"] emote which people can hear.", "Audible Emotes") - if (isnull(next_line)) - return - LAZYADD(audible_emotes, next_line) - add_another = tgui_alert(user, "Add [length(spoken_lines) ? "another" : "a"] emote which people can hear?", "Audible Emotes", list("Yes", "No")) - if (isnull(add_another)) - return - - add_another = tgui_alert(user, "Add [length(spoken_lines) ? "another" : "a"] emote which people can see?", "Visible Emotes", list("Yes", "No")) - while (add_another == "Yes") - next_line = tgui_input_text(user, "Enter [length(spoken_lines) ? "another" : "an"] emote which people can see.", "Visible Emotes") - if (isnull(next_line)) - return - LAZYADD(visible_emotes, next_line) - add_another = tgui_alert(user, "Add [length(spoken_lines) ? "another" : "a"] emote which people can see?", "Visible Emotes", list("Yes", "No")) - if (isnull(add_another)) - return - - if (!length(spoken_lines) && !length(audible_emotes) && !length(visible_emotes)) - return // Well you didn't tell it to say anything... - - if (length(spoken_lines) || length(audible_emotes)) - add_another = tgui_alert(user, "Add [length(spoken_lines) ? "another" : "a"] sound to play when doing something audible?", "Sounds", list("Yes", "No")) - while (add_another == "Yes") - next_line = input("", "Select sound",) as null|sound - if (isnull(next_line)) - return - LAZYADD(sounds, next_line) - add_another = tgui_alert(user, "Add [length(spoken_lines) ? "another" : "a"] sound to play when doing something audible?", "Sounds", list("Yes", "No")) - if (isnull(add_another)) - return - - if (QDELETED(my_guy)) - to_chat(user, span_warning("Target stopped existing.")) - return - - var/datum/ai_controller/our_controller = my_guy.ai_controller - if (length(spoken_lines)) - spoken_lines = string_list(spoken_lines) - if (length(audible_emotes)) - audible_emotes = string_list(audible_emotes) - if (length(visible_emotes)) - visible_emotes = string_list(visible_emotes) - - var/list/emotes = list( - BB_EMOTE_SAY = spoken_lines, - BB_EMOTE_HEAR = audible_emotes, - BB_EMOTE_SEE = visible_emotes, - BB_EMOTE_SOUND = sounds, - BB_SPEAK_CHANCE = speech_chance, - ) - our_controller.set_blackboard_key(BB_BASIC_MOB_SPEAK_LINES, emotes) - - var/behaviour_exists = !!(locate(/datum/ai_planning_subtree/random_speech/blackboard) in our_controller.planning_subtrees) - if (behaviour_exists) - return - our_controller.planning_subtrees = list(GLOB.ai_subtrees[/datum/ai_planning_subtree/random_speech/blackboard]) + our_controller.planning_subtrees - ADMIN_VERB(open_event_logger, R_DEBUG, "Open Event Logger", "Open the event logger interface.", ADMIN_CATEGORY_DEBUG) GLOB.event_logger.ui_interact(user.mob) +ADMIN_VERB(view_behavior_tree, R_DEBUG, "View Behavior Tree", "Inspect the AI behavior tree of a mob.", ADMIN_CATEGORY_DEBUG) + GLOB.bt_viewer.ui_interact(user.mob) + ADMIN_VERB(new_blackmarket_item, R_BUILD, "Create Black Market Item", "Add an item to the black market for purchase.", ADMIN_CATEGORY_EVENTS, object as text) if(!object) to_chat(user, span_boldwarning("Failed! Provide a full or partial typepath!")) diff --git a/code/modules/antagonists/blob/powers.dm b/code/modules/antagonists/blob/powers.dm index f5a30224eba..34534ffeca0 100644 --- a/code/modules/antagonists/blob/powers.dm +++ b/code/modules/antagonists/blob/powers.dm @@ -353,7 +353,7 @@ for(var/mob/living/basic/blob_mob as anything in blob_mobs) if(!isturf(blob_mob.loc) || get_dist(blob_mob, tile) > 35 || blob_mob.key) continue - blob_mob.ai_controller.clear_blackboard_key(BB_BASIC_MOB_CURRENT_TARGET) + blob_mob.ai_controller.clear_blackboard_key(BB_CURRENT_TARGET) blob_mob.ai_controller.set_blackboard_key(BB_TRAVEL_DESTINATION, pick(surrounding_turfs)) /** Opens the reroll menu to change strains */ diff --git a/code/modules/antagonists/heretic/heretic_knowledge.dm b/code/modules/antagonists/heretic/heretic_knowledge.dm index 21eb8af8106..28b3798b745 100644 --- a/code/modules/antagonists/heretic/heretic_knowledge.dm +++ b/code/modules/antagonists/heretic/heretic_knowledge.dm @@ -434,7 +434,7 @@ summoned = mob_to_summon else summoned = new mob_to_summon(loc) - summoned.ai_controller?.set_ai_status(AI_STATUS_OFF) + summoned.ai_controller?.force_ai_off() // Fade in the summon while the ghost poll is ongoing. // Also don't let them mess with the summon while waiting summoned.alpha = 0 @@ -457,6 +457,7 @@ summoned.ghostize(FALSE) summoned.PossessByPlayer(chosen_one.key) + summoned.ai_controller?.clear_forced_off() //the client keeps the AI off from here; if they disconnect the AI may take back over user.log_message("created a [summoned.name], controlled by [key_name(chosen_one)].", LOG_GAME) message_admins("[ADMIN_LOOKUPFLW(user)] created a [summoned.name], [ADMIN_LOOKUPFLW(summoned)].") diff --git a/code/modules/bitrunning/antagonists/netguardian.bt.json b/code/modules/bitrunning/antagonists/netguardian.bt.json new file mode 100644 index 00000000000..794640d5a97 --- /dev/null +++ b/code/modules/bitrunning/antagonists/netguardian.bt.json @@ -0,0 +1,110 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/netguardian", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability", + "vars": { + "ability_key": "BB_NETGUARDIAN_ROCKET_ABILITY", + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_ranged_attack/avoid_friendly_fire", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION", + "time_between_perform": "1 SECONDS" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/attack_obstructions", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets/most_wounded", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] +} diff --git a/code/modules/bitrunning/antagonists/netguardian.dm b/code/modules/bitrunning/antagonists/netguardian.dm index 80bba1c050a..d05d249733f 100644 --- a/code/modules/bitrunning/antagonists/netguardian.dm +++ b/code/modules/bitrunning/antagonists/netguardian.dm @@ -113,29 +113,10 @@ return TRUE /datum/ai_controller/basic_controller/netguardian + behavior_tree_json = "code/modules/bitrunning/antagonists/netguardian.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/target_retaliate/check_faction, - /datum/ai_planning_subtree/simple_find_wounded_target, - /datum/ai_planning_subtree/targeted_mob_ability/fire_rockets, - /datum/ai_planning_subtree/basic_ranged_attack_subtree/netguardian, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) -/datum/ai_planning_subtree/basic_ranged_attack_subtree/netguardian - ranged_attack_behavior = /datum/ai_behavior/basic_ranged_attack/netguardian - -/datum/ai_behavior/basic_ranged_attack/netguardian - action_cooldown = 1 SECONDS - avoid_friendly_fire = TRUE - -/datum/ai_planning_subtree/targeted_mob_ability/fire_rockets - ability_key = BB_NETGUARDIAN_ROCKET_ABILITY - finish_planning = FALSE diff --git a/code/modules/bitrunning/virtual_domain/domains/crewman.bt.json b/code/modules/bitrunning/virtual_domain/domains/crewman.bt.json new file mode 100644 index 00000000000..7fd6dd5e23b --- /dev/null +++ b/code/modules/bitrunning/virtual_domain/domains/crewman.bt.json @@ -0,0 +1,118 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/fake_crewman", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_equals", + "vars": { + "key": "BB_CALLS_REINFORCEMENTS", + "value": true + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/cooldown", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "cooldown_key": "BB_BASIC_MOB_REINFORCEMENTS_COOLDOWN", + "cooldown_duration": "30 SECONDS", + "lock_on_succeed": true + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/call_reinforcements" + } + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/attack_obstructions", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/move_to_reinforce" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] +} diff --git a/code/modules/bitrunning/virtual_domain/domains/crewman_hostile.bt.json b/code/modules/bitrunning/virtual_domain/domains/crewman_hostile.bt.json new file mode 100644 index 00000000000..29c836e25cd --- /dev/null +++ b/code/modules/bitrunning/virtual_domain/domains/crewman_hostile.bt.json @@ -0,0 +1,118 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/fake_crewman/instant_hostile", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_equals", + "vars": { + "key": "BB_CALLS_REINFORCEMENTS", + "value": true + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/cooldown", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "cooldown_key": "BB_BASIC_MOB_REINFORCEMENTS_COOLDOWN", + "cooldown_duration": "30 SECONDS", + "lock_on_succeed": true + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/call_reinforcements" + } + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/attack_obstructions", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/move_to_reinforce" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] +} diff --git a/code/modules/bitrunning/virtual_domain/domains/crewman_hostile_ranged.bt.json b/code/modules/bitrunning/virtual_domain/domains/crewman_hostile_ranged.bt.json new file mode 100644 index 00000000000..d50e13f3197 --- /dev/null +++ b/code/modules/bitrunning/virtual_domain/domains/crewman_hostile_ranged.bt.json @@ -0,0 +1,129 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/fake_crewman/instant_hostile/ranged", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_equals", + "vars": { + "key": "BB_CALLS_REINFORCEMENTS", + "value": true + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/cooldown", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "cooldown_key": "BB_BASIC_MOB_REINFORCEMENTS_COOLDOWN", + "cooldown_duration": "30 SECONDS", + "lock_on_succeed": true + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/call_reinforcements" + } + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/attack_obstructions", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_ranged_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/succeed" + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/maintain_distance", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/move_to_reinforce" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] +} diff --git a/code/modules/bitrunning/virtual_domain/domains/crewman_ranged.bt.json b/code/modules/bitrunning/virtual_domain/domains/crewman_ranged.bt.json new file mode 100644 index 00000000000..beaf56a6d78 --- /dev/null +++ b/code/modules/bitrunning/virtual_domain/domains/crewman_ranged.bt.json @@ -0,0 +1,129 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/fake_crewman/ranged", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_equals", + "vars": { + "key": "BB_CALLS_REINFORCEMENTS", + "value": true + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/cooldown", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "cooldown_key": "BB_BASIC_MOB_REINFORCEMENTS_COOLDOWN", + "cooldown_duration": "30 SECONDS", + "lock_on_succeed": true + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/call_reinforcements" + } + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/attack_obstructions", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_ranged_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/succeed" + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/maintain_distance", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/move_to_reinforce" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] +} diff --git a/code/modules/bitrunning/virtual_domain/domains/heretic_hunt.dm b/code/modules/bitrunning/virtual_domain/domains/heretic_hunt.dm index 77a4fcfed60..3dee62028c2 100644 --- a/code/modules/bitrunning/virtual_domain/domains/heretic_hunt.dm +++ b/code/modules/bitrunning/virtual_domain/domains/heretic_hunt.dm @@ -78,54 +78,26 @@ user.AddElement(/datum/element/rust_healing) user.add_faction(FACTION_HERETIC) -// All it does is stand there, only attacks if attacked (Manuel player) +// All it does is stand there, only attacks if attacked (Manuel player) (TODO: make them ahelp to really simulate manuel players) /datum/ai_controller/basic_controller/fake_crewman + behavior_tree_json = "code/modules/bitrunning/virtual_domain/domains/crewman.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_TARGET_MINIMUM_STAT = HARD_CRIT, BB_REINFORCEMENTS_SAY = "Help me!", + BB_CALLS_REINFORCEMENTS = TRUE, ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk/less_walking - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/call_reinforcements, - /datum/ai_planning_subtree/target_retaliate, - /datum/ai_planning_subtree/attack_obstacle_in_path/trooper, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) /datum/ai_controller/basic_controller/fake_crewman/ranged - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/call_reinforcements, - /datum/ai_planning_subtree/target_retaliate, - /datum/ai_planning_subtree/maintain_distance, - /datum/ai_planning_subtree/ranged_skirmish, - /datum/ai_planning_subtree/attack_obstacle_in_path/trooper, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) + behavior_tree_json = "code/modules/bitrunning/virtual_domain/domains/crewman_ranged.bt.json" // Immediately tries to attack the player (Terry player) /datum/ai_controller/basic_controller/fake_crewman/instant_hostile - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/call_reinforcements, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/attack_obstacle_in_path/trooper, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) + behavior_tree_json = "code/modules/bitrunning/virtual_domain/domains/crewman_hostile.bt.json" /datum/ai_controller/basic_controller/fake_crewman/instant_hostile/ranged - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/call_reinforcements, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/maintain_distance, - /datum/ai_planning_subtree/ranged_skirmish, - /datum/ai_planning_subtree/attack_obstacle_in_path/trooper, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) + behavior_tree_json = "code/modules/bitrunning/virtual_domain/domains/crewman_hostile_ranged.bt.json" // The actual crewmate /mob/living/basic/fake_crewman diff --git a/code/modules/mining/equipment/kinetic_crusher/trophies_fauna.dm b/code/modules/mining/equipment/kinetic_crusher/trophies_fauna.dm index f0b0b3331f7..ee70b061193 100644 --- a/code/modules/mining/equipment/kinetic_crusher/trophies_fauna.dm +++ b/code/modules/mining/equipment/kinetic_crusher/trophies_fauna.dm @@ -314,7 +314,7 @@ var/mob/living/basic/mining/demon_afterimage/crusher/friend = new(drop_off) friend.set_faction(list(FACTION_NEUTRAL)) friend.befriend(user) - friend.ai_controller?.set_blackboard_key(BB_BASIC_MOB_CURRENT_TARGET, target) + friend.ai_controller?.set_blackboard_key(BB_CURRENT_TARGET, target) COOLDOWN_START(src, summon_cooldown, 30 SECONDS) ///try to make them spawn all around the target to surround him diff --git a/code/modules/mining/equipment/kinetic_crusher/trophies_megafauna.dm b/code/modules/mining/equipment/kinetic_crusher/trophies_megafauna.dm index e91b2b07f51..6edec34d3d9 100644 --- a/code/modules/mining/equipment/kinetic_crusher/trophies_megafauna.dm +++ b/code/modules/mining/equipment/kinetic_crusher/trophies_megafauna.dm @@ -244,7 +244,7 @@ return var/mob/living/basic/mining/legion_brood/minion = new (user.loc) minion.assign_creator(user) - minion.ai_controller.set_blackboard_key(BB_BASIC_MOB_CURRENT_TARGET, target) + minion.ai_controller.set_blackboard_key(BB_CURRENT_TARGET, target) /obj/item/crusher_trophy/legionnaire_spine/attack_self(mob/user) if(!isliving(user)) diff --git a/code/modules/mob/inventory.dm b/code/modules/mob/inventory.dm index b78c0e24561..24e9690ae2d 100644 --- a/code/modules/mob/inventory.dm +++ b/code/modules/mob/inventory.dm @@ -194,6 +194,7 @@ if(QDELETED(I)) // this is here because some ABSTRACT items like slappers and circle hands could be moved from hand to hand then delete, which meant you'd have a null in your hand until you cleared it (say, by dropping it) held_items[hand_index] = null return FALSE + SEND_SIGNAL(I, COMSIG_ITEM_ENTERED_HANDS, src, hand_index) return hand_index //Puts the item into the first available left hand if possible and calls all necessary triggers/updates. returns 1 on success. diff --git a/code/modules/mob/living/basic/alien/_alien.dm b/code/modules/mob/living/basic/alien/_alien.dm index af0bd349172..e76c7fec11b 100644 --- a/code/modules/mob/living/basic/alien/_alien.dm +++ b/code/modules/mob/living/basic/alien/_alien.dm @@ -67,23 +67,25 @@ /mob/living/basic/alien/get_butt_sprite() return icon('icons/mob/butts.dmi', BUTT_SPRITE_XENOMORPH) -///Places alien weeds on the turf the mob is currently standing on. +///Places alien weeds on the turf the mob is currently standing on. Returns TRUE if weeds were placed. /mob/living/basic/alien/proc/place_weeds() if(!isturf(loc) || isspaceturf(loc)) - return + return FALSE if(locate(/obj/structure/alien/weeds/node) in get_turf(src)) - return + return FALSE visible_message(span_alertalien("[src] plants some alien weeds!")) new /obj/structure/alien/weeds/node(loc) + return TRUE -///Lays an egg on the turf the mob is currently standing on. +///Lays an egg on the turf the mob is currently standing on. Returns TRUE if an egg was laid. /mob/living/basic/alien/proc/lay_alien_egg() if(!isturf(loc) || isspaceturf(loc)) - return + return FALSE if(locate(/obj/structure/alien/egg) in get_turf(src)) - return + return FALSE visible_message(span_alertalien("[src] lays an egg!")) new /obj/structure/alien/egg(loc) + return TRUE /mob/living/basic/alien/get_bloodtype() return get_blood_type(BLOOD_TYPE_XENO) diff --git a/code/modules/mob/living/basic/alien/alien.bt.json b/code/modules/mob/living/basic/alien/alien.bt.json new file mode 100644 index 00000000000..5c8713ca333 --- /dev/null +++ b/code/modules/mob/living/basic/alien/alien.bt.json @@ -0,0 +1,9 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/alien", + "type": "subtree", + "subtype": "/datum/bt_node/subtree/basic_alien", + "bindings": { + "buewuqr6": "/datum/bt_node/subtree/melee_alien_combat", + "bkzbx73d": "/datum/bt_node/subtree/lay_alien_egg" + } +} diff --git a/code/modules/mob/living/basic/alien/alien_ai.dm b/code/modules/mob/living/basic/alien/alien_ai.dm index decca14cec8..2e31eed3263 100644 --- a/code/modules/mob/living/basic/alien/alien_ai.dm +++ b/code/modules/mob/living/basic/alien/alien_ai.dm @@ -1,76 +1,57 @@ /datum/ai_controller/basic_controller/alien + behavior_tree_json = "code/modules/mob/living/basic/alien/alien.bt.json" ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, - ) - - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/basic_melee_attack_subtree, + BB_RANGED_SKIRMISH_MIN_DISTANCE = 2, + BB_RANGED_SKIRMISH_MAX_DISTANCE = 3, ) movement_delay = 0.8 SECONDS /datum/ai_controller/basic_controller/alien/sentinel - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/basic_ranged_attack_subtree/alien, - ) + behavior_tree_json = "code/modules/mob/living/basic/alien/sentinel.bt.json" + /datum/ai_controller/basic_controller/alien/drone - idle_behavior = /datum/idle_behavior/idle_random_walk/plant_weeds + behavior_tree_json = "code/modules/mob/living/basic/alien/drone.bt.json" /datum/ai_controller/basic_controller/alien/queen - idle_behavior = /datum/idle_behavior/idle_random_walk/plant_weeds/queen + behavior_tree_json = "code/modules/mob/living/basic/alien/queen.bt.json" - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/basic_ranged_attack_subtree/alien, - ) -/** - * Alien projectile - * Try to avoid friendly fire, and has a 3 second delay. - */ -/datum/ai_planning_subtree/basic_ranged_attack_subtree/alien - ranged_attack_behavior = /datum/ai_behavior/basic_ranged_attack/alien +/datum/bt_node/subtree/basic_alien + behavior_tree_json = "code/modules/mob/living/basic/alien/basic_alien.bt.json" -/datum/ai_behavior/basic_ranged_attack/alien - action_cooldown = 3 SECONDS - required_distance = 3 - avoid_friendly_fire = TRUE -/datum/idle_behavior/idle_random_walk/plant_weeds - var/plant_cooldown = 30 - var/plants_off = 0 +/datum/bt_node/subtree/ranged_alien_combat + behavior_tree_json = "code/modules/mob/living/basic/alien/ranged_alien_combat.bt.json" -/datum/idle_behavior/idle_random_walk/plant_weeds/perform_idle_behavior(seconds_per_tick, datum/ai_controller/controller) - . = ..() - if(!.) - return . - plant_cooldown-- + +/datum/bt_node/subtree/melee_alien_combat + behavior_tree_json = "code/modules/mob/living/basic/alien/melee_alien_combat.bt.json" + +/datum/bt_node/subtree/plant_alien_weeds + behavior_tree_json = "code/modules/mob/living/basic/alien/plant_alien_weeds.bt.json" + +/datum/bt_node/subtree/lay_alien_egg + behavior_tree_json = "code/modules/mob/living/basic/alien/lay_alien_egg.bt.json" + + +/// Plants alien weeds on the pawn's current turf. Fails if the pawn can't plant or weeds couldn't be placed. +/datum/bt_node/ai_behavior/plant_alien_weeds + +/datum/bt_node/ai_behavior/plant_alien_weeds/perform(seconds_per_tick, datum/ai_controller/controller) var/mob/living/basic/alien/alien_pawn = controller.pawn - if(alien_pawn.can_plant_weeds && !plants_off && plant_cooldown <= 0) - plant_cooldown = initial(plant_cooldown) - alien_pawn.place_weeds() + if(!alien_pawn.can_plant_weeds || !alien_pawn.place_weeds()) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED -/datum/idle_behavior/idle_random_walk/plant_weeds/queen - var/eggs_off = 0 - var/egg_cooldown = 30 +/// Lays an alien egg on the pawn's current turf. Fails if the pawn can't lay eggs or an egg couldn't be placed. +/datum/bt_node/ai_behavior/lay_alien_egg -/datum/idle_behavior/idle_random_walk/plant_weeds/queen/perform_idle_behavior(seconds_per_tick, datum/ai_controller/controller) - . = ..() - if(!.) - return . - egg_cooldown-- +/datum/bt_node/ai_behavior/lay_alien_egg/perform(seconds_per_tick, datum/ai_controller/controller) var/mob/living/basic/alien/alien_pawn = controller.pawn - if(alien_pawn.can_lay_eggs && !eggs_off && egg_cooldown <= 0) - egg_cooldown = initial(egg_cooldown) - alien_pawn.lay_alien_egg() + if(!alien_pawn.can_lay_eggs || !alien_pawn.lay_alien_egg()) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED diff --git a/code/modules/mob/living/basic/alien/basic_alien.bt.json b/code/modules/mob/living/basic/alien/basic_alien.bt.json new file mode 100644 index 00000000000..c9163e6f775 --- /dev/null +++ b/code/modules/mob/living/basic/alien/basic_alien.bt.json @@ -0,0 +1,60 @@ +{ + "dm_type": "/datum/bt_node/subtree/basic_alien", + "bindings": { + "buewuqr6": { + "label": "Combat Subtree", + "default": "/datum/bt_node/subtree" + }, + "bkzbx73d": { + "label": "Idle Behavior", + "default": "/datum/bt_node/subtree/random_walk" + } + }, + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "subtree", + "subtype": "$buewuqr6" + } + }, + { + "type": "subtree", + "subtype": "$bkzbx73d" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/alien/drone.bt.json b/code/modules/mob/living/basic/alien/drone.bt.json new file mode 100644 index 00000000000..7763e2fcd9b --- /dev/null +++ b/code/modules/mob/living/basic/alien/drone.bt.json @@ -0,0 +1,9 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/alien/drone", + "type": "subtree", + "subtype": "/datum/bt_node/subtree/basic_alien", + "bindings": { + "buewuqr6": "/datum/bt_node/subtree/melee_alien_combat", + "bkzbx73d": "/datum/bt_node/ai_behavior/plant_alien_weeds" + } +} diff --git a/code/modules/mob/living/basic/alien/lay_alien_egg.bt.json b/code/modules/mob/living/basic/alien/lay_alien_egg.bt.json new file mode 100644 index 00000000000..1cb4f5336b0 --- /dev/null +++ b/code/modules/mob/living/basic/alien/lay_alien_egg.bt.json @@ -0,0 +1,14 @@ +{ + "dm_type": "/datum/bt_node/subtree/lay_alien_egg", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/cooldown", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "cooldown_key": "BB_ALIEN_PLANT_COOLDOWN", + "cooldown_duration": "30 SECONDS" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/lay_alien_egg" + } +} diff --git a/code/modules/mob/living/basic/alien/melee_alien_combat.bt.json b/code/modules/mob/living/basic/alien/melee_alien_combat.bt.json new file mode 100644 index 00000000000..51a64624442 --- /dev/null +++ b/code/modules/mob/living/basic/alien/melee_alien_combat.bt.json @@ -0,0 +1,47 @@ +{ + "dm_type": "/datum/bt_node/subtree/melee_alien_combat", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/attack_obstructions", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] +} diff --git a/code/modules/mob/living/basic/alien/plant_alien_weeds.bt.json b/code/modules/mob/living/basic/alien/plant_alien_weeds.bt.json new file mode 100644 index 00000000000..a8bb21032ce --- /dev/null +++ b/code/modules/mob/living/basic/alien/plant_alien_weeds.bt.json @@ -0,0 +1,23 @@ +{ + "dm_type": "/datum/bt_node/subtree/plant_alien_weeds", + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/cooldown", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "cooldown_key": "BB_ALIEN_PLANT_COOLDOWN", + "cooldown_duration": "30 SECONDS" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/plant_alien_weeds" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/idle_random_walk" + } + ] +} diff --git a/code/modules/mob/living/basic/alien/queen.bt.json b/code/modules/mob/living/basic/alien/queen.bt.json new file mode 100644 index 00000000000..d4570cee74f --- /dev/null +++ b/code/modules/mob/living/basic/alien/queen.bt.json @@ -0,0 +1,9 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/alien/queen", + "type": "subtree", + "subtype": "/datum/bt_node/subtree/basic_alien", + "bindings": { + "buewuqr6": "/datum/bt_node/subtree/ranged_alien_combat", + "bkzbx73d": "/datum/bt_node/ai_behavior/lay_alien_egg" + } +} diff --git a/code/modules/mob/living/basic/alien/ranged_alien_combat.bt.json b/code/modules/mob/living/basic/alien/ranged_alien_combat.bt.json new file mode 100644 index 00000000000..47f467ed942 --- /dev/null +++ b/code/modules/mob/living/basic/alien/ranged_alien_combat.bt.json @@ -0,0 +1,48 @@ +{ + "dm_type": "/datum/bt_node/subtree/ranged_alien_combat", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/attack_obstructions", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_ranged_attack", + "vars": { + "time_between_perform": "3 SECONDS", + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION", + "avoid_friendly_fire": true + } + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/maintain_distance", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "approach_movement_type": "/datum/ai_movement/basic_avoidance" + } + } + ] +} diff --git a/code/modules/mob/living/basic/alien/sentinel.bt.json b/code/modules/mob/living/basic/alien/sentinel.bt.json new file mode 100644 index 00000000000..a0e73fda01a --- /dev/null +++ b/code/modules/mob/living/basic/alien/sentinel.bt.json @@ -0,0 +1,8 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/alien/sentinel", + "type": "subtree", + "subtype": "/datum/bt_node/subtree/basic_alien", + "bindings": { + "buewuqr6": "/datum/bt_node/subtree/ranged_alien_combat" + } +} diff --git a/code/modules/mob/living/basic/blob_minions/blob_ai.dm b/code/modules/mob/living/basic/blob_minions/blob_ai.dm index b8368fc7f3c..208d38dee9b 100644 --- a/code/modules/mob/living/basic/blob_minions/blob_ai.dm +++ b/code/modules/mob/living/basic/blob_minions/blob_ai.dm @@ -3,55 +3,34 @@ * Only notable quirk is that it uses JPS movement, simple avoidance would fail to realise it can path through blobs */ /datum/ai_controller/basic_controller/blobbernaut + behavior_tree_json = "code/modules/mob/living/basic/blob_minions/blobbernaut.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_TARGET_MINIMUM_STAT = HARD_CRIT, ) ai_movement = /datum/ai_movement/jps - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) /** * Move to a point designated by the overmind, otherwise just slap people nearby */ /datum/ai_controller/basic_controller/blob_zombie + behavior_tree_json = "code/modules/mob/living/basic/blob_minions/blob_zombie.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_TARGET_MINIMUM_STAT = HARD_CRIT, ) ai_movement = /datum/ai_movement/jps - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/travel_to_point/and_clear_target, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) /** * As blob zombie but will prioritise attacking corpses to zombify them */ /datum/ai_controller/basic_controller/blob_spore + behavior_tree_json = "code/modules/mob/living/basic/blob_minions/blob_spore.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_TARGET_MINIMUM_STAT = HARD_CRIT, ) ai_movement = /datum/ai_movement/jps - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/find_and_hunt_target/corpses/human, - /datum/ai_planning_subtree/travel_to_point/and_clear_target, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) diff --git a/code/modules/mob/living/basic/blob_minions/blob_spore.bt.json b/code/modules/mob/living/basic/blob_minions/blob_spore.bt.json new file mode 100644 index 00000000000..17d02701525 --- /dev/null +++ b/code/modules/mob/living/basic/blob_minions/blob_spore.bt.json @@ -0,0 +1,69 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/blob_spore", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/move_to_and_hunt", + "bindings": { + "bvtz06kb": "BB_CURRENT_HUNTING_TARGET", + "b3y599q4": "BB_CURRENT_HUNTING_TARGET", + "brrasnah": "BB_CURRENT_HUNTING_TARGET" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_TRAVEL_DESTINATION" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_TRAVEL_DESTINATION", + "required_dist": 0 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/clear_key", + "vars": { + "key": "BB_TRAVEL_DESTINATION" + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_hostile_obstacles_combat" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_CURRENT_HUNTING_TARGET", + "targeting_strategy": "/datum/targeting_strategy/dead_mob", + "target_source": "/datum/target_source/oview_single_type/human_mob" + } + } + ] +} diff --git a/code/modules/mob/living/basic/blob_minions/blob_zombie.bt.json b/code/modules/mob/living/basic/blob_minions/blob_zombie.bt.json new file mode 100644 index 00000000000..7d2cf1b5b58 --- /dev/null +++ b/code/modules/mob/living/basic/blob_minions/blob_zombie.bt.json @@ -0,0 +1,42 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/blob_zombie", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_TRAVEL_DESTINATION" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_TRAVEL_DESTINATION", + "required_dist": 0 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/clear_key", + "vars": { + "key": "BB_TRAVEL_DESTINATION" + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_hostile_obstacles_combat" + } + ] +} diff --git a/code/modules/mob/living/basic/blob_minions/blobbernaut.bt.json b/code/modules/mob/living/basic/blob_minions/blobbernaut.bt.json new file mode 100644 index 00000000000..89e67315111 --- /dev/null +++ b/code/modules/mob/living/basic/blob_minions/blobbernaut.bt.json @@ -0,0 +1,14 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/blobbernaut", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_hostile_obstacles_combat" + } + ] +} diff --git a/code/modules/mob/living/basic/boss/blood_drunk_miner/_blood_drunk_miner.dm b/code/modules/mob/living/basic/boss/blood_drunk_miner/_blood_drunk_miner.dm index a251db94200..682d24646b5 100644 --- a/code/modules/mob/living/basic/boss/blood_drunk_miner/_blood_drunk_miner.dm +++ b/code/modules/mob/living/basic/boss/blood_drunk_miner/_blood_drunk_miner.dm @@ -68,11 +68,10 @@ Difficulty: Medium grant_actions_by_list(get_innate_actions()) ai_controller.set_blackboard_key(BB_BDM_RANGED_ATTACK_COOLDOWN, ranged_attack_cooldown_duration) - RegisterSignals(ai_controller, list(AI_CONTROLLER_BEHAVIOR_QUEUED(/datum/ai_behavior/basic_melee_attack), AI_CONTROLLER_BEHAVIOR_QUEUED(/datum/ai_behavior/targeted_mob_ability)), PROC_REF(handle_saw_transformation)) RegisterSignal(src, COMSIG_LIVING_DROP_LOOT, PROC_REF(death_effect)) - AddComponent(/datum/component/boss_music, 'sound/music/boss/bdm_boss.ogg', COMSIG_AI_BLACKBOARD_KEY_SET(BB_BASIC_MOB_CURRENT_TARGET)) + AddComponent(/datum/component/boss_music, 'sound/music/boss/bdm_boss.ogg', COMSIG_AI_BLACKBOARD_KEY_SET(BB_CURRENT_TARGET)) /// Block deletion of their saw under normal circumstances. It is fused to their hands as far as we're concerned. /mob/living/basic/boss/blood_drunk_miner/proc/on_saw_deleted(datum/source, force) @@ -99,12 +98,6 @@ Difficulty: Medium ) return innate_abilities -/// Invokes the transform weapon ability when signaled by the AI controller. -/mob/living/basic/boss/blood_drunk_miner/proc/handle_saw_transformation() - SIGNAL_HANDLER - - INVOKE_ASYNC(ai_controller.blackboard[BB_BDM_TRANSFORM_WEAPON_ABILITY], TYPE_PROC_REF(/datum/action, Trigger), src, NONE) - /mob/living/basic/boss/blood_drunk_miner/proc/transform_saw() miner_saw.attack_self(src) var/saw_open = HAS_TRAIT(miner_saw, TRAIT_TRANSFORM_ACTIVE) diff --git a/code/modules/mob/living/basic/boss/blood_drunk_miner/blood_drunk_ai.dm b/code/modules/mob/living/basic/boss/blood_drunk_miner/blood_drunk_ai.dm index 64efdb0b8a9..41c22c242f8 100644 --- a/code/modules/mob/living/basic/boss/blood_drunk_miner/blood_drunk_ai.dm +++ b/code/modules/mob/living/basic/boss/blood_drunk_miner/blood_drunk_ai.dm @@ -5,6 +5,7 @@ /// - If in melee range, use melee attacks (depending on saw state) /// - After attacks, transform saw state from open to closed. /datum/ai_controller/blood_drunk_miner + behavior_tree_json = "code/modules/mob/living/basic/boss/blood_drunk_miner/blood_drunk_miner.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/no_gutted_mobs, BB_TARGET_MINIMUM_STAT = DEAD, @@ -14,53 +15,10 @@ movement_delay = 0.25 SECONDS ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/targeted_mob_ability/blood_drunk/shoot_pka, - /datum/ai_planning_subtree/targeted_mob_ability/blood_drunk/dash_attack, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) - -/// Parent type that contains key logic important for subsequent abilities -/datum/ai_planning_subtree/targeted_mob_ability/blood_drunk - /// Range where we determine what distance we're at. If higher, we consider ourselves out of PKA range and will dash attack instead. Inclusive when it comes to choosing to shoot PKA. - var/pka_range = 3 - -/// Check our blackboard to see if we are able to use a ranged ability in the first place -/datum/ai_planning_subtree/targeted_mob_ability/blood_drunk/additional_ability_checks(datum/ai_controller/controller, datum/action/cooldown/using_action) - . = ..() - if(controller.blackboard[BB_BDM_RANGED_ATTACK_COOLDOWN] > world.time) - return FALSE - controller.override_blackboard_key(BB_BDM_RANGED_ATTACK_COOLDOWN, world.time + controller.blackboard[BB_BDM_RANGED_ATTACK_COOLDOWN_DURATION]) - -/// The BDM will preferentially shoot its PKA within range over other abilities -/datum/ai_planning_subtree/targeted_mob_ability/blood_drunk/shoot_pka - ability_key = BB_BDM_KINETIC_ACCELERATOR_ABILITY - -/datum/ai_planning_subtree/targeted_mob_ability/blood_drunk/shoot_pka/additional_ability_checks(datum/ai_controller/controller, datum/action/cooldown/using_action) - . = ..() - // do not shoot the PKA if we are not in the right range - var/mob/living/pawn = controller.pawn - var/mob/living/victim = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] - if(get_dist(pawn, victim) >= pka_range) - return FALSE - return TRUE - -/// The BDM will dash attack if not in PKA range -/datum/ai_planning_subtree/targeted_mob_ability/blood_drunk/dash_attack - ability_key = BB_BDM_DASH_ATTACK_ABILITY - -/datum/ai_planning_subtree/targeted_mob_ability/blood_drunk/dash_attack/additional_ability_checks(datum/ai_controller/controller, datum/action/cooldown/using_action) - . = ..() - // only dash attack if we are out of PKA range - var/mob/living/pawn = controller.pawn - var/mob/living/victim = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] - if(get_dist(pawn, victim) < pka_range) - return FALSE - return TRUE /datum/ai_controller/blood_drunk_miner/doom movement_delay = 0.5 SECONDS + + +/datum/bt_node/subtree/blood_drunk_miner_combat + behavior_tree_json = "code/modules/mob/living/basic/boss/blood_drunk_miner/blood_drunk_miner_combat.bt.json" diff --git a/code/modules/mob/living/basic/boss/blood_drunk_miner/blood_drunk_miner.bt.json b/code/modules/mob/living/basic/boss/blood_drunk_miner/blood_drunk_miner.bt.json new file mode 100644 index 00000000000..f6c7192c71b --- /dev/null +++ b/code/modules/mob/living/basic/boss/blood_drunk_miner/blood_drunk_miner.bt.json @@ -0,0 +1,45 @@ +{ + "dm_type": "/datum/ai_controller/blood_drunk_miner", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/blood_drunk_miner_combat" + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/wait", + "vars": { + "duration": 0 + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/boss/blood_drunk_miner/blood_drunk_miner_combat.bt.json b/code/modules/mob/living/basic/boss/blood_drunk_miner/blood_drunk_miner_combat.bt.json new file mode 100644 index 00000000000..ad023b0ae2a --- /dev/null +++ b/code/modules/mob/living/basic/boss/blood_drunk_miner/blood_drunk_miner_combat.bt.json @@ -0,0 +1,132 @@ +{ + "dm_type": "/datum/bt_node/subtree/blood_drunk_miner_combat", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/attack_obstructions", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/mob_stat_at_least", + "vars": { + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability", + "vars": { + "ability_key": "BB_BDM_KINETIC_ACCELERATOR_ABILITY", + "target_key": "BB_CURRENT_TARGET", + "maximum_distance": 3 + } + }, + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability", + "vars": { + "ability_key": "BB_BDM_TRANSFORM_WEAPON_ABILITY", + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/succeed" + } + ] + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/is_at_distance", + "vars": { + "invert": false, + "target_key": "BB_CURRENT_TARGET", + "min_distance": 3 + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability", + "vars": { + "ability_key": "BB_BDM_DASH_ATTACK_ABILITY", + "target_key": "BB_CURRENT_TARGET" + } + } + }, + { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability", + "vars": { + "ability_key": "BB_BDM_TRANSFORM_WEAPON_ABILITY", + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/succeed" + } + ] + } + ] + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } +} diff --git a/code/modules/mob/living/basic/boss/boss.dm b/code/modules/mob/living/basic/boss/boss.dm index 8f4a4924800..8a5cf5e8159 100644 --- a/code/modules/mob/living/basic/boss/boss.dm +++ b/code/modules/mob/living/basic/boss/boss.dm @@ -81,7 +81,8 @@ return if(should_devour(target)) devour(target) - return BASIC_MOB_END_ATTACK_CHAIN_COOLDOWN + return BASIC_MOB_END_ATTACK_CHAIN_COOLDOWN + return /// Determines if this mob is worth devouring /mob/living/basic/boss/proc/should_devour(mob/living/victim) diff --git a/code/modules/mob/living/basic/boss/thing/thing.dm b/code/modules/mob/living/basic/boss/thing/thing.dm index 02943669ff2..a1d2167e241 100644 --- a/code/modules/mob/living/basic/boss/thing/thing.dm +++ b/code/modules/mob/living/basic/boss/thing/thing.dm @@ -69,8 +69,8 @@ if(!maploaded) return spawn_loc = loc - RegisterSignal(src, COMSIG_AI_BLACKBOARD_KEY_SET(BB_BASIC_MOB_CURRENT_TARGET), PROC_REF(target_gained)) - RegisterSignal(src, COMSIG_AI_BLACKBOARD_KEY_CLEARED(BB_BASIC_MOB_CURRENT_TARGET), PROC_REF(target_lost)) + RegisterSignal(src, COMSIG_AI_BLACKBOARD_KEY_SET(BB_CURRENT_TARGET), PROC_REF(target_gained)) + RegisterSignal(src, COMSIG_AI_BLACKBOARD_KEY_CLEARED(BB_CURRENT_TARGET), PROC_REF(target_lost)) SSqueuelinks.add_to_queue(src, RUIN_QUEUE, 0) return INITIALIZE_HINT_LATELOAD @@ -179,9 +179,9 @@ /// Immediately set out blackboard target key (if empty) to whoever attacks us; this is primarily because it has a lowered aggro range and a high sight range /mob/living/basic/boss/thing/proc/immediate_aggro(datum/source, mob/attacker, flags) SIGNAL_HANDLER - if(isnull(ai_controller) || stat || !istype(attacker) || ai_controller.blackboard_key_exists(BB_BASIC_MOB_CURRENT_TARGET)) + if(isnull(ai_controller) || stat || !istype(attacker) || ai_controller.blackboard_key_exists(BB_CURRENT_TARGET)) return - ai_controller?.set_blackboard_key(BB_BASIC_MOB_CURRENT_TARGET, attacker) + ai_controller?.set_blackboard_key(BB_CURRENT_TARGET, attacker) /mob/living/basic/boss/thing/vv_edit_var(vname, vval) . = ..() @@ -303,7 +303,7 @@ /// queue id var/queue_id = RUIN_QUEUE /// blackboard key for target - var/target_bb_key = BB_BASIC_MOB_CURRENT_TARGET + var/target_bb_key = BB_CURRENT_TARGET /obj/structure/aggro_gate/Initialize(mapload) . = ..() diff --git a/code/modules/mob/living/basic/boss/thing/thing_ai.dm b/code/modules/mob/living/basic/boss/thing/thing_ai.dm index e8956cac2b1..55668658db2 100644 --- a/code/modules/mob/living/basic/boss/thing/thing_ai.dm +++ b/code/modules/mob/living/basic/boss/thing/thing_ai.dm @@ -1,72 +1,17 @@ /datum/ai_controller/basic_controller/thing_boss + behavior_tree_json = "code/modules/mob/living/basic/boss/thing/thing_boss.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/no_gutted_mobs, BB_TARGET_MINIMUM_STAT = DEAD, // Will attack dead ungutted mobs - BB_THETHING_ATTACKMODE = TRUE, //Whether we are using our melee abilities right now + BB_THETHING_MELEEMODE = TRUE, //Whether we are using our melee abilities right now BB_THETHING_NOAOE = TRUE, // Restricts us to only melee abilities BB_THETHING_LASTAOE = null, // Last AOE ability key executed - BB_AGGRO_RANGE = 16, - BB_AGGRO_GRAB_RANGE = 6, ) ai_movement = /datum/ai_movement/basic_avoidance // dont need anything better because the arena is a square lol - idle_behavior = null - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/thing_boss_aoe, - /datum/ai_planning_subtree/thing_boss_melee, - ) -/datum/ai_planning_subtree/thing_boss_aoe/SelectBehaviors(datum/ai_controller/monkey/controller, seconds_per_tick) - var/mob/living/pawn = controller.pawn - if(HAS_TRAIT_FROM(pawn, TRAIT_IMMOBILIZED, MEGAFAUNA_TRAIT) || (controller.blackboard[BB_THETHING_ATTACKMODE] || controller.blackboard[BB_THETHING_NOAOE])) - return - // our target - var/mob/living/shaft_miner = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] - if(QDELETED(shaft_miner) || shaft_miner.stat == DEAD) //Dont use abilities on off z level targets, or dead shaft miners. We want to melee those. - return +/datum/bt_node/subtree/thing_aoe + behavior_tree_json = "code/modules/mob/living/basic/boss/thing/thing_aoe.bt.json" - controller.set_blackboard_key(BB_THETHING_ATTACKMODE, TRUE) // putting this here so we go to melee mode if we cant do any aoe - var/static/list/aoe_attacks = list(BB_THETHING_DECIMATE, BB_THETHING_BIGTENDRILS, BB_THETHING_CARDTENDRILS, BB_THETHING_ACIDSPIT) - var/list/possible_attacks = aoe_attacks.Copy() - controller.blackboard[BB_THETHING_LASTAOE] - for(var/bb_action_key in possible_attacks) - var/datum/action/action = controller.blackboard[bb_action_key] - if(!action?.IsAvailable()) - possible_attacks -= bb_action_key - if(!length(possible_attacks)) - return - var/current_aoe_key = pick(possible_attacks) - controller.set_blackboard_key(BB_THETHING_LASTAOE, current_aoe_key) - controller.queue_behavior(/datum/ai_behavior/targeted_mob_ability, current_aoe_key, BB_BASIC_MOB_CURRENT_TARGET) - if(prob(60) && shaft_miner.body_position != LYING_DOWN) //potential follow-up - controller.queue_behavior(/datum/ai_behavior/targeted_mob_ability, BB_THETHING_CHARGE, BB_BASIC_MOB_CURRENT_TARGET) - return SUBTREE_RETURN_FINISH_PLANNING - - -/datum/ai_planning_subtree/thing_boss_melee/SelectBehaviors(datum/ai_controller/monkey/controller, seconds_per_tick) - var/mob/living/pawn = controller.pawn - if(HAS_TRAIT_FROM(pawn, TRAIT_IMMOBILIZED, MEGAFAUNA_TRAIT)) - return - - // our target - var/mob/living/shaft_miner = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] - if(QDELETED(shaft_miner)) - return - var/target_dist = get_dist(pawn, shaft_miner) - - var/datum/action/shriek = controller.blackboard[BB_THETHING_SHRIEK] - var/datum/action/charge = controller.blackboard[BB_THETHING_CHARGE] - if(isnull(shriek) || isnull(charge)) - return // pray this never occurs - - controller.set_blackboard_key(BB_THETHING_ATTACKMODE, FALSE) - - if(shriek.IsAvailable() && target_dist <= 2 && shaft_miner.stat != DEAD) - controller.queue_behavior(/datum/ai_behavior/targeted_mob_ability/min_range/short, BB_THETHING_SHRIEK, BB_BASIC_MOB_CURRENT_TARGET) - return - else if(charge.IsAvailable() && target_dist >= 5) // While we cant hit prone targets, this helps to close the distance. - controller.queue_behavior(/datum/ai_behavior/targeted_mob_ability, BB_THETHING_CHARGE, BB_BASIC_MOB_CURRENT_TARGET) - return - - controller.queue_behavior(/datum/ai_behavior/basic_melee_attack, BB_BASIC_MOB_CURRENT_TARGET, BB_TARGETING_STRATEGY, BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION) +/datum/bt_node/subtree/thing_melee + behavior_tree_json = "code/modules/mob/living/basic/boss/thing/thing_melee.bt.json" diff --git a/code/modules/mob/living/basic/boss/thing/thing_aoe.bt.json b/code/modules/mob/living/basic/boss/thing/thing_aoe.bt.json new file mode 100644 index 00000000000..43fdef79c1a --- /dev/null +++ b/code/modules/mob/living/basic/boss/thing/thing_aoe.bt.json @@ -0,0 +1,72 @@ +{ + "dm_type": "/datum/bt_node/subtree/thing_aoe", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_true", + "vars": { + "invert": true, + "key": "BB_THETHING_NOAOE" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_true", + "vars": { + "invert": true, + "key": "BB_THETHING_MELEEMODE" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/pawn_has_trait_from", + "vars": { + "observer_abort": "BT_ABORT_SELF", + "invert": true, + "trait": "TRAIT_IMMOBILIZED", + "source": "MEGAFAUNA_TRAIT" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/set_bb_key", + "vars": { + "target_key": "BB_THETHING_MELEEMODE", + "value": true + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/pick_random_ability", + "vars": { + "ability_keys": "list(BB_THETHING_DECIMATE, BB_THETHING_BIGTENDRILS, BB_THETHING_CARDTENDRILS, BB_THETHING_ACIDSPIT)", + "last_used_key": "BB_THETHING_LASTAOE", + "result_key": "BB_GENERIC_ACTION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability", + "vars": { + "ability_key": "BB_GENERIC_ACTION", + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/random_chance", + "vars": { + "chance": 0.6 + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability", + "vars": { + "ability_key": "BB_THETHING_CHARGE", + "target_key": "BB_CURRENT_TARGET" + } + } + } + ] + } + } + } +} diff --git a/code/modules/mob/living/basic/boss/thing/thing_boss.bt.json b/code/modules/mob/living/basic/boss/thing/thing_boss.bt.json new file mode 100644 index 00000000000..e681d751851 --- /dev/null +++ b/code/modules/mob/living/basic/boss/thing/thing_boss.bt.json @@ -0,0 +1,70 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/thing_boss", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/thing_aoe" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/thing_melee" + } + ] + } + ] + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/wait", + "vars": { + "duration": 0 + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION", + "vision_range": 6 + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/boss/thing/thing_melee.bt.json b/code/modules/mob/living/basic/boss/thing/thing_melee.bt.json new file mode 100644 index 00000000000..96abd197f26 --- /dev/null +++ b/code/modules/mob/living/basic/boss/thing/thing_melee.bt.json @@ -0,0 +1,89 @@ +{ + "dm_type": "/datum/bt_node/subtree/thing_melee", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/true_for_time", + "vars": { + "observer_abort": "BT_ABORT_SELF", + "duration": "5 SECONDS" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/set_bb_key", + "vars": { + "target_key": "BB_THETHING_MELEEMODE", + "value": false + } + }, + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability", + "vars": { + "ability_key": "BB_THETHING_SHRIEK", + "target_key": "BB_CURRENT_TARGET", + "maximum_distance": 2 + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/is_at_distance", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "min_distance": 3 + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability", + "vars": { + "ability_key": "BB_THETHING_CHARGE", + "target_key": "BB_CURRENT_TARGET" + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/pawn_has_trait_from", + "vars": { + "observer_abort": "BT_ABORT_SELF", + "invert": true, + "trait": "TRAIT_IMMOBILIZED", + "source": "MEGAFAUNA_TRAIT" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + } + ] + } + ] + } +} diff --git a/code/modules/mob/living/basic/bots/bot.bt.json b/code/modules/mob/living/basic/bots/bot.bt.json new file mode 100644 index 00000000000..b1cc876173b --- /dev/null +++ b/code/modules/mob/living/basic/bots/bot.bt.json @@ -0,0 +1,22 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/bot", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity/pacifist" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/bot_respond_to_summon" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/bot_salute_authority" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/bot_patrol" + } + ] +} diff --git a/code/modules/mob/living/basic/bots/bot_ai.dm b/code/modules/mob/living/basic/bots/bot_ai.dm index a5f8202e66e..42ff9f2715d 100644 --- a/code/modules/mob/living/basic/bots/bot_ai.dm +++ b/code/modules/mob/living/basic/bots/bot_ai.dm @@ -1,6 +1,5 @@ -#define BOT_NO_BEACON_PATH_PENALTY 30 SECONDS - /datum/ai_controller/basic_controller/bot + behavior_tree_json = "code/modules/mob/living/basic/bots/bot.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_SALUTE_MESSAGES = list( @@ -11,24 +10,16 @@ ) ai_movement = /datum/ai_movement/jps/bot - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity/pacifist, - /datum/ai_planning_subtree/respond_to_summon, - /datum/ai_planning_subtree/salute_authority, - /datum/ai_planning_subtree/find_patrol_beacon, - ) max_target_distance = AI_BOT_PATH_LENGTH - can_idle = FALSE - ///minimum distance we need to be from our target in path calculations - var/minimum_distance = 0 - ///keys to be reset when the bot is reseted + ai_traits = DEFAULT_AI_FLAGS | RUN_WHILE_UNWATCHED + ///keys to be reset when the bot is reset var/list/reset_keys = list( BB_BEACON_TARGET, BB_PREVIOUS_BEACON_TARGET, BB_BOT_SUMMON_TARGET, ) -/datum/targeting_strategy/basic/bot/can_attack(mob/living/living_mob, atom/the_target, vision_range) +/datum/targeting_strategy/basic/bot/is_valid_target(mob/living/living_mob, atom/the_target, vision_range, datum/ai_controller/controller = null) var/datum/ai_controller/basic_controller/bot/my_controller = living_mob.ai_controller if(isnull(my_controller)) return FALSE @@ -39,9 +30,8 @@ return FALSE if(get_turf(living_mob) == get_turf(living_target)) return ..() - var/list/path = get_path_to(living_mob, living_target, mintargetdist = my_controller.minimum_distance, max_distance = 10, access = my_controller.get_access()) - if(!length(path) || QDELETED(living_mob)) - my_controller?.add_to_blacklist(living_target) + if(!my_controller.can_reach_target(living_target, distance = 10)) + my_controller.add_to_blacklist(living_target) return FALSE return ..() @@ -56,7 +46,7 @@ /datum/ai_controller/basic_controller/bot/proc/on_movement_start(mob/living/basic/bot/source, atom/target) SIGNAL_HANDLER - if(current_movement_target == blackboard[BB_BEACON_TARGET]) + if(target == blackboard[BB_BEACON_TARGET]) source.update_bot_mode(new_mode = BOT_PATROL) return @@ -69,6 +59,10 @@ set_blackboard_key_assoc_lazylist(BB_TEMPORARY_IGNORE_LIST, target, TRUE) addtimer(CALLBACK(src, PROC_REF(remove_from_blacklist), target), final_duration) +/// Unreachable targets get added to the temporary ignore list so we stop pathing to them. Subtypes override to skip blacklisting in stationary mode. +/datum/ai_controller/basic_controller/bot/note_unreachable_target(atom/target) + add_to_blacklist(target) + /datum/ai_controller/basic_controller/bot/proc/remove_from_blacklist(atom/target) if(QDELETED(target)) return @@ -100,15 +94,15 @@ /datum/ai_controller/basic_controller/bot/proc/reset_bot() SIGNAL_HANDLER - CancelActions() + cancel_current_plan() if(!length(reset_keys)) return for(var/key in reset_keys) clear_blackboard_key(key) ///set the target if we can reach them -/datum/ai_controller/basic_controller/bot/proc/set_if_can_reach(key, target, duration, distance = 10, bypass_add_to_blacklist = FALSE) - if(can_reach_target(target, distance)) +/datum/ai_controller/basic_controller/bot/proc/set_if_can_reach(key, target, duration, distance = 10, bypass_add_to_blacklist = FALSE, minimum_distance = 0) + if(can_reach_target(target, distance, minimum_distance)) EVLOG_MAPTEXT(src, EVLOG_CATEGORY_AI_TARGETING, "[pawn] has selected [target] as a target for blackboard key [key]!", get_turf(target), "Target: [target]") EVLOG_LINES(src, EVLOG_CATEGORY_AI_TARGETING, "Line to target", get_turf(pawn), get_turf(target)) set_blackboard_key(key, target) @@ -120,252 +114,3 @@ EVLOG_LINES(src, EVLOG_CATEGORY_AI_TARGETING, "Line to target", get_turf(pawn), get_turf(target)) add_to_blacklist(target, final_duration) return FALSE - -/datum/ai_controller/basic_controller/bot/proc/can_reach_target(target, distance = 10) - if(!isdatum(target)) //we dont need to check if its not a datum! - return TRUE - if(get_turf(pawn) == get_turf(target)) - return TRUE - var/list/path = get_path_to(pawn, target, simulated_only = !HAS_TRAIT(pawn, TRAIT_SPACEWALK), mintargetdist = minimum_distance, max_distance = distance, access = get_access()) - return (!!length(path)) - -/datum/ai_planning_subtree/find_patrol_beacon - ///travel towards beacon behavior - var/travel_behavior = /datum/ai_behavior/travel_towards/beacon - -/datum/ai_planning_subtree/find_patrol_beacon/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/mob/living/basic/bot/bot_pawn = controller.pawn - - if(controller.blackboard[BB_BOT_BEACON_COOLDOWN] > world.time) - return - - if(!(bot_pawn.bot_mode_flags & BOT_MODE_AUTOPATROL) || bot_pawn.mode == BOT_SUMMON) - return - - if(controller.blackboard_key_exists(BB_BEACON_TARGET)) - controller.queue_behavior(travel_behavior, BB_BEACON_TARGET) - return - - if(controller.blackboard_key_exists(BB_PREVIOUS_BEACON_TARGET)) - controller.queue_behavior(/datum/ai_behavior/find_next_beacon_target, BB_BEACON_TARGET) - return - - controller.queue_behavior(/datum/ai_behavior/find_first_beacon_target, BB_BEACON_TARGET) - -/datum/ai_behavior/find_first_beacon_target - -/datum/ai_behavior/find_first_beacon_target/perform(seconds_per_tick, datum/ai_controller/controller, target_key) - var/closest_distance = INFINITY - var/mob/living/basic/bot/bot_pawn = controller.pawn - var/atom/final_target - var/atom/previous_target = controller.blackboard[BB_PREVIOUS_BEACON_TARGET] - for(var/obj/machinery/navbeacon/beacon as anything in GLOB.navbeacons["[bot_pawn.z]"]) - var/dist = get_dist(bot_pawn, beacon) - if(beacon == previous_target || dist <= 1) - continue - if(dist > closest_distance) - continue - closest_distance = dist - final_target = beacon - - if(isnull(final_target)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - controller.set_blackboard_key(BB_BEACON_TARGET, final_target) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -/datum/ai_behavior/find_next_beacon_target - action_cooldown = 5 SECONDS - -/datum/ai_behavior/find_next_beacon_target/perform(seconds_per_tick, datum/ai_controller/basic_controller/bot/controller, target_key) - var/mob/living/basic/bot/bot_pawn = controller.pawn - var/atom/final_target - var/obj/machinery/navbeacon/prev_beacon = controller.blackboard[BB_PREVIOUS_BEACON_TARGET] - if(QDELETED(prev_beacon)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - for(var/obj/machinery/navbeacon/beacon as anything in GLOB.navbeacons["[bot_pawn.z]"]) - if(beacon.location == prev_beacon.codes[NAVBEACON_PATROL_NEXT]) - final_target = beacon - break - - if(isnull(final_target)) - controller.clear_blackboard_key(BB_PREVIOUS_BEACON_TARGET) //failed to find the next beacon, search for a first beacon again - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - controller.set_blackboard_key(BB_PREVIOUS_BEACON_TARGET, final_target) - controller.clear_blackboard_key(BB_BEACON_TARGET) - - if(LAZYACCESS(controller.blackboard[BB_TEMPORARY_IGNORE_LIST], final_target) || get_dist(bot_pawn, final_target) > controller.max_target_distance) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - if(controller.set_if_can_reach(key = BB_BEACON_TARGET, target = final_target, duration = 3 MINUTES, distance = controller.max_target_distance)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - - controller.set_blackboard_key(BB_BOT_BEACON_COOLDOWN, world.time + BOT_NO_BEACON_PATH_PENALTY) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - -/datum/ai_behavior/travel_towards/beacon - clear_target = TRUE - new_movement_type = /datum/ai_movement/jps/bot/travel_to_beacon - -/datum/ai_behavior/travel_towards/beacon/setup(datum/ai_controller/controller, target_key) - var/atom/target_beacon = controller.blackboard[target_key] - if(LAZYACCESS(controller.blackboard[BB_TEMPORARY_IGNORE_LIST], target_beacon)) - return FALSE - return ..() - -/datum/ai_behavior/travel_towards/beacon/finish_action(datum/ai_controller/basic_controller/bot/controller, succeeded, target_key) - var/atom/target = controller.blackboard[target_key] - if(!succeeded) - controller.set_blackboard_key(BB_BOT_BEACON_COOLDOWN, world.time + BOT_NO_BEACON_PATH_PENALTY) - controller.add_to_blacklist(target, 3 MINUTES) - controller.set_blackboard_key(BB_PREVIOUS_BEACON_TARGET, target) - return ..() - -/datum/ai_planning_subtree/respond_to_summon - -/datum/ai_planning_subtree/respond_to_summon/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(!controller.blackboard_key_exists(BB_BOT_SUMMON_TARGET)) - return - controller.queue_behavior(/datum/ai_behavior/travel_towards/bot_summon, BB_BOT_SUMMON_TARGET) - return SUBTREE_RETURN_FINISH_PLANNING - -/datum/ai_behavior/travel_towards/bot_summon - clear_target = TRUE - new_movement_type = /datum/ai_movement/jps/bot/travel_to_beacon - -/datum/ai_behavior/travel_towards/bot_summon/finish_action(datum/ai_controller/controller, succeeded, target_key) - var/mob/living/basic/bot/bot_pawn = controller.pawn - if(QDELETED(bot_pawn)) // pawn can be null at this point - return ..() - bot_pawn.calling_ai_ref = null - bot_pawn.update_bot_mode(new_mode = BOT_IDLE) - return ..() - -/datum/ai_planning_subtree/salute_authority - -/datum/ai_planning_subtree/salute_authority/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/mob/living/basic/bot/bot_pawn = controller.pawn - //we are criminals, dont salute the dirty pigs - if(bot_pawn.bot_access_flags & BOT_COVER_EMAGGED) - return - if(controller.blackboard_key_exists(BB_SALUTE_TARGET)) - controller.queue_behavior(/datum/ai_behavior/salute_authority, BB_SALUTE_TARGET, BB_SALUTE_MESSAGES) - return SUBTREE_RETURN_FINISH_PLANNING - - controller.queue_behavior(/datum/ai_behavior/find_and_set/valid_authority, BB_SALUTE_TARGET) - - -/datum/ai_behavior/find_and_set/valid_authority - behavior_flags = AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION - action_cooldown = BOT_COMMISSIONED_SALUTE_DELAY - -/datum/ai_behavior/find_and_set/valid_authority/search_tactic(datum/ai_controller/controller, locate_path, search_range = SEARCH_TACTIC_DEFAULT_RANGE) - for(var/mob/living/nearby_mob in oview(search_range, controller.pawn)) - if(!HAS_TRAIT(nearby_mob, TRAIT_COMMISSIONED)) - continue - return nearby_mob - return null - -/datum/ai_behavior/salute_authority - -/datum/ai_behavior/salute_authority/perform(seconds_per_tick, datum/ai_controller/controller, target_key, salute_keys) - if(!controller.blackboard_key_exists(target_key)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - var/list/salute_list = controller.blackboard[salute_keys] - if(!length(salute_list)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - var/mob/living/basic/bot/bot_pawn = controller.pawn - //special interaction if we are wearing a fedora - var/obj/item/our_hat = (locate(/obj/item/clothing/head) in bot_pawn) - if(our_hat) - salute_list += "tips [our_hat] at " - - bot_pawn.manual_emote(pick(salute_list) + " [controller.blackboard[target_key]]!") - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -/datum/ai_behavior/salute_authority/finish_action(datum/ai_controller/controller, succeeded, target_key) - . = ..() - controller.clear_blackboard_key(target_key) - -/datum/ai_behavior/bot_search - action_cooldown = 2 SECONDS - behavior_flags = AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION - -/datum/ai_behavior/bot_search/perform(seconds_per_tick, datum/ai_controller/basic_controller/bot/controller, target_key, looking_for, radius = 5, pathing_distance = 10, bypass_add_blacklist = FALSE, turf_search = FALSE) - if(!istype(controller)) - stack_trace("attempted to give [controller.pawn] the bot search behavior!") - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - var/mob/living/living_pawn = controller.pawn - var/list/ignore_list = controller.blackboard[BB_TEMPORARY_IGNORE_LIST] - var/list/objects_to_search = turf_search ? RANGE_TURFS(radius, controller.pawn) : oview(radius, controller.pawn) //use range turfs instead of oview when we can for performance - for(var/atom/potential_target as anything in objects_to_search) - if(QDELETED(living_pawn)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - if(!is_type_in_typecache(potential_target, looking_for)) - continue - if(LAZYACCESS(ignore_list, potential_target)) - continue - if(!valid_target(controller, potential_target)) - continue - if(!can_see(controller.pawn, potential_target, radius)) - continue - if(controller.set_if_can_reach(key = target_key, target = potential_target, distance = pathing_distance, bypass_add_to_blacklist = bypass_add_blacklist)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - -/datum/ai_behavior/bot_search/proc/valid_target(datum/ai_controller/basic_controller/bot/controller, atom/my_target) - return TRUE - -///behavior to make our bot talk -/datum/ai_behavior/bot_speech - action_cooldown = 5 SECONDS - behavior_flags = AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION - -/datum/ai_behavior/bot_speech/perform(seconds_per_tick, datum/ai_controller/controller, list/list_to_pick_from, announce_key) - var/datum/action/cooldown/bot_announcement/announcement = controller.blackboard[announce_key] - - if(isnull(announcement) || !length(list_to_pick_from)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - announcement.announce(pick(list_to_pick_from)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -///behavior to interact with atoms -/datum/ai_behavior/bot_interact - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH - ///should we remove the target afterwards? - var/clear_target = TRUE - -/datum/ai_behavior/bot_interact/setup(datum/ai_controller/controller, target_key) - . = ..() - var/turf/target = controller.blackboard[target_key] - if(isnull(target)) - return FALSE - set_movement_target(controller, target) - -/datum/ai_behavior/bot_interact/perform(seconds_per_tick, datum/ai_controller/controller, target_key) - var/mob/living/basic/living_pawn = controller.pawn - var/atom/target = controller.blackboard[target_key] - - if(QDELETED(target)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - living_pawn.UnarmedAttack(target, proximity_flag = TRUE) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -/datum/ai_behavior/bot_interact/finish_action(datum/ai_controller/basic_controller/bot/controller, succeeded, target_key) - . = ..() - var/atom/target = controller.blackboard[target_key] - if(clear_target) - controller.clear_blackboard_key(target_key) - if(!succeeded && !isnull(target)) - controller.add_to_blacklist(target) - -/datum/ai_behavior/bot_interact/keep_target - clear_target = FALSE - - -#undef BOT_NO_BEACON_PATH_PENALTY diff --git a/code/modules/mob/living/basic/bots/bot_hud.dm b/code/modules/mob/living/basic/bots/bot_hud.dm index 602b2cc54bd..f3d693073bc 100644 --- a/code/modules/mob/living/basic/bots/bot_hud.dm +++ b/code/modules/mob/living/basic/bots/bot_hud.dm @@ -32,7 +32,7 @@ set_hud_image_state(DIAG_BOT_HUD, "") ///proc that handles drawing and transforming the bot's path onto diagnostic huds -/mob/living/basic/bot/proc/generate_bot_path(datum/move_loop/has_target/jps/source) +/mob/living/basic/bot/proc/generate_bot_path(datum/move_loop/has_target/jps/source, list/path) SIGNAL_HANDLER UnregisterSignal(src, COMSIG_MOVELOOP_JPS_FINISHED_PATHING) @@ -40,8 +40,11 @@ if(isnull(ai_controller)) return + if(!length(path)) + return - var/atom/move_target = ai_controller.current_movement_target + + var/atom/move_target = path[path.len] if(move_target != ai_controller.blackboard[BB_BEACON_TARGET]) return diff --git a/code/modules/mob/living/basic/bots/cleanbot/clean_pet_target.bt.json b/code/modules/mob/living/basic/bots/cleanbot/clean_pet_target.bt.json new file mode 100644 index 00000000000..d3d867cc11f --- /dev/null +++ b/code/modules/mob/living/basic/bots/cleanbot/clean_pet_target.bt.json @@ -0,0 +1,28 @@ +{ + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_PET_TARGET" + }, + "child": { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_PET_TARGET", + "required_dist": 1 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/execute_clean", + "vars": { + "target_key": "BB_CURRENT_PET_TARGET" + } + } + ] + } +} diff --git a/code/modules/mob/living/basic/bots/cleanbot/cleanbot.bt.json b/code/modules/mob/living/basic/bots/cleanbot/cleanbot.bt.json new file mode 100644 index 00000000000..10be8fb903e --- /dev/null +++ b/code/modules/mob/living/basic/bots/cleanbot/cleanbot.bt.json @@ -0,0 +1,251 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/bot/cleanbot", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity/pacifist" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/bot_respond_to_summon" + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/override_id_set", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "override_id": "SUBPLAN_ID_PET_COMMAND" + }, + "child": { + "type": "subtree", + "subtype": "/datum/bt_node/subtree", + "override_id": "SUBPLAN_ID_PET_COMMAND" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bot_is_emagged", + "vars": { + "invert": true + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CURRENT_TARGET", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": true + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/execute_clean", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + } + ] + } + }, + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_FRIENDLY_JANITOR" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_FRIENDLY_JANITOR", + "required_dist": 1, + "finish_on_arrival": true, + "movement_type": "/datum/ai_movement/basic_avoidance" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/befriend_target", + "vars": { + "target_key": "BB_FRIENDLY_JANITOR", + "befriend_message": "BB_FRIENDLY_MESSAGE" + } + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_FRIENDLY_JANITOR", + "invert": true + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_FRIENDLY_JANITOR", + "target_source": "/datum/target_source/oview_single_type/human_mob", + "targeting_strategy": "/datum/targeting_strategy/conscious_human/cleanbot_whisperer", + "vision_range": 5, + "time_between_perform": "30 SECONDS" + } + } + } + ] + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/key_off_cooldown", + "vars": { + "observer_abort": "BT_ABORT_NONE", + "cooldown_key": "BB_POST_CLEAN_COOLDOWN" + }, + "child": { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/bot_patrol" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "time_between_perform": "3 SECONDS", + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "/datum/targeting_strategy/pickup_item", + "target_source": "/datum/target_source/cleanbot_cleanables", + "vision_range": 5, + "ignore_list_key": "BB_TEMPORARY_IGNORE_LIST", + "must_be_reachable": true, + "reach_distance": 15 + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/bot_salute_authority" + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bot_is_emagged", + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/cooldown", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "cooldown_key": "BB_ACID_SPRAY_COOLDOWN", + "cooldown_duration": 30 + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_ACID_SPRAY_TARGET" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_ACID_SPRAY_TARGET", + "required_dist": 0, + "finish_on_arrival": true, + "movement_type": "/datum/ai_movement/basic_avoidance" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/execute_clean", + "vars": { + "target_key": "BB_ACID_SPRAY_TARGET" + } + } + ] + } + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/use_mob_ability", + "vars": { + "ability_key": "BB_CLEANBOT_FOAM" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/key_off_cooldown", + "vars": { + "cooldown_key": "BB_POST_CLEAN_COOLDOWN" + }, + "child": { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/bot_patrol" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_ACID_SPRAY_TARGET", + "target_source": "/datum/target_source/oview_single_type/human_mob", + "targeting_strategy": "/datum/targeting_strategy/conscious_human", + "ignore_list_key": "BB_TEMPORARY_IGNORE_LIST", + "vision_range": 5, + "time_between_perform": "30 SECONDS" + } + } + ] + } + } + ] +} diff --git a/code/modules/mob/living/basic/bots/cleanbot/cleanbot_ai.dm b/code/modules/mob/living/basic/bots/cleanbot/cleanbot_ai.dm index 25029c5bc55..99948e13dec 100644 --- a/code/modules/mob/living/basic/bots/cleanbot/cleanbot_ai.dm +++ b/code/modules/mob/living/basic/bots/cleanbot/cleanbot_ai.dm @@ -2,6 +2,7 @@ #define POST_CLEAN_COOLDOWN 5 SECONDS /datum/ai_controller/basic_controller/bot/cleanbot + behavior_tree_json = "code/modules/mob/living/basic/bots/cleanbot/cleanbot.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/allow_items, BB_PET_TARGETING_STRATEGY = /datum/targeting_strategy/basic/not_friends, @@ -13,20 +14,9 @@ ), BB_FRIENDLY_MESSAGE = "empathetically acknowledges your hardwork and tough circumstances", ) - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity/pacifist, - /datum/ai_planning_subtree/respond_to_summon, - /datum/ai_planning_subtree/pet_planning/cleanbot, - /datum/ai_planning_subtree/cleaning_subtree, - /datum/ai_planning_subtree/befriend_janitors, - /datum/ai_planning_subtree/acid_spray, - /datum/ai_planning_subtree/use_mob_ability/foam_area, - /datum/ai_planning_subtree/salute_authority, - /datum/ai_planning_subtree/find_patrol_beacon/cleanbot, - ) reset_keys = list( BB_ACTIVE_PET_COMMAND, - BB_CLEAN_TARGET, + BB_CURRENT_TARGET, BB_BEACON_TARGET, BB_PREVIOUS_BEACON_TARGET, BB_BOT_SUMMON_TARGET, @@ -39,99 +29,40 @@ BB_HUNTABLE_TRASH = CLEANBOT_CLEAN_TRASH, ) -/datum/ai_planning_subtree/pet_planning/cleanbot/SelectBehaviors(datum/ai_controller/basic_controller/bot/controller, seconds_per_tick) - var/mob/living/basic/bot/bot_pawn = controller.pawn - //we are DONE listening to orders - if(bot_pawn.bot_access_flags & BOT_COVER_EMAGGED) - return - return ..() +/// Gathers nearby cleanable atoms: decals plus whatever types the cleanbot's currently enabled janitor mode flags allow. +/datum/target_source/cleanbot_cleanables -/datum/ai_planning_subtree/cleaning_subtree - -/datum/ai_planning_subtree/cleaning_subtree/SelectBehaviors(datum/ai_controller/basic_controller/bot/cleanbot/controller, seconds_per_tick) - if(controller.blackboard_key_exists(BB_CLEAN_TARGET)) - controller.queue_behavior(/datum/ai_behavior/execute_clean, BB_CLEAN_TARGET) - return SUBTREE_RETURN_FINISH_PLANNING - +/datum/target_source/cleanbot_cleanables/collect_candidates(mob/living/pawn, datum/ai_controller/basic_controller/bot/cleanbot/controller, range) var/list/final_hunt_list = list() - final_hunt_list += controller.blackboard[BB_CLEANABLE_DECALS] - var/list/flag_list = controller.clean_flags - var/mob/living/basic/bot/cleanbot/bot_pawn = controller.pawn - for(var/list_key in flag_list) - if(!(bot_pawn.janitor_mode_flags & flag_list[list_key])) + var/mob/living/basic/bot/cleanbot/bot_pawn = pawn + for(var/list_key in controller.clean_flags) + if(!(bot_pawn.janitor_mode_flags & controller.clean_flags[list_key])) continue final_hunt_list += controller.blackboard[list_key] + if(!length(final_hunt_list)) + return list() + var/list/type_filter = typecacheof(final_hunt_list) + return typecache_filter_list(oview(range, pawn), type_filter) - controller.queue_behavior(/datum/ai_behavior/find_and_set/in_list/clean_targets, BB_CLEAN_TARGET, final_hunt_list) +///clean that shit bro fr fr 67 +/datum/bt_node/ai_behavior/execute_clean + var/target_key -/datum/ai_behavior/find_and_set/in_list/clean_targets - action_cooldown = 3 SECONDS - -/datum/ai_behavior/find_and_set/in_list/clean_targets/search_tactic(datum/ai_controller/basic_controller/bot/controller, locate_paths, search_range = SEARCH_TACTIC_DEFAULT_RANGE) - var/list/found = typecache_filter_list(oview(search_range, controller.pawn), locate_paths) - var/list/ignore_list = controller.blackboard[BB_TEMPORARY_IGNORE_LIST] - for(var/atom/found_item in found) - if(QDELETED(controller.pawn)) - break - if(LAZYACCESS(ignore_list, found_item)) - continue - if(get_turf(found_item) == get_turf(controller.pawn)) - return found_item - var/list/path = get_path_to(controller.pawn, found_item, max_distance = BOT_CLEAN_PATH_LIMIT, access = controller.get_access()) - if(!length(path)) - controller.add_to_blacklist(found_item) - continue - return found_item - -/datum/ai_planning_subtree/acid_spray - -/datum/ai_planning_subtree/acid_spray/SelectBehaviors(datum/ai_controller/basic_controller/bot/controller, seconds_per_tick) - var/mob/living/basic/bot/cleanbot/bot_pawn = controller.pawn - if(!(bot_pawn.bot_access_flags & BOT_COVER_EMAGGED)) - return - if(controller.blackboard_key_exists(BB_ACID_SPRAY_TARGET)) - controller.queue_behavior(/datum/ai_behavior/execute_clean, BB_ACID_SPRAY_TARGET) - return SUBTREE_RETURN_FINISH_PLANNING - - controller.queue_behavior(/datum/ai_behavior/find_and_set/spray_target, BB_ACID_SPRAY_TARGET, /mob/living/carbon/human, 5) - -/datum/ai_behavior/find_and_set/spray_target - action_cooldown = 30 SECONDS - behavior_flags = AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION - -/datum/ai_behavior/find_and_set/spray_target/search_tactic(datum/ai_controller/controller, locate_path, search_range = SEARCH_TACTIC_DEFAULT_RANGE) - var/list/ignore_list = controller.blackboard[BB_TEMPORARY_IGNORE_LIST] - for(var/mob/living/carbon/human/human_target in oview(search_range, controller.pawn)) - if(LAZYACCESS(ignore_list, human_target)) - continue - if(human_target.stat != CONSCIOUS || isnull(human_target.mind)) - continue - return human_target - return null - -/datum/ai_behavior/execute_clean - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION | AI_BEHAVIOR_REQUIRE_REACH - -/datum/ai_behavior/execute_clean/setup(datum/ai_controller/controller, target_key) - . = ..() - var/turf/target = controller.blackboard[target_key] - if(isnull(target)) - return FALSE - set_movement_target(controller, target) - -/datum/ai_behavior/execute_clean/perform(seconds_per_tick, datum/ai_controller/controller, target_key) +/datum/bt_node/ai_behavior/execute_clean/perform(seconds_per_tick, datum/ai_controller/controller) var/mob/living/basic/living_pawn = controller.pawn var/atom/target = controller.blackboard[target_key] - if(QDELETED(target)) + EVLOG_TEXT(controller, EVLOG_CATEGORY_AI_BEHAVIORS, "[living_pawn] execute_clean: target deleted") return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - + if(get_dist(living_pawn, target) > 1) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + EVLOG_MAPTEXT(controller, EVLOG_CATEGORY_AI_BEHAVIORS, "[living_pawn] cleaning [target]", get_turf(target), "Cleaning") living_pawn.UnarmedAttack(target, proximity_flag = TRUE) return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED -/datum/ai_behavior/execute_clean/finish_action(datum/ai_controller/basic_controller/bot/controller, succeeded, target_key, targeting_strategy_key, hiding_location_key) +/datum/bt_node/ai_behavior/execute_clean/finish_action(datum/ai_controller/basic_controller/bot/controller, succeeded) . = ..() controller.set_blackboard_key(BB_POST_CLEAN_COOLDOWN, POST_CLEAN_COOLDOWN + world.time) var/atom/target = controller.blackboard[target_key] @@ -147,56 +78,25 @@ var/list/speech_list = controller.blackboard[BB_CLEANBOT_EMAGGED_PHRASES] if(length(speech_list)) var/mob/living/living_pawn = controller.pawn - if(!QDELETED(living_pawn)) // pawn can be null at this point - living_pawn.say(pick(speech_list), forced = "ai controller") + if(!QDELETED(living_pawn)) + INVOKE_ASYNC(living_pawn, TYPE_PROC_REF(/atom/movable, say), pick(speech_list), forced = "ai controller") controller.clear_blackboard_key(target_key) -/datum/ai_planning_subtree/use_mob_ability/foam_area - ability_key = BB_CLEANBOT_FOAM - finish_planning = FALSE -/datum/ai_planning_subtree/use_mob_ability/foam_area/SelectBehaviors(datum/ai_controller/basic_controller/bot/controller, seconds_per_tick) - var/mob/living/basic/bot/bot_pawn = controller.pawn - if(!(bot_pawn.bot_access_flags & BOT_COVER_EMAGGED)) - return - return ..() +/// Valid if the target is a conscious human janitor-whisperer the cleanbot hasn't already befriended. +/datum/targeting_strategy/conscious_human/cleanbot_whisperer/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + if(!HAS_TRAIT(target, TRAIT_CLEANBOT_WHISPERER)) + return FALSE + return !living_mob.has_ally(REF(target)) -/datum/ai_planning_subtree/befriend_janitors -/datum/ai_planning_subtree/befriend_janitors/SelectBehaviors(datum/ai_controller/basic_controller/bot/controller, seconds_per_tick) - var/mob/living/basic/bot/bot_pawn = controller.pawn - //we are now evil. dont befriend the janitors - if(bot_pawn.bot_access_flags & BOT_COVER_EMAGGED) - return - if(controller.blackboard_key_exists(BB_FRIENDLY_JANITOR)) - controller.queue_behavior(/datum/ai_behavior/befriend_target, BB_FRIENDLY_JANITOR, BB_FRIENDLY_MESSAGE) - return SUBTREE_RETURN_FINISH_PLANNING - - controller.queue_behavior(/datum/ai_behavior/find_and_set/friendly_janitor, BB_FRIENDLY_JANITOR, /mob/living/carbon/human, 5) - -/datum/ai_behavior/find_and_set/friendly_janitor - action_cooldown = 30 SECONDS - behavior_flags = AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION - -/datum/ai_behavior/find_and_set/friendly_janitor/search_tactic(datum/ai_controller/controller, locate_path, search_range = SEARCH_TACTIC_DEFAULT_RANGE) - var/mob/living/living_pawn = controller.pawn - for(var/mob/living/carbon/human/human_target in oview(search_range, living_pawn)) - if(human_target.stat != CONSCIOUS || isnull(human_target.mind)) - continue - if(!HAS_TRAIT(human_target, TRAIT_CLEANBOT_WHISPERER)) - continue - if(living_pawn.has_ally(REF(human_target))) - continue - return human_target - return null - -/datum/ai_planning_subtree/find_patrol_beacon/cleanbot - -/datum/ai_planning_subtree/find_patrol_beacon/cleanbot/SelectBehaviors(datum/ai_controller/basic_controller/bot/controller, seconds_per_tick) - if(controller.blackboard[BB_POST_CLEAN_COOLDOWN] >= world.time) - return - return ..() +/datum/bt_node/subtree/clean_pet_target + behavior_tree_json = "code/modules/mob/living/basic/bots/cleanbot/clean_pet_target.bt.json" +///Tells the cleanbot to go clean a target /datum/pet_command/clean command_name = "Clean" command_desc = "Command a cleanbot to clean the mess." @@ -215,11 +115,11 @@ return ..() /datum/pet_command/clean/execute_action(datum/ai_controller/basic_controller/bot/controller) - if(controller.blackboard_key_exists(BB_CURRENT_PET_TARGET)) - controller.queue_behavior(/datum/ai_behavior/execute_clean, BB_CURRENT_PET_TARGET) - return SUBTREE_RETURN_FINISH_PLANNING - - controller.clear_blackboard_key(BB_ACTIVE_PET_COMMAND) + var/atom/target = controller.blackboard[BB_CURRENT_PET_TARGET] + if(QDELETED(target)) + controller.clear_blackboard_key(BB_ACTIVE_PET_COMMAND) + return + controller.set_behavior_tree_override(SUBPLAN_ID_PET_COMMAND, /datum/bt_node/subtree/clean_pet_target) #undef BOT_CLEAN_PATH_LIMIT #undef POST_CLEAN_COOLDOWN diff --git a/code/modules/mob/living/basic/bots/dedbot.bt.json b/code/modules/mob/living/basic/bots/dedbot.bt.json new file mode 100644 index 00000000000..04c05cdf64e --- /dev/null +++ b/code/modules/mob/living/basic/bots/dedbot.bt.json @@ -0,0 +1,73 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/bot/dedbot", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": true, + "movement_type": "/datum/ai_movement/basic_avoidance" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability/melee", + "vars": { + "ability_key": "BB_DEDBOT_SLASH", + "target_key": "BB_CURRENT_TARGET" + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/bot_respond_to_summon" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/bot_patrol" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/bots/dedbot.dm b/code/modules/mob/living/basic/bots/dedbot.dm index d55570edbdc..c81baec82c9 100644 --- a/code/modules/mob/living/basic/bots/dedbot.dm +++ b/code/modules/mob/living/basic/bots/dedbot.dm @@ -49,31 +49,20 @@ grant_actions_by_list(innate_actions) /datum/ai_controller/basic_controller/bot/dedbot + behavior_tree_json = "code/modules/mob/living/basic/bots/dedbot.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_TARGET_MINIMUM_STAT = DEAD, BB_AGGRO_RANGE = 2, ) ai_movement = /datum/ai_movement/jps/bot - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/targeted_mob_ability/exenterate, - /datum/ai_planning_subtree/respond_to_summon, - /datum/ai_planning_subtree/find_patrol_beacon, - ) max_target_distance = AI_BOT_PATH_LENGTH - ///keys to be reset when the bot is reseted reset_keys = list( BB_BEACON_TARGET, BB_PREVIOUS_BEACON_TARGET, BB_BOT_SUMMON_TARGET, ) -/datum/ai_planning_subtree/targeted_mob_ability/exenterate - ability_key = BB_DEDBOT_SLASH - finish_planning = FALSE - /datum/action/cooldown/mob_cooldown/exenterate name = "Exenterate" desc = "Disembowel every living thing in range with your blades." diff --git a/code/modules/mob/living/basic/bots/ed209/ed209.bt.json b/code/modules/mob/living/basic/bots/ed209/ed209.bt.json new file mode 100644 index 00000000000..b1fd91ac2cd --- /dev/null +++ b/code/modules/mob/living/basic/bots/ed209/ed209.bt.json @@ -0,0 +1,132 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/bot/ed209", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity/pacifist" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/bot_respond_to_summon" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": false, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": false, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/is_target_stunned", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": true, + "movement_type": "/datum/ai_movement/basic_avoidance" + } + } + ] + } + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_ranged_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION", + "max_range": 9 + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/maintain_distance", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "approach_movement_type": "/datum/ai_movement/basic_avoidance" + } + } + ] + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/bot_patrol" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/bots/ed209/ed209_ai.dm b/code/modules/mob/living/basic/bots/ed209/ed209_ai.dm index d699a153bb2..4305104d5fb 100644 --- a/code/modules/mob/living/basic/bots/ed209/ed209_ai.dm +++ b/code/modules/mob/living/basic/bots/ed209/ed209_ai.dm @@ -2,18 +2,13 @@ #define SPECIAL_LINES "special_lines" /datum/ai_controller/basic_controller/bot/ed209 + behavior_tree_json = "code/modules/mob/living/basic/bots/ed209/ed209.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/secbot, BB_UNREACHABLE_LIST_COOLDOWN = 1 MINUTES, BB_ALWAYS_IGNORE_FACTION = TRUE, - ) - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity/pacifist, - /datum/ai_planning_subtree/respond_to_summon, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/ranged_skirmish, - /datum/ai_planning_subtree/arrest_target/ed209, - /datum/ai_planning_subtree/find_patrol_beacon, + BB_RANGED_SKIRMISH_MIN_DISTANCE = 2, + BB_RANGED_SKIRMISH_MAX_DISTANCE = 3 ) reset_keys = list( BB_BEACON_TARGET, @@ -26,7 +21,7 @@ . = ..() if(. & AI_CONTROLLER_INCOMPATIBLE) return - RegisterSignal(new_pawn, COMSIG_AI_BLACKBOARD_KEY_SET(BB_BASIC_MOB_CURRENT_TARGET), PROC_REF(on_target_set)) + RegisterSignal(new_pawn, COMSIG_AI_BLACKBOARD_KEY_SET(BB_CURRENT_TARGET), PROC_REF(on_target_set)) /datum/ai_controller/basic_controller/bot/ed209/proc/on_target_set() @@ -48,18 +43,5 @@ var/list/final_list = my_bot.sheriffized ? lines_to_pick[SPECIAL_LINES] : lines_to_pick[DEFAULT_LINES] INVOKE_ASYNC(announcement, TYPE_PROC_REF(/datum/action/cooldown/bot_announcement, announce), pick(final_list)) -/datum/ai_planning_subtree/arrest_target/ed209 - arrest_behavior = /datum/ai_behavior/basic_melee_attack/interact_once/bot/ed209 - - -/datum/ai_behavior/basic_melee_attack/interact_once/bot/ed209 - action_cooldown = 0.5 SECONDS - -/datum/ai_behavior/basic_melee_attack/interact_once/bot/ed209/perform(seconds_per_tick, datum/ai_controller/controller, target_key, targeting_strategy_key, hiding_location_key) - . = ..() - if(!(. & AI_BEHAVIOR_DELAY)) - return AI_BEHAVIOR_DELAY //this kinda sucks but we have to do this cause we need to shoot while moving to stun - - #undef DEFAULT_LINES #undef SPECIAL_LINES diff --git a/code/modules/mob/living/basic/bots/ed209/ed209_nukie_ai.dm b/code/modules/mob/living/basic/bots/ed209/ed209_nukie_ai.dm index 24bf9a20367..3d5a6e283e1 100644 --- a/code/modules/mob/living/basic/bots/ed209/ed209_nukie_ai.dm +++ b/code/modules/mob/living/basic/bots/ed209/ed209_nukie_ai.dm @@ -2,13 +2,10 @@ blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_UNREACHABLE_LIST_COOLDOWN = 1 MINUTES, + BB_RANGED_SKIRMISH_MIN_DISTANCE = 2, + BB_RANGED_SKIRMISH_MAX_DISTANCE = 3 ) - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity/pacifist, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_ranged_attack_subtree, - /datum/ai_planning_subtree/find_patrol_beacon, - ) + behavior_tree_json = "code/modules/mob/living/basic/bots/ed209/ed209_syndicate.bt.json" reset_keys = list( BB_BEACON_TARGET, BB_PREVIOUS_BEACON_TARGET, diff --git a/code/modules/mob/living/basic/bots/ed209/ed209_syndicate.bt.json b/code/modules/mob/living/basic/bots/ed209/ed209_syndicate.bt.json new file mode 100644 index 00000000000..469fba53542 --- /dev/null +++ b/code/modules/mob/living/basic/bots/ed209/ed209_syndicate.bt.json @@ -0,0 +1,21 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/bot/ed209/syndicate", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity/pacifist" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/bot_respond_to_summon" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_ranged_combat", + "bindings": { + "bsjjwub2": "/datum/bt_node/subtree/bot_patrol" + } + } + ] +} diff --git a/code/modules/mob/living/basic/bots/firebot/firebot.bt.json b/code/modules/mob/living/basic/bots/firebot/firebot.bt.json new file mode 100644 index 00000000000..08156b1ddd8 --- /dev/null +++ b/code/modules/mob/living/basic/bots/firebot/firebot.bt.json @@ -0,0 +1,119 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/bot/firebot", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "1 SECONDS", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity/pacifist" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/bot_respond_to_summon" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/announce_fire_detected" + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": true, + "movement_type": "/datum/ai_movement/basic_avoidance" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/bot_interact/extinguish", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/bot_patrol" + } + ] + }, + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "target_source": "/datum/target_source/firebot_targets", + "targeting_strategy": "/datum/targeting_strategy/extinguishable_person", + "ignore_list_key": "BB_TEMPORARY_IGNORE_LIST", + "must_be_reachable": true, + "vision_range": 5 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "target_source": "/datum/target_source/range_turfs/firebot_hotspots", + "targeting_strategy": "/datum/targeting_strategy/burning_hotspot", + "ignore_list_key": "BB_TEMPORARY_IGNORE_LIST", + "must_be_reachable": true, + "vision_range": 5 + } + } + ] + } + ] + } + ] + }, + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/handle_firebot_speech" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/bot_salute_authority" + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/bots/firebot/firebot_ai.dm b/code/modules/mob/living/basic/bots/firebot/firebot_ai.dm index b40f5da5abd..8e44c73c548 100644 --- a/code/modules/mob/living/basic/bots/firebot/firebot_ai.dm +++ b/code/modules/mob/living/basic/bots/firebot/firebot_ai.dm @@ -5,17 +5,9 @@ BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/allow_turfs, BB_UNREACHABLE_LIST_COOLDOWN = 3 MINUTES, ) - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity/pacifist, - /datum/ai_planning_subtree/respond_to_summon, - /datum/ai_planning_subtree/extinguishing_people, - /datum/ai_planning_subtree/extinguishing_turfs, - /datum/ai_planning_subtree/salute_authority, - /datum/ai_planning_subtree/firebot_speech, - /datum/ai_planning_subtree/find_patrol_beacon, - ) + behavior_tree_json = "code/modules/mob/living/basic/bots/firebot/firebot.bt.json" reset_keys = list( - BB_FIREBOT_EXTINGUISH_TARGET, + BB_CURRENT_TARGET, BB_BEACON_TARGET, BB_PREVIOUS_BEACON_TARGET, BB_BOT_SUMMON_TARGET, @@ -27,106 +19,106 @@ . = ..() if(. & AI_CONTROLLER_INCOMPATIBLE) return - RegisterSignal(new_pawn, COMSIG_AI_BLACKBOARD_KEY_SET(BB_FIREBOT_EXTINGUISH_TARGET), PROC_REF(on_target_found)) -///say a silly line whenever we find someone on fire -/datum/ai_controller/basic_controller/bot/firebot/proc/on_target_found() - SIGNAL_HANDLER - if(!COOLDOWN_FINISHED(src, announcement_cooldown)) - return - var/datum/action/cooldown/bot_announcement/announcement = blackboard[BB_ANNOUNCE_ABILITY] + +/datum/bt_node/ai_behavior/announce_fire_detected + +/datum/bt_node/ai_behavior/announce_fire_detected/perform(seconds_per_tick, datum/ai_controller/basic_controller/bot/firebot/controller) + if(!COOLDOWN_FINISHED(controller, announcement_cooldown)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED + var/datum/action/cooldown/bot_announcement/announcement = controller.blackboard[BB_ANNOUNCE_ABILITY] if(isnull(announcement)) - return - - var/list/lines = blackboard[BB_FIREBOT_FIRE_DETECTED_LINES] + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED + var/list/lines = controller.blackboard[BB_FIREBOT_FIRE_DETECTED_LINES] if(!length(lines)) - return + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED INVOKE_ASYNC(announcement, TYPE_PROC_REF(/datum/action/cooldown/bot_announcement, announce), pick(lines)) - COOLDOWN_START(src, announcement_cooldown, ANNOUNCEMENT_TIMER) + COOLDOWN_START(controller, announcement_cooldown, ANNOUNCEMENT_TIMER) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED -///subtree for extinguishing people -/datum/ai_planning_subtree/extinguishing_people - -/datum/ai_planning_subtree/extinguishing_people/SelectBehaviors(datum/ai_controller/basic_controller/bot/controller, seconds_per_tick) - if(controller.blackboard_key_exists(BB_FIREBOT_EXTINGUISH_TARGET)) - controller.queue_behavior(/datum/ai_behavior/basic_melee_attack/interact_once/extinguish, BB_FIREBOT_EXTINGUISH_TARGET, BB_TARGETING_STRATEGY) - return SUBTREE_RETURN_FINISH_PLANNING - - var/mob/living/basic/bot/firebot/living_bot = controller.pawn - var/range = living_bot.firebot_mode_flags & FIREBOT_STATIONARY_MODE ? 1 : 5 - - if(living_bot.firebot_mode_flags & FIREBOT_EXTINGUISH_PEOPLE) - controller.queue_behavior(/datum/ai_behavior/bot_search/people_on_fire, BB_FIREBOT_EXTINGUISH_TARGET, controller.blackboard[BB_FIREBOT_CAN_EXTINGUISH], range) - -///behavior for finding people on fire -/datum/ai_behavior/bot_search/people_on_fire - -/datum/ai_behavior/bot_search/people_on_fire/valid_target(datum/ai_controller/basic_controller/bot/controller, mob/living/my_target) - var/mob/living/basic/bot/living_bot = controller.pawn - return (my_target.on_fire || (living_bot.bot_access_flags & BOT_COVER_EMAGGED)) - -///subtree for finding turfs to extinguish -/datum/ai_planning_subtree/extinguishing_turfs - -/datum/ai_planning_subtree/extinguishing_turfs/SelectBehaviors(datum/ai_controller/basic_controller/bot/controller, seconds_per_tick) - if(controller.blackboard_key_exists(BB_FIREBOT_EXTINGUISH_TARGET)) +/// Firebot skips blacklisting unreachable targets while stationary, matching the old set_if_can_reach bypass. +/datum/ai_controller/basic_controller/bot/firebot/note_unreachable_target(atom/target) + var/mob/living/basic/bot/firebot/bot_pawn = pawn + if(bot_pawn.firebot_mode_flags & FIREBOT_STATIONARY_MODE) return + return ..() - var/mob/living/basic/bot/firebot/living_bot = controller.pawn - var/should_bypass_blacklist = living_bot.firebot_mode_flags & FIREBOT_STATIONARY_MODE +/// Gathers nearby living mobs to extinguish; empty unless people-extinguishing is on, range clamped to adjacent tiles when stationary. +/datum/target_source/firebot_targets - if(living_bot.firebot_mode_flags & FIREBOT_EXTINGUISH_FLAMES) - controller.queue_behavior(/datum/ai_behavior/search_burning_turfs, BB_FIREBOT_EXTINGUISH_TARGET, should_bypass_blacklist) +/datum/target_source/firebot_targets/collect_candidates(mob/living/pawn, datum/ai_controller/controller, range) + var/mob/living/basic/bot/firebot/bot_pawn = pawn + if(!(bot_pawn.firebot_mode_flags & FIREBOT_EXTINGUISH_PEOPLE)) + return list() + if(bot_pawn.firebot_mode_flags & FIREBOT_STATIONARY_MODE) + range = 1 + var/list/candidates = list() + for(var/mob/living/candidate in oview(range, pawn)) + candidates += candidate + return candidates -///behavior to find burning turfs -/datum/ai_behavior/search_burning_turfs - action_cooldown = 2 SECONDS - behavior_flags = AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION +/// Valid if the mob is on fire (or anyone, while emagged) and is a type this firebot is allowed to extinguish. +/datum/targeting_strategy/extinguishable_person/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + var/mob/living/target_mob = target + if(!isliving(target_mob)) + return FALSE + var/mob/living/basic/bot/firebot/bot_pawn = living_mob + if(!target_mob.on_fire && !(bot_pawn.bot_access_flags & BOT_COVER_EMAGGED)) + return FALSE + return is_type_in_list(target_mob, controller.blackboard[BB_FIREBOT_CAN_EXTINGUISH]) -/datum/ai_behavior/search_burning_turfs/perform(seconds_per_tick, datum/ai_controller/basic_controller/bot/controller, target_key, bypass_add_blacklist = FALSE) - var/mob/living/living_pawn = controller.pawn - var/list/ignore_list = controller.blackboard[BB_TEMPORARY_IGNORE_LIST] - for(var/turf/possible_turf as anything in RANGE_TURFS(5, living_pawn)) - if(QDELETED(living_pawn)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - if(!isopenturf(possible_turf)) - continue - var/turf/open/open_turf = possible_turf - if(!open_turf.active_hotspot) - continue - if(LAZYACCESS(ignore_list, possible_turf)) - continue - if(controller.set_if_can_reach(key = target_key, target = possible_turf, bypass_add_to_blacklist = bypass_add_blacklist)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED +/// Gathers turfs in range; empty unless flame-extinguishing is enabled. +/datum/target_source/range_turfs/firebot_hotspots - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED +/datum/target_source/range_turfs/firebot_hotspots/collect_candidates(mob/living/pawn, datum/ai_controller/controller, range) + var/mob/living/basic/bot/firebot/bot_pawn = pawn + if(!(bot_pawn.firebot_mode_flags & FIREBOT_EXTINGUISH_FLAMES)) + return list() + return ..(pawn, controller, range) -///behavior to extinguish mobs or turfs -/datum/ai_behavior/basic_melee_attack/interact_once/extinguish +/// Valid if the turf is an open turf with an active fire. +/datum/targeting_strategy/burning_hotspot/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + if(!isopenturf(target)) + return FALSE + var/turf/open/open_turf = target + return !!open_turf.active_hotspot -/datum/ai_behavior/basic_melee_attack/interact_once/extinguish/finish_action(datum/ai_controller/basic_controller/bot/controller, succeeded, target_key, targeting_strategy_key, hiding_location_key) - var/atom/target = controller.blackboard[BB_FIREBOT_EXTINGUISH_TARGET] + + +/datum/bt_node/ai_behavior/bot_interact/extinguish + +/datum/bt_node/ai_behavior/bot_interact/extinguish/finish_action(datum/ai_controller/basic_controller/bot/controller, succeeded) + . = ..() + // if we couldn't reach OR we emagged a living target, blacklist them + var/atom/target = controller.blackboard[target_key] var/mob/living/basic/bot/living_bot = controller.pawn - - //if we couldnt path, or we successfully burnt someone, ignore them for a bit! if(!succeeded || (isliving(target) && (living_bot.bot_access_flags & BOT_COVER_EMAGGED))) controller.add_to_blacklist(target) - return ..() -///subtree to make us say funny idle lines -/datum/ai_planning_subtree/firebot_speech - ///chance we spout lines + +/datum/bt_node/ai_behavior/handle_firebot_speech + time_between_perform = 20 SECONDS var/speech_prob = 3 -/datum/ai_planning_subtree/firebot_speech/SelectBehaviors(datum/ai_controller/basic_controller/bot/controller, seconds_per_tick) - if(controller.blackboard[BB_FIREBOT_EXTINGUISH_TARGET] || !SPT_PROB(speech_prob, seconds_per_tick)) - return +/datum/bt_node/ai_behavior/handle_firebot_speech/perform(seconds_per_tick, datum/ai_controller/controller) + if(!SPT_PROB(speech_prob, seconds_per_tick)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED var/mob/living/basic/bot/living_bot = controller.pawn var/list/idle_lines = (living_bot.bot_access_flags & BOT_COVER_EMAGGED) ? controller.blackboard[BB_FIREBOT_EMAGGED_LINES] : controller.blackboard[BB_FIREBOT_IDLE_LINES] - controller.queue_behavior(/datum/ai_behavior/bot_speech, idle_lines, BB_ANNOUNCE_ABILITY) + if(!length(idle_lines)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + var/datum/action/cooldown/bot_announcement/announcement = controller.blackboard[BB_ANNOUNCE_ABILITY] + announcement?.announce(pick(idle_lines)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED #undef ANNOUNCEMENT_TIMER diff --git a/code/modules/mob/living/basic/bots/honkbots/honkbot.bt.json b/code/modules/mob/living/basic/bots/honkbots/honkbot.bt.json new file mode 100644 index 00000000000..2e6d19f16c4 --- /dev/null +++ b/code/modules/mob/living/basic/bots/honkbots/honkbot.bt.json @@ -0,0 +1,191 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/bot/honkbot", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "1 SECONDS", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity/pacifist" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/bot_respond_to_summon" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": true, + "movement_type": "/datum/ai_movement/basic_avoidance" + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/honkbot_slip" + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CLOWN_FRIEND" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/play_with_clown", + "vars": { + "target_key": "BB_CLOWN_FRIEND" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CLOWN_FRIEND", + "required_dist": 1, + "finish_on_arrival": true, + "movement_type": "/datum/ai_movement/basic_avoidance" + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/bot_patrol" + } + ] + }, + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_SLIPPERY_TARGET", + "target_source": "/datum/target_source/honkbot_slippery", + "targeting_strategy": "/datum/targeting_strategy/can_see", + "ignore_list_key": "BB_TEMPORARY_IGNORE_LIST", + "vision_range": 5, + "time_between_perform": "5 SECONDS" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_SLIP_TARGET", + "target_source": "/datum/target_source/oview_single_type/human_mob", + "targeting_strategy": "/datum/targeting_strategy/can_see/slip_victim", + "ignore_list_key": "BB_TEMPORARY_IGNORE_LIST", + "must_be_reachable": true, + "vision_range": 5 + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_CLOWN_FRIEND", + "target_source": "/datum/target_source/oview_single_type/living_mob", + "targeting_strategy": "/datum/targeting_strategy/clown_friend", + "ignore_list_key": "BB_TEMPORARY_IGNORE_LIST", + "must_be_reachable": true, + "vision_range": 5, + "time_between_perform": "5 SECONDS" + } + } + ] + } + ] + } + ] + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/bot_salute_authority" + } + ] +} diff --git a/code/modules/mob/living/basic/bots/honkbots/honkbot.dm b/code/modules/mob/living/basic/bots/honkbots/honkbot.dm index fbe91b7cc4d..a96ee6309dd 100644 --- a/code/modules/mob/living/basic/bots/honkbots/honkbot.dm +++ b/code/modules/mob/living/basic/bots/honkbots/honkbot.dm @@ -55,7 +55,7 @@ return honkbot_sounds /mob/living/basic/bot/secbot/honkbot/proc/pre_slip() - return (prob(70) && ai_controller?.blackboard_key_exists(BB_BASIC_MOB_CURRENT_TARGET)) + return (prob(70) && ai_controller?.blackboard_key_exists(BB_CURRENT_TARGET)) /mob/living/basic/bot/secbot/honkbot/proc/post_slip() INVOKE_ASYNC(src, TYPE_PROC_REF(/mob/living/basic/bot, speak), HONKBOT_VOICED_HONK_SAD) diff --git a/code/modules/mob/living/basic/bots/honkbots/honkbot_ai.dm b/code/modules/mob/living/basic/bots/honkbots/honkbot_ai.dm index 3a4d022087d..ea668a55c19 100644 --- a/code/modules/mob/living/basic/bots/honkbots/honkbot_ai.dm +++ b/code/modules/mob/living/basic/bots/honkbots/honkbot_ai.dm @@ -4,16 +4,7 @@ BB_UNREACHABLE_LIST_COOLDOWN = 1 MINUTES, BB_ALWAYS_IGNORE_FACTION = TRUE, ) - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity/pacifist, - /datum/ai_planning_subtree/respond_to_summon, - /datum/ai_planning_subtree/use_mob_ability/random_honk, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/arrest_target, - /datum/ai_planning_subtree/slip_victims, - /datum/ai_planning_subtree/play_with_clowns, - /datum/ai_planning_subtree/find_patrol_beacon, - ) + behavior_tree_json = "code/modules/mob/living/basic/bots/honkbots/honkbot.bt.json" reset_keys = list( BB_BEACON_TARGET, BB_PREVIOUS_BEACON_TARGET, @@ -24,15 +15,10 @@ . = ..() if(. & AI_CONTROLLER_INCOMPATIBLE) return - RegisterSignal(new_pawn, COMSIG_AI_BLACKBOARD_KEY_CLEARED(BB_SLIP_TARGET), PROC_REF(on_clear_target)) + // on_clear_target intentionally removed it caused grab-then-immediately-release bug + // Cleanup is handled by on_stop_pulling instead RegisterSignal(new_pawn, COMSIG_ATOM_NO_LONGER_PULLING, PROC_REF(on_stop_pulling)) -/datum/ai_controller/basic_controller/bot/honkbot/proc/on_clear_target(datum/source) - SIGNAL_HANDLER - - var/mob/living/living_pawn = pawn - living_pawn.stop_pulling() - /datum/ai_controller/basic_controller/bot/honkbot/proc/on_stop_pulling(datum/source) SIGNAL_HANDLER @@ -43,128 +29,107 @@ add_to_blacklist(slip_target) clear_blackboard_key(BB_SLIP_TARGET) -/datum/ai_planning_subtree/play_with_clowns/SelectBehaviors(datum/ai_controller/basic_controller/bot/controller, seconds_per_tick) - var/mob/living/clown_target = controller.blackboard[BB_CLOWN_FRIEND] - if(QDELETED(clown_target)) - var/list/my_list = controller.blackboard[BB_CLOWNS_LIST] - controller.queue_behavior(/datum/ai_behavior/bot_search/clown_friends, BB_CLOWN_FRIEND, my_list) - return - controller.queue_behavior(/datum/ai_behavior/play_with_clown, BB_CLOWN_FRIEND) - return SUBTREE_RETURN_FINISH_PLANNING -/datum/ai_behavior/bot_search/clown_friends -/datum/ai_behavior/bot_search/clown_friends/valid_target(datum/ai_controller/basic_controller/bot/controller, mob/living/my_target) - if(HAS_TRAIT(my_target, TRAIT_PERCEIVED_AS_CLOWN)) - return TRUE - if(!istype(my_target, /mob/living/silicon/robot)) - return FALSE - var/mob/living/silicon/robot/robot_target = my_target - return istype(robot_target.model, /obj/item/robot_model/clown) +/datum/bt_node/subtree/honkbot_slip + behavior_tree_json = "code/modules/mob/living/basic/bots/honkbots/honkbot_slip.bt.json" -/datum/ai_behavior/play_with_clown - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION -/datum/ai_behavior/play_with_clown/setup(datum/ai_controller/controller, target_key) + +/datum/bt_node/ai_behavior/use_mob_ability/random_honk + +/datum/bt_node/ai_behavior/use_mob_ability/random_honk/perform(seconds_per_tick, datum/ai_controller/controller) + if(!SPT_PROB(5, seconds_per_tick)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + return ..() + + + +/// Valid if the target is a visible human who isn't buckled and has gravity someone a slip can actually knock over. +/datum/targeting_strategy/can_see/slip_victim/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) . = ..() - var/atom/target = controller.blackboard[target_key] - if(QDELETED(target)) + if(!.) return FALSE - set_movement_target(controller, target) + var/mob/living/carbon/human/candidate = target + if(!istype(candidate)) + return FALSE + return !candidate.buckled && candidate.has_gravity() -/datum/ai_behavior/play_with_clown/perform(seconds_per_tick, datum/ai_controller/controller, target_key) + +// Positions the pulled victim onto the slippery item by stepping away, then releases. +/datum/bt_node/ai_behavior/release_and_slip + var/victim_key + +/datum/bt_node/ai_behavior/release_and_slip/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/victim = controller.blackboard[victim_key] + var/mob/living/our_mob = controller.pawn + if(QDELETED(victim) || our_mob.pulling != victim) + EVLOG_TEXT(controller, EVLOG_CATEGORY_AI_BEHAVIORS, "[our_mob] release_and_slip: not pulling victim") + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + EVLOG_MAPTEXT(controller, EVLOG_CATEGORY_AI_BEHAVIORS, "[our_mob] releasing [victim]!", get_turf(our_mob), "HONK!") + var/list/possible_dirs = GLOB.alldirs.Copy() + possible_dirs -= get_dir(our_mob, victim) + for(var/direction in possible_dirs) + var/turf/possible_turf = get_step(our_mob, direction) + if(possible_turf.is_blocked_turf(source_atom = our_mob)) + possible_dirs -= direction + if(length(possible_dirs)) + step(our_mob, pick(possible_dirs)) + our_mob.stop_pulling() + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + + +/// Gathers nearby atoms matching the slippery-item typepaths stored in BB_SLIPPERY_ITEMS. +/datum/target_source/honkbot_slippery + +/datum/target_source/honkbot_slippery/collect_candidates(mob/living/pawn, datum/ai_controller/controller, range) + var/list/slippery_items = controller.blackboard[BB_SLIPPERY_ITEMS] + if(!length(slippery_items)) + return list() + return typecache_filter_list(oview(range, pawn), typecacheof(slippery_items)) + +/// Valid only if the target is within line of sight (not just within range). +/datum/targeting_strategy/can_see/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + return can_see(living_mob, target, vision_range) + +/// Valid if the target is a conscious clown (by trait, or a borg running the clown model). +/datum/targeting_strategy/clown_friend/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + var/mob/living/target_mob = target + if(!isliving(target_mob) || target_mob.stat != CONSCIOUS) + return FALSE + if(HAS_TRAIT(target_mob, TRAIT_PERCEIVED_AS_CLOWN)) + return TRUE + if(istype(target_mob, /mob/living/silicon/robot)) + var/mob/living/silicon/robot/robot_target = target_mob + return istype(robot_target.model, /obj/item/robot_model/clown) + return FALSE + +/datum/bt_node/ai_behavior/play_with_clown + var/target_key + +/datum/bt_node/ai_behavior/play_with_clown/perform(seconds_per_tick, datum/ai_controller/controller) var/mob/living/living_target = controller.blackboard[target_key] if(QDELETED(living_target)) return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + if(get_dist(controller.pawn, living_target) > 1) + return AI_BEHAVIOR_INSTANT var/mob/living/living_pawn = controller.pawn var/datum/action/honk_ability = controller.blackboard[BB_HONK_ABILITY] honk_ability?.Trigger() living_pawn.manual_emote("celebrates with [living_target]!") - living_pawn.emote("flip") - living_pawn.emote("beep") + INVOKE_ASYNC(living_pawn, TYPE_PROC_REF(/mob, emote), "flip") + INVOKE_ASYNC(living_pawn, TYPE_PROC_REF(/mob, emote), "beep") return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED -/datum/ai_behavior/play_with_clown/finish_action(datum/ai_controller/basic_controller/bot/controller, succeeded, target_key, targeting_strategy_key, hiding_location_key) +/datum/bt_node/ai_behavior/play_with_clown/finish_action(datum/ai_controller/basic_controller/bot/controller, succeeded) . = ..() var/mob/living/living_target = controller.blackboard[target_key] - if(QDELETED(living_target)) - return - controller.add_to_blacklist(living_target) + if(!isnull(living_target)) + controller.add_to_blacklist(living_target) controller.clear_blackboard_key(target_key) - -/datum/ai_planning_subtree/slip_victims/SelectBehaviors(datum/ai_controller/basic_controller/bot/controller, seconds_per_tick) - var/mob/living/living_pawn = controller.pawn - if(!living_pawn.has_gravity()) - return - - var/atom/slippery_item = controller.blackboard[BB_SLIPPERY_TARGET] - if(QDELETED(slippery_item) || !can_see(controller.pawn, slippery_item, 5)) - controller.clear_blackboard_key(BB_SLIP_TARGET) - controller.clear_blackboard_key(BB_SLIPPERY_TARGET) - controller.queue_behavior(/datum/ai_behavior/bot_search, BB_SLIPPERY_TARGET, controller.blackboard[BB_SLIPPERY_ITEMS]) - return - - var/mob/living/living_target = controller.blackboard[BB_SLIP_TARGET] - - if(QDELETED(living_target)) - var/static/list/to_slip = typecacheof(list(/mob/living/carbon/human)) - controller.queue_behavior(/datum/ai_behavior/bot_search/slip_target, BB_SLIP_TARGET, to_slip) - return - - if(living_pawn.pulling == living_target) - controller.queue_behavior(/datum/ai_behavior/drag_to_slip, BB_SLIP_TARGET, BB_SLIPPERY_TARGET) - return SUBTREE_RETURN_FINISH_PLANNING - - controller.queue_behavior(/datum/ai_behavior/drag_target, BB_SLIP_TARGET) - return SUBTREE_RETURN_FINISH_PLANNING - -/datum/ai_behavior/bot_search/slip_target - -/datum/ai_behavior/bot_search/slip_target/valid_target(datum/ai_controller/basic_controller/bot/controller, mob/living/my_target) - return (!my_target.buckled && my_target.has_gravity()) - -/datum/ai_behavior/drag_to_slip - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION - required_distance = 0 - -/datum/ai_behavior/drag_to_slip/setup(datum/ai_controller/controller, slip_target, slippery_target) - . = ..() - var/atom/target = controller.blackboard[slippery_target] - if(QDELETED(target)) - return FALSE - set_movement_target(controller, target) - -/datum/ai_behavior/drag_to_slip/perform(seconds_per_tick, datum/ai_controller/controller, slip_target, slippery_target) - var/mob/living/our_pawn = controller.pawn - var/atom/living_target = controller.blackboard[slip_target] - if(QDELETED(living_target)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - var/list/possible_dirs = GLOB.alldirs.Copy() - possible_dirs -= get_dir(our_pawn, living_target) - for(var/direction in possible_dirs) - var/turf/possible_turf = get_step(our_pawn, direction) - if(possible_turf.is_blocked_turf(source_atom = our_pawn)) - possible_dirs -= direction - step(our_pawn, pick(possible_dirs)) - our_pawn.stop_pulling() - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -/datum/ai_behavior/drag_to_slip/finish_action(datum/ai_controller/basic_controller/bot/controller, success, slip_target, slippery_target) - . = ..() - if(success) - var/mob/living/living_pawn = controller.pawn - living_pawn.emote("flip") - var/atom/slipped_victim = controller.blackboard[slip_target] - if(!isnull(slipped_victim)) - controller.add_to_blacklist(slipped_victim) - controller.clear_blackboard_key(slip_target) - controller.clear_blackboard_key(slippery_target) - -/datum/ai_planning_subtree/use_mob_ability/random_honk - ability_key = BB_HONK_ABILITY - -/datum/ai_planning_subtree/use_mob_ability/random_honk/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(!SPT_PROB(5, seconds_per_tick)) - return - return ..() - diff --git a/code/modules/mob/living/basic/bots/honkbots/honkbot_slip.bt.json b/code/modules/mob/living/basic/bots/honkbots/honkbot_slip.bt.json new file mode 100644 index 00000000000..b54d73dc62b --- /dev/null +++ b/code/modules/mob/living/basic/bots/honkbots/honkbot_slip.bt.json @@ -0,0 +1,77 @@ +{ + "dm_type": "/datum/bt_node/subtree/honkbot_slip", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/can_see_target", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_SLIPPERY_TARGET", + "range": 5 + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/can_see_target", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_SLIP_TARGET", + "range": 5 + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/pawn_has_gravity", + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_SLIP_TARGET", + "required_dist": 1, + "finish_on_arrival": true, + "movement_type": "/datum/ai_movement/basic_avoidance" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/grab_target", + "vars": { + "target_key": "BB_SLIP_TARGET" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/is_grabbing_target", + "vars": { + "observer_abort": "BT_ABORT_SELF", + "key": "BB_SLIP_TARGET" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_SLIPPERY_TARGET", + "required_dist": 0, + "finish_on_arrival": true, + "movement_type": "/datum/ai_movement/basic_avoidance" + } + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/release_and_slip", + "vars": { + "victim_key": "BB_SLIP_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/perform_emote", + "vars": { + "emote": "flip" + } + } + ] + } + } + } +} diff --git a/code/modules/mob/living/basic/bots/hygienebot/hygienebot.bt.json b/code/modules/mob/living/basic/bots/hygienebot/hygienebot.bt.json new file mode 100644 index 00000000000..27d4acdcf32 --- /dev/null +++ b/code/modules/mob/living/basic/bots/hygienebot/hygienebot.bt.json @@ -0,0 +1,109 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/bot/hygienebot", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity/pacifist" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/bot_respond_to_summon" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_WASH_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "1 SECONDS", + "finish_on_primary": true, + "children": [ + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/wash_target", + "vars": { + "target_key": "BB_WASH_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_WASH_TARGET", + "required_dist": 0, + "finish_on_arrival": false, + "movement_type": "/datum/ai_movement/basic_avoidance" + } + } + ] + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/cooldown", + "vars": { + "observer_abort": "BT_ABORT_NONE", + "cooldown_key": "BB_TRASH_TALK_COOLDOWN", + "cooldown_duration": "4 SECONDS" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/commence_trashtalk", + "vars": { + "target_key": "BB_WASH_TARGET" + } + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/bot_patrol" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target/hygiene_wash", + "vars": { + "target_key": "BB_WASH_TARGET", + "target_source": "/datum/target_source/oview_single_type/human_mob", + "targeting_strategy": "/datum/targeting_strategy/conscious_human/washable_human", + "ignore_list_key": "BB_TEMPORARY_IGNORE_LIST", + "vision_range": 5, + "time_between_perform": "5 SECONDS" + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/bot_salute_authority" + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/bots/hygienebot/hygienebot_ai.dm b/code/modules/mob/living/basic/bots/hygienebot/hygienebot_ai.dm index f8e8e1edcdf..495ae571541 100644 --- a/code/modules/mob/living/basic/bots/hygienebot/hygienebot_ai.dm +++ b/code/modules/mob/living/basic/bots/hygienebot/hygienebot_ai.dm @@ -2,6 +2,7 @@ #define BOT_ANGER_THRESHOLD 5 /datum/ai_controller/basic_controller/bot/hygienebot + behavior_tree_json = "code/modules/mob/living/basic/bots/hygienebot/hygienebot.bt.json" blackboard = list( BB_SALUTE_MESSAGES = list( "salutes", @@ -9,14 +10,6 @@ ), BB_WASH_FRUSTRATION = 0, ) - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity/pacifist, - /datum/ai_planning_subtree/respond_to_summon, - /datum/ai_planning_subtree/handle_trash_talk, - /datum/ai_planning_subtree/wash_people, - /datum/ai_planning_subtree/salute_authority, - /datum/ai_planning_subtree/find_patrol_beacon, - ) reset_keys = list( BB_WASH_TARGET, BB_BEACON_TARGET, @@ -24,122 +17,68 @@ BB_BOT_SUMMON_TARGET, ) -/datum/ai_planning_subtree/handle_trash_talk -/datum/ai_planning_subtree/handle_trash_talk/SelectBehaviors(datum/ai_controller/basic_controller/bot/controller, seconds_per_tick) - if(!controller.blackboard_key_exists(BB_WASH_TARGET)) - return - controller.queue_behavior(/datum/ai_behavior/commence_trashtalk, BB_WASH_TARGET) -/datum/ai_behavior/commence_trashtalk - action_cooldown = 4 SECONDS +/datum/bt_node/ai_behavior/commence_trashtalk + var/target_key -/datum/ai_behavior/commence_trashtalk/perform(seconds_per_tick, datum/ai_controller/controller, target_key) +/datum/bt_node/ai_behavior/commence_trashtalk/perform(seconds_per_tick, datum/ai_controller/controller) if(!controller.blackboard_key_exists(target_key)) - return AI_BEHAVIOR_FAILED | AI_BEHAVIOR_DELAY + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED var/frustration_count = controller.blackboard[BB_WASH_FRUSTRATION] controller.set_blackboard_key(BB_WASH_FRUSTRATION, min(frustration_count + 1, BOT_FRUSTRATION_LIMIT)) if(controller.blackboard[BB_WASH_FRUSTRATION] < BOT_ANGER_THRESHOLD) - return AI_BEHAVIOR_FAILED | AI_BEHAVIOR_DELAY + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED var/datum/action/cooldown/bot_announcement/announcement = controller.blackboard[BB_ANNOUNCE_ABILITY] announcement?.announce(pick(controller.blackboard[BB_WASH_THREATS])) - return AI_BEHAVIOR_SUCCEEDED | AI_BEHAVIOR_DELAY - - -/datum/ai_planning_subtree/wash_people - -/datum/ai_planning_subtree/wash_people/SelectBehaviors(datum/ai_controller/basic_controller/bot/controller, seconds_per_tick) - var/mob/living/basic/bot/bot_pawn = controller.pawn - - var/atom/wash_target = controller.blackboard[BB_WASH_TARGET] - if(QDELETED(wash_target)) - controller.queue_behavior(/datum/ai_behavior/find_valid_wash_targets, BB_WASH_TARGET, bot_pawn.bot_access_flags) - return - - if(get_dist(bot_pawn, wash_target) < 9) - controller.queue_behavior(/datum/ai_behavior/wash_target, BB_WASH_TARGET) - return SUBTREE_RETURN_FINISH_PLANNING - - controller.clear_blackboard_key(BB_WASH_TARGET) //delete if too far - -/datum/ai_behavior/find_valid_wash_targets - action_cooldown = 5 SECONDS - -/datum/ai_behavior/find_valid_wash_targets/perform(seconds_per_tick, datum/ai_controller/basic_controller/bot/controller, target_key, our_access_flags) - . = ..() - var/list/ignore_list = controller.blackboard[BB_TEMPORARY_IGNORE_LIST] - var/atom/found_target - for(var/mob/living/carbon/human/wash_potential in oview(5, controller.pawn)) - - if(found_target) - break - - if(isnull(wash_potential.mind) || wash_potential.stat != CONSCIOUS) - continue - - if(LAZYACCESS(ignore_list, wash_potential)) - continue - - - // BUBBER EDIT ADDITION BEGIN - Dirty quirk - if (HAS_TRAIT(wash_potential, TRAIT_DIRTY)) - found_target = wash_potential - break - // BUGGER EDIT ADDITION END - - if(our_access_flags & BOT_COVER_EMAGGED) - controller.add_to_blacklist(wash_potential) - found_target = wash_potential - break - - for(var/atom/clothing in wash_potential.get_equipped_items(INCLUDE_HELD|INCLUDE_PROSTHETICS)) - if(GET_ATOM_BLOOD_DNA_LENGTH(clothing)) - found_target = wash_potential - break - - if(isnull(found_target)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - controller.set_blackboard_key(target_key, found_target) return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED -/datum/ai_behavior/find_valid_wash_targets/finish_action(datum/ai_controller/controller, succeeded, target_key) + + +/// Valid if the target is a conscious human with bloodied clothing (or anyone, while emagged). +/datum/targeting_strategy/conscious_human/washable_human/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) . = ..() - if(!succeeded) - return + if(!.) + return FALSE + var/mob/living/basic/bot/bot_pawn = living_mob + if(bot_pawn.bot_access_flags & BOT_COVER_EMAGGED) + return TRUE + var/mob/living/carbon/human/human_target = target + for(var/atom/clothing in human_target.get_equipped_items(INCLUDE_HELD|INCLUDE_PROSTHETICS)) + if(GET_ATOM_BLOOD_DNA_LENGTH(clothing)) + return TRUE + return FALSE + +/// Finds someone to wash and announces it; while emagged the target is blacklisted so the bot washes each person only once. +/datum/bt_node/ai_behavior/acquire_target/update_interaction_target/hygiene_wash + +/datum/bt_node/ai_behavior/acquire_target/update_interaction_target/hygiene_wash/on_target_found(datum/ai_controller/basic_controller/bot/controller, atom/target, datum/targeting_strategy/strategy) + var/mob/living/basic/bot/bot_pawn = controller.pawn + if(bot_pawn.bot_access_flags & BOT_COVER_EMAGGED) + controller.add_to_blacklist(target) var/datum/action/cooldown/bot_announcement/announcement = controller.blackboard[BB_ANNOUNCE_ABILITY] announcement.announce(pick(controller.blackboard[BB_WASH_FOUND])) -/datum/ai_behavior/wash_target - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION - required_distance = 0 -/datum/ai_behavior/wash_target/setup(datum/ai_controller/controller, target_key) - . = ..() - var/atom/target = controller.blackboard[target_key] - if(QDELETED(target)) - return FALSE - set_movement_target(controller, target) +/datum/bt_node/ai_behavior/wash_target + var/target_key -/datum/ai_behavior/wash_target/perform(seconds_per_tick, datum/ai_controller/basic_controller/bot/controller, target_key) - . = ..() +/datum/bt_node/ai_behavior/wash_target/perform(seconds_per_tick, datum/ai_controller/basic_controller/bot/controller) var/mob/living/carbon/human/unclean_target = controller.blackboard[target_key] var/mob/living/basic/living_pawn = controller.pawn if(QDELETED(unclean_target)) return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + if(get_dist(living_pawn, unclean_target) > 0) + return AI_BEHAVIOR_INSTANT + living_pawn.melee_attack(unclean_target) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - if(living_pawn.loc == get_turf(unclean_target)) - living_pawn.melee_attack(unclean_target) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - -/datum/ai_behavior/wash_target/finish_action(datum/ai_controller/controller, succeeded, target_key) +/datum/bt_node/ai_behavior/wash_target/finish_action(datum/ai_controller/controller, succeeded) . = ..() controller.clear_blackboard_key(target_key) var/wash_frustration = controller.blackboard[BB_WASH_FRUSTRATION] - controller.clear_blackboard_key(BB_WASH_FRUSTRATION) + controller.set_blackboard_key(BB_WASH_FRUSTRATION, 0) if(!succeeded || wash_frustration <= BOT_ANGER_THRESHOLD) return var/datum/action/cooldown/bot_announcement/announcement = controller.blackboard[BB_ANNOUNCE_ABILITY] diff --git a/code/modules/mob/living/basic/bots/medbot/medbot.bt.json b/code/modules/mob/living/basic/bots/medbot/medbot.bt.json new file mode 100644 index 00000000000..695f0cebe8c --- /dev/null +++ b/code/modules/mob/living/basic/bots/medbot/medbot.bt.json @@ -0,0 +1,109 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/bot/medbot", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity/pacifist" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/bot_respond_to_summon" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "1 SECONDS", + "finish_on_primary": false, + "children": [ + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": false, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": false, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bot_medical_flag", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "flag": "MEDBOT_DECLARE_CRIT" + }, + "child": { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/medbot_find_and_announce_crit" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bot_medical_flag", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "invert": true, + "flag": "MEDBOT_TIPPED_MODE" + }, + "child": { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/medbot_treat_patient" + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/bot_patrol" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_PATIENT_IN_CRIT", + "target_source": "/datum/target_source/oview_single_type/human_mob", + "targeting_strategy": "/datum/targeting_strategy/crit_patient", + "vision_range": 7 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target/medbot_patient", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "target_source": "/datum/target_source/oview_single_type/human_mob/medbot_patient", + "targeting_strategy": "/datum/targeting_strategy/treatable_patient", + "ignore_list_key": "BB_TEMPORARY_IGNORE_LIST", + "must_be_reachable": true, + "reach_distance": 20, + "vision_range": 7 + } + } + ] + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/bot_salute_authority" + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bot_medical_flag", + "vars": { + "flag": "MEDBOT_SPEAK_MODE" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/handle_medbot_speech", + "vars": { + "announce_key": "BB_ANNOUNCE_ABILITY" + } + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/bots/medbot/medbot_ai.dm b/code/modules/mob/living/basic/bots/medbot/medbot_ai.dm index 17f94f4ac1c..60035e0a8a0 100644 --- a/code/modules/mob/living/basic/bots/medbot/medbot_ai.dm +++ b/code/modules/mob/living/basic/bots/medbot/medbot_ai.dm @@ -1,17 +1,18 @@ #define BOT_PATIENT_PATH_LIMIT 20 + +/// Find and treat a patient used by both the speak-mode parallel and the silent fallback branch. +/datum/bt_node/subtree/medbot_treat_patient + behavior_tree_json = "code/modules/mob/living/basic/bots/medbot/medbot_treat_patient.bt.json" + +/// Find a patient in hard-crit and announce them on radio. +/datum/bt_node/subtree/medbot_find_and_announce_crit + behavior_tree_json = "code/modules/mob/living/basic/bots/medbot/medbot_find_and_announce_crit.bt.json" + /datum/ai_controller/basic_controller/bot/medbot - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity/pacifist, - /datum/ai_planning_subtree/respond_to_summon, - /datum/ai_planning_subtree/handle_medbot_speech, - /datum/ai_planning_subtree/find_and_hunt_target/patients_in_crit, - /datum/ai_planning_subtree/treat_wounded_target, - /datum/ai_planning_subtree/salute_authority, - /datum/ai_planning_subtree/find_patrol_beacon/medbot, - ) + behavior_tree_json = "code/modules/mob/living/basic/bots/medbot/medbot.bt.json" ai_movement = /datum/ai_movement/jps/bot/medbot reset_keys = list( - BB_PATIENT_TARGET, + BB_CURRENT_TARGET, BB_BEACON_TARGET, BB_PREVIOUS_BEACON_TARGET, BB_BOT_SUMMON_TARGET, @@ -21,7 +22,7 @@ maximum_length = BOT_PATIENT_PATH_LIMIT max_pathing_attempts = 20 -// only AI isnt allowed to move when this flag is set, sentient players can +// only AI isn't allowed to move when this flag is set, sentient players can /datum/ai_movement/jps/bot/medbot/allowed_to_move(datum/move_loop/source) var/datum/ai_controller/controller = source.extra_info var/mob/living/basic/bot/medbot/bot_pawn = controller.pawn @@ -33,176 +34,137 @@ maximum_length = AI_BOT_PATH_LENGTH -/datum/ai_planning_subtree/treat_wounded_target -/datum/ai_planning_subtree/treat_wounded_target/SelectBehaviors(datum/ai_controller/basic_controller/bot/controller, seconds_per_tick) - var/mob/living/basic/bot/medbot/bot_pawn = controller.pawn - if(bot_pawn.medical_mode_flags & MEDBOT_TIPPED_MODE) - controller.clear_blackboard_key(BB_PATIENT_TARGET) +/// Medbot's note_unreachable_target skips blacklisting while stationary, matching the old set_if_can_reach bypass. +/datum/ai_controller/basic_controller/bot/medbot/note_unreachable_target(atom/target) + var/mob/living/basic/bot/medbot/bot_pawn = pawn + if(bot_pawn.medical_mode_flags & MEDBOT_STATIONARY_MODE) return - var/is_stationary = bot_pawn.medical_mode_flags & MEDBOT_STATIONARY_MODE - if(controller.blackboard_key_exists(BB_PATIENT_TARGET)) - controller.queue_behavior(/datum/ai_behavior/tend_to_patient, BB_PATIENT_TARGET, bot_pawn.heal_threshold, bot_pawn.damage_type_healer, bot_pawn.bot_access_flags, is_stationary) - return SUBTREE_RETURN_FINISH_PLANNING + return ..() - controller.queue_behavior(/datum/ai_behavior/find_suitable_patient, BB_PATIENT_TARGET, bot_pawn.heal_threshold, bot_pawn.damage_type_healer, bot_pawn.medical_mode_flags, bot_pawn.bot_access_flags) +/// Gathers nearby humans as patients; range is clamped to adjacent tiles when the medbot is in stationary mode. I should probably just make this a blackboard thing but I cannot be arsed right now. +/datum/target_source/oview_single_type/human_mob/medbot_patient -/datum/ai_behavior/find_suitable_patient - var/search_range = 7 - action_cooldown = 2 SECONDS +/datum/target_source/oview_single_type/human_mob/medbot_patient/collect_candidates(mob/living/pawn, datum/ai_controller/controller, range) + var/mob/living/basic/bot/medbot/bot_pawn = pawn + if(bot_pawn.medical_mode_flags & MEDBOT_STATIONARY_MODE) + range = 1 + return ..(pawn, controller, range) -/datum/ai_behavior/find_suitable_patient/perform(seconds_per_tick, datum/ai_controller/basic_controller/bot/controller, target_key, threshold, heal_type, mode_flags, access_flags) - search_range = (mode_flags & MEDBOT_STATIONARY_MODE) ? 1 : initial(search_range) - var/list/ignore_keys = controller.blackboard[BB_TEMPORARY_IGNORE_LIST] - for(var/mob/living/carbon/human/treatable_target in oview(search_range, controller.pawn)) - // BUBBER EDIT ADDITION START - Don't hunt down synthetics - if(treatable_target.mob_biotypes & MOB_ROBOTIC) - continue - // BUBBER EDIT ADDITION END - if(LAZYACCESS(ignore_keys, treatable_target) || treatable_target.stat == DEAD) - continue - if((access_flags & BOT_COVER_EMAGGED) && treatable_target.stat == CONSCIOUS) - controller.set_if_can_reach(key = BB_PATIENT_TARGET, target = treatable_target, distance = BOT_PATIENT_PATH_LIMIT, bypass_add_to_blacklist = (search_range == 1)) - break - if((heal_type == HEAL_ALL_DAMAGE)) - if(treatable_target.get_total_damage() > threshold) - controller.set_if_can_reach(key = BB_PATIENT_TARGET, target = treatable_target, distance = BOT_PATIENT_PATH_LIMIT, bypass_add_to_blacklist = (search_range == 1)) - break - continue - if(treatable_target.get_current_damage_of_type(damagetype = heal_type) > threshold) - controller.set_if_can_reach(key = BB_PATIENT_TARGET, target = treatable_target, distance = BOT_PATIENT_PATH_LIMIT, bypass_add_to_blacklist = (search_range == 1)) - break - - if(controller.blackboard_key_exists(BB_PATIENT_TARGET)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - else - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - -/datum/ai_behavior/find_suitable_patient/finish_action(datum/ai_controller/controller, succeeded, target_key) +/// Valid if the patient needs the damage type this medbot heals (or is a conscious target while emagged). +/datum/targeting_strategy/treatable_patient/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) . = ..() - if(!succeeded || QDELETED(controller.pawn) ||get_dist(controller.pawn, controller.blackboard[target_key]) <= 1) + if(!.) + return FALSE + var/mob/living/carbon/human/patient = target + if(!istype(patient) || patient.stat == DEAD) + return FALSE + var/mob/living/basic/bot/medbot/bot_pawn = living_mob + if((bot_pawn.bot_access_flags & BOT_COVER_EMAGGED) && patient.stat == CONSCIOUS) + return TRUE + if(bot_pawn.damage_type_healer == HEAL_ALL_DAMAGE) + return patient.get_total_damage() > bot_pawn.heal_threshold + return patient.get_current_damage_of_type(damagetype = bot_pawn.damage_type_healer) > bot_pawn.heal_threshold + +/// Finds a patient to treat, announcing that the bot is on its way when the patient isn't already adjacent. +/datum/bt_node/ai_behavior/acquire_target/update_interaction_target/medbot_patient + +/datum/bt_node/ai_behavior/acquire_target/update_interaction_target/medbot_patient/on_target_found(datum/ai_controller/controller, atom/target, datum/targeting_strategy/strategy) + if(QDELETED(controller.pawn) || get_dist(controller.pawn, target) <= 1) return var/datum/action/cooldown/bot_announcement/announcement = controller.blackboard[BB_ANNOUNCE_ABILITY] announcement?.announce(pick(controller.blackboard[BB_WAIT_SPEECH])) -/datum/ai_behavior/tend_to_patient - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION | AI_BEHAVIOR_REQUIRE_REACH -/datum/ai_behavior/tend_to_patient/setup(datum/ai_controller/controller, target_key) - . = ..() - var/atom/target = controller.blackboard[target_key] - if(QDELETED(target)) - return FALSE - set_movement_target(controller, target) -/datum/ai_behavior/tend_to_patient/perform(seconds_per_tick, datum/ai_controller/basic_controller/bot/controller, target_key, threshold, damage_type_healer, access_flags, is_stationary) +/datum/bt_node/ai_behavior/tend_to_patient + var/target_key + +/datum/bt_node/ai_behavior/tend_to_patient/perform(seconds_per_tick, datum/ai_controller/basic_controller/bot/controller) var/mob/living/carbon/human/patient = controller.blackboard[target_key] if(QDELETED(patient) || patient.stat == DEAD) + EVLOG_TEXT(controller, EVLOG_CATEGORY_AI_BEHAVIORS, "[controller.pawn] tend_to_patient: patient gone (deleted=[QDELETED(patient)], stat=[patient?.stat])") return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - if(check_if_healed(patient, threshold, damage_type_healer, access_flags)) + if(get_dist(controller.pawn, patient) > 1) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED //We technically failed, but we want to try again so succeed. + var/mob/living/basic/bot/medbot/bot_pawn = controller.pawn + if(check_if_healed(patient, bot_pawn.heal_threshold, bot_pawn.damage_type_healer, bot_pawn.bot_access_flags)) + EVLOG_TEXT(controller, EVLOG_CATEGORY_AI_BEHAVIORS, "[bot_pawn] tend_to_patient: [patient] is fully healed") return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - - var/mob/living/basic/bot/bot_pawn = controller.pawn if(patient.stat >= HARD_CRIT && prob(5)) var/datum/action/cooldown/bot_announcement/announcement = controller.blackboard[BB_ANNOUNCE_ABILITY] announcement?.announce(pick(controller.blackboard[BB_NEAR_DEATH_SPEECH])) + EVLOG_MAPTEXT(controller, EVLOG_CATEGORY_AI_BEHAVIORS, "[bot_pawn] healing [patient] (dmg=[patient.get_total_damage()])", get_turf(patient), "Heal") bot_pawn.melee_attack(patient) return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED -// only clear the target if they get healed -/datum/ai_behavior/tend_to_patient/finish_action(datum/ai_controller/basic_controller/bot/controller, succeeded, target_key, threshold, damage_type_healer, access_flags, is_stationary) +/datum/bt_node/ai_behavior/tend_to_patient/finish_action(datum/ai_controller/basic_controller/bot/controller, succeeded) . = ..() + var/mob/living/basic/bot/medbot/bot_pawn = controller.pawn var/atom/target = controller.blackboard[target_key] + var/is_stationary = bot_pawn.medical_mode_flags & MEDBOT_STATIONARY_MODE if(!succeeded) - if(!isnull(target) && !is_stationary) controller.add_to_blacklist(target) - controller.clear_blackboard_key(target_key) return - - if(QDELETED(target) || !check_if_healed(target, threshold, damage_type_healer, access_flags)) + if(QDELETED(target) || !check_if_healed(target, bot_pawn.heal_threshold, bot_pawn.damage_type_healer, bot_pawn.bot_access_flags)) return - var/datum/action/cooldown/bot_announcement/announcement = controller.blackboard[BB_ANNOUNCE_ABILITY] announcement?.announce(pick(controller.blackboard[BB_AFTERHEAL_SPEECH])) controller.clear_blackboard_key(target_key) -/datum/ai_behavior/tend_to_patient/proc/check_if_healed(mob/living/carbon/human/patient, threshold, damage_type_healer, access_flags) +/datum/bt_node/ai_behavior/tend_to_patient/proc/check_if_healed(mob/living/carbon/human/patient, threshold, damage_type_healer, access_flags) if(access_flags & BOT_COVER_EMAGGED) return (patient.stat > CONSCIOUS) var/patient_damage = (damage_type_healer == HEAL_ALL_DAMAGE) ? patient.get_total_damage() : patient.get_current_damage_of_type(damagetype = damage_type_healer) return (patient_damage <= threshold) -/datum/ai_planning_subtree/handle_medbot_speech - var/speech_chance = 5 -/datum/ai_planning_subtree/handle_medbot_speech/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) +/datum/bt_node/ai_behavior/handle_medbot_speech + var/announce_key + time_between_perform = 20 SECONDS + +/datum/bt_node/ai_behavior/handle_medbot_speech/perform(seconds_per_tick, datum/ai_controller/controller) var/mob/living/basic/bot/medbot/bot_pawn = controller.pawn - //we cant speak! - if(!(bot_pawn.medical_mode_flags & MEDBOT_SPEAK_MODE)) - return - var/currently_tipped = bot_pawn.medical_mode_flags & MEDBOT_TIPPED_MODE - speech_chance = ((bot_pawn.bot_access_flags & BOT_COVER_EMAGGED) || currently_tipped) ? 15 : initial(speech_chance) - + var/speech_chance = ((bot_pawn.bot_access_flags & BOT_COVER_EMAGGED) || currently_tipped) ? 15 : 5 if(!SPT_PROB(speech_chance, seconds_per_tick)) - return - - controller.queue_behavior(/datum/ai_behavior/handle_medbot_speech, BB_ANNOUNCE_ABILITY, bot_pawn.mode, bot_pawn.bot_access_flags, currently_tipped) - -/datum/ai_behavior/handle_medbot_speech - action_cooldown = 20 SECONDS - behavior_flags = AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION - -/datum/ai_behavior/handle_medbot_speech/perform(seconds_per_tick, datum/ai_controller/controller, announce_key, mode, cover_flags, currently_tipped) - . = ..() + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED var/datum/action/cooldown/bot_announcement/announcement = controller.blackboard[announce_key] var/list/speech_to_pick_from - if(currently_tipped) speech_to_pick_from = controller.blackboard[BB_WORRIED_ANNOUNCEMENTS] - else if(cover_flags & BOT_COVER_EMAGGED) + else if(bot_pawn.bot_access_flags & BOT_COVER_EMAGGED) speech_to_pick_from = controller.blackboard[BB_EMAGGED_SPEECH] - else if(mode == BOT_IDLE) + else if(bot_pawn.mode == BOT_IDLE) speech_to_pick_from = controller.blackboard[BB_IDLE_SPEECH] var/mob/living/living_pawn = controller.pawn - if(locate(/obj/item/clothing/head/costume/chicken) in living_pawn) speech_to_pick_from += MEDIBOT_VOICED_CHICKEN - if(!length(speech_to_pick_from)) return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - announcement.announce(pick(speech_to_pick_from)) return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED -/datum/ai_planning_subtree/find_and_hunt_target/patients_in_crit - target_key = BB_PATIENT_IN_CRIT - hunting_behavior = /datum/ai_behavior/announce_patient - finding_behavior = /datum/ai_behavior/find_hunt_target/patient_in_crit - hunt_targets = list(/mob/living/carbon/human) - finish_planning = FALSE -/datum/ai_planning_subtree/find_and_hunt_target/patients_in_crit/SelectBehaviors(datum/ai_controller/basic_controller/bot/controller, seconds_per_tick) - var/mob/living/basic/bot/medbot/bot_pawn = controller.pawn - if(!(bot_pawn.medical_mode_flags & MEDBOT_DECLARE_CRIT)) - return - return ..() -/datum/ai_behavior/find_hunt_target/patient_in_crit - -/datum/ai_behavior/find_hunt_target/patient_in_crit/valid_dinner(mob/living/source, mob/living/carbon/human/patient, radius) - if(patient.stat < UNCONSCIOUS || isnull(patient.mind)) +/// Valid if the patient is at least unconscious, has a mind, and is visible used to announce medical emergencies. +/datum/targeting_strategy/crit_patient/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) return FALSE - return can_see(source, patient, radius) + var/mob/living/carbon/human/patient = target + if(!istype(patient) || patient.stat < UNCONSCIOUS || isnull(patient.mind)) + return FALSE + return can_see(living_mob, patient, vision_range) -/datum/ai_behavior/announce_patient - action_cooldown = 3 MINUTES - behavior_flags = AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION +/datum/bt_node/ai_behavior/announce_patient + var/target_key + time_between_perform = 3 MINUTES -/datum/ai_behavior/announce_patient/perform(seconds_per_tick, datum/ai_controller/basic_controller/bot/controller, target_key) +/datum/bt_node/ai_behavior/announce_patient/perform(seconds_per_tick, datum/ai_controller/basic_controller/bot/controller) var/mob/living/living_target = controller.blackboard[target_key] if(QDELETED(living_target)) return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED @@ -213,22 +175,8 @@ announcement.announce(text_to_announce, controller.blackboard[BB_RADIO_CHANNEL]) return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED -/datum/ai_behavior/announce_patient/finish_action(datum/ai_controller/controller, succeeded, target_key) +/datum/bt_node/ai_behavior/announce_patient/finish_action(datum/ai_controller/controller, succeeded) . = ..() controller.clear_blackboard_key(target_key) -/datum/ai_planning_subtree/find_patrol_beacon/medbot - ///travel towards beacon behavior - travel_behavior = /datum/ai_behavior/travel_towards/beacon/medbot - -/datum/ai_planning_subtree/find_patrol_beacon/medbot/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/mob/living/basic/bot/medbot/bot_pawn = controller.pawn - if(bot_pawn.medical_mode_flags & MEDBOT_STATIONARY_MODE) - return - return ..() - - -/datum/ai_behavior/travel_towards/beacon/medbot - new_movement_type = /datum/ai_movement/jps/bot/medbot/travel_to_beacon - #undef BOT_PATIENT_PATH_LIMIT diff --git a/code/modules/mob/living/basic/bots/medbot/medbot_find_and_announce_crit.bt.json b/code/modules/mob/living/basic/bots/medbot/medbot_find_and_announce_crit.bt.json new file mode 100644 index 00000000000..48d29e9b23c --- /dev/null +++ b/code/modules/mob/living/basic/bots/medbot/medbot_find_and_announce_crit.bt.json @@ -0,0 +1,16 @@ +{ + "dm_type": "/datum/bt_node/subtree/medbot_find_and_announce_crit", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_PATIENT_IN_CRIT" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/announce_patient", + "vars": { + "target_key": "BB_PATIENT_IN_CRIT" + } + } +} diff --git a/code/modules/mob/living/basic/bots/medbot/medbot_treat_patient.bt.json b/code/modules/mob/living/basic/bots/medbot/medbot_treat_patient.bt.json new file mode 100644 index 00000000000..eb4da905953 --- /dev/null +++ b/code/modules/mob/living/basic/bots/medbot/medbot_treat_patient.bt.json @@ -0,0 +1,28 @@ +{ + "dm_type": "/datum/bt_node/subtree/medbot_treat_patient", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/tend_to_patient", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + } + ] + } +} diff --git a/code/modules/mob/living/basic/bots/mulebot/mulebot.bt.json b/code/modules/mob/living/basic/bots/mulebot/mulebot.bt.json new file mode 100644 index 00000000000..7b0d49a1a86 --- /dev/null +++ b/code/modules/mob/living/basic/bots/mulebot/mulebot.bt.json @@ -0,0 +1,103 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/bot/mulebot", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "1 SECONDS", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/bot_respond_to_summon" + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_MULEBOT_TRAVEL_TARGET" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_MULEBOT_TRAVEL_TARGET", + "required_dist": 0, + "finish_on_arrival": true + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/handle_delivery", + "vars": { + "target_key": "BB_MULEBOT_TRAVEL_TARGET" + } + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bot_wire_cut", + "vars": { + "wire": "WIRE_BEACON", + "invert": true + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_MULEBOT_TRAVEL_TARGET", + "invert": true + }, + "child": { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bot_mode", + "vars": { + "mode": "BOT_DELIVER" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/find_delivery_beacon", + "vars": { + "target_key": "BB_MULEBOT_TRAVEL_TARGET", + "tag_key": "BB_MULEBOT_DESTINATION_BEACON" + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bot_mode", + "vars": { + "mode": "BOT_GO_HOME" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/find_delivery_beacon", + "vars": { + "target_key": "BB_MULEBOT_TRAVEL_TARGET", + "tag_key": "BB_MULEBOT_HOME_BEACON" + } + } + } + ] + } + } + } + ] + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/bot_salute_authority" + } + ] +} diff --git a/code/modules/mob/living/basic/bots/mulebot/mulebot.dm b/code/modules/mob/living/basic/bots/mulebot/mulebot.dm index 55d5632e5e1..9367a81d6d5 100644 --- a/code/modules/mob/living/basic/bots/mulebot/mulebot.dm +++ b/code/modules/mob/living/basic/bots/mulebot/mulebot.dm @@ -165,7 +165,7 @@ . = ..() if(mode != BOT_BLOCKED) return - var/obj/machinery/navbeacon/beacon = ai_controller.current_movement_target + var/obj/machinery/navbeacon/beacon = ai_controller.blackboard[BB_CURRENT_MOVEMENT_TARGET] if(!istype(beacon)) return var/intended_mode = beacon.location == ai_controller.blackboard[BB_MULEBOT_HOME_BEACON] ? BOT_GO_HOME : BOT_DELIVER diff --git a/code/modules/mob/living/basic/bots/mulebot/mulebot_ai.dm b/code/modules/mob/living/basic/bots/mulebot/mulebot_ai.dm index 7898ced148c..c5fba554619 100644 --- a/code/modules/mob/living/basic/bots/mulebot/mulebot_ai.dm +++ b/code/modules/mob/living/basic/bots/mulebot/mulebot_ai.dm @@ -1,4 +1,5 @@ /datum/ai_controller/basic_controller/bot/mulebot + behavior_tree_json = "code/modules/mob/living/basic/bots/mulebot/mulebot.bt.json" blackboard = list( BB_SALUTE_MESSAGES = list( "blinks its light in appreciation towards", @@ -6,12 +7,6 @@ ) ai_movement = /datum/ai_movement/jps/bot/mulebot max_target_distance = AI_MULEBOT_PATH_LENGTH - planning_subtrees = list( - /datum/ai_planning_subtree/respond_to_summon, - /datum/ai_planning_subtree/salute_authority, - /datum/ai_planning_subtree/attempt_delivery, - /datum/ai_planning_subtree/find_delivery_beacon, - ) reset_keys = list( BB_BOT_SUMMON_TARGET, BB_MULEBOT_DESTINATION_BEACON, @@ -40,72 +35,15 @@ ) RegisterSignals(my_bot, content_signals, PROC_REF(update_able_to_run)) -/datum/ai_planning_subtree/find_delivery_beacon - ///what behavior do we use to seek beacons - var/find_beacon_behaviour = /datum/ai_behavior/find_delivery_beacon +/// Loads or unloads cargo at the delivery beacon held in target_key, then heads home or idles. +/datum/bt_node/ai_behavior/handle_delivery + var/target_key + time_between_perform = 1 SECONDS -/datum/ai_planning_subtree/find_delivery_beacon/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/mob/living/basic/bot/mulebot/bot_pawn = controller.pawn - if(bot_pawn.wires.is_cut(WIRE_BEACON)) - return - - if(!controller.blackboard_key_exists(BB_MULEBOT_TRAVEL_TARGET)) - controller.queue_behavior(find_beacon_behaviour, BB_MULEBOT_TRAVEL_TARGET) - -/datum/ai_behavior/find_delivery_beacon - behavior_flags = AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION - -/datum/ai_behavior/find_delivery_beacon/perform(seconds_per_tick, datum/ai_controller/controller, target_key) - var/mob/living/basic/bot/mulebot/bot_pawn = controller.pawn - var/atom/delivery_beacon - - var/beacon_tag = null - - switch(bot_pawn.mode) - if(BOT_DELIVER) - beacon_tag = controller.blackboard[BB_MULEBOT_DESTINATION_BEACON] - if(BOT_GO_HOME) - beacon_tag = controller.blackboard[BB_MULEBOT_HOME_BEACON] - else - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - for(var/obj/machinery/navbeacon/beacon as anything in GLOB.deliverybeacons) - if(beacon.location == beacon_tag) - delivery_beacon = beacon - break - - if(isnull(delivery_beacon)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - controller.set_blackboard_key(BB_MULEBOT_TRAVEL_TARGET, delivery_beacon) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -/datum/ai_behavior/travel_towards/delivery_beacon - new_movement_type = /datum/ai_movement/jps/bot/mulebot - -/datum/ai_planning_subtree/attempt_delivery - ///behavior we use to unload crates - var/delivery_behaviour = /datum/ai_behavior/handle_delivery - -/datum/ai_planning_subtree/attempt_delivery/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(!controller.blackboard_key_exists(BB_MULEBOT_TRAVEL_TARGET)) - return - - controller.queue_behavior(delivery_behaviour, BB_MULEBOT_TRAVEL_TARGET) - return SUBTREE_RETURN_FINISH_PLANNING - -/datum/ai_behavior/handle_delivery - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH - -/datum/ai_behavior/handle_delivery/setup(datum/ai_controller/controller, target_key) - . = ..() - var/atom/target = controller.blackboard[target_key] - if(QDELETED(target)) - return FALSE - set_movement_target(controller, target) - -/datum/ai_behavior/handle_delivery/perform(seconds_per_tick, datum/ai_controller/controller, target_key) +/datum/bt_node/ai_behavior/handle_delivery/perform(seconds_per_tick, datum/ai_controller/controller) var/obj/machinery/navbeacon/beacon = controller.blackboard[target_key] + if(QDELETED(beacon)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED var/mob/living/basic/bot/mulebot/bot_pawn = controller.pawn var/load_direction = beacon.codes[NAVBEACON_DELIVERY_DIRECTION] // this will be the load/unload dir diff --git a/code/modules/mob/living/basic/bots/repairbot/repairbot.bt.json b/code/modules/mob/living/basic/bots/repairbot/repairbot.bt.json new file mode 100644 index 00000000000..2949201eb08 --- /dev/null +++ b/code/modules/mob/living/basic/bots/repairbot/repairbot.bt.json @@ -0,0 +1,32 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/bot/repairbot", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity/pacifist" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/repairbot_repair_target" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/repairbot_find_target" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/bot_salute_authority" + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/bots/repairbot/repairbot_ai.dm b/code/modules/mob/living/basic/bots/repairbot/repairbot_ai.dm index 621d0033895..41c2330fdb8 100644 --- a/code/modules/mob/living/basic/bots/repairbot/repairbot_ai.dm +++ b/code/modules/mob/living/basic/bots/repairbot/repairbot_ai.dm @@ -1,52 +1,104 @@ #define REPAIRBOT_SPEECH_TIMER 30 SECONDS +/// Emagged repairbot behavior: mug robots then deconstruct structures. +/datum/bt_node/subtree/repairbot_emagged + behavior_tree_json = "code/modules/mob/living/basic/bots/repairbot/repairbot_emagged.bt.json" + +/datum/bt_node/subtree/repairbot_repair_target + behavior_tree_json = "code/modules/mob/living/basic/bots/repairbot/repairbot_repair_target.bt.json" + +/datum/bt_node/subtree/repairbot_find_target + behavior_tree_json = "code/modules/mob/living/basic/bots/repairbot/repairbot_find_target.bt.json" + /datum/ai_controller/basic_controller/bot/repairbot - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity/pacifist, - /datum/ai_planning_subtree/repairbot_speech, - /datum/ai_planning_subtree/mug_robot, - /datum/ai_planning_subtree/refill_materials, - /datum/ai_planning_subtree/repairbot_deconstruction, - /datum/ai_planning_subtree/respond_to_summon, - /datum/ai_planning_subtree/replace_floors/breaches, - /datum/ai_planning_subtree/wall_girder, - /datum/ai_planning_subtree/build_girder, - /datum/ai_planning_subtree/replace_window, - /datum/ai_planning_subtree/replace_floors, - /datum/ai_planning_subtree/fix_window, - /datum/ai_planning_subtree/salute_authority, - /datum/ai_planning_subtree/find_patrol_beacon, - ) + behavior_tree_json = "code/modules/mob/living/basic/bots/repairbot/repairbot.bt.json" + + + reset_keys = list( - BB_TILELESS_FLOOR, - BB_GIRDER_TARGET, - BB_GIRDER_TO_WALL_TARGET, BB_BEACON_TARGET, BB_PREVIOUS_BEACON_TARGET, - BB_WELDER_TARGET, - BB_WINDOW_FRAMETARGET, + BB_CURRENT_TARGET, ) - minimum_distance = 1 -///subtree to refill our stacks -/datum/ai_planning_subtree/refill_materials -/datum/ai_planning_subtree/refill_materials/SelectBehaviors(datum/ai_controller/basic_controller/bot/controller, seconds_per_tick) - var/static/list/refillable_items = typecacheof(list( - /obj/item/stack/sheet/iron, - /obj/item/stack/sheet/glass, - /obj/item/stack/tile, + +/datum/bt_node/ai_behavior/repairbot_speech + +/datum/bt_node/ai_behavior/repairbot_speech/setup(datum/ai_controller/controller) + if(controller.blackboard[BB_REPAIRBOT_SPEECH_COOLDOWN] > world.time) + return FALSE + var/static/list/keys_to_look = list( + BB_CURRENT_TARGET, + BB_DECONSTRUCT_TARGET, + ) + for(var/key in keys_to_look) + if(controller.blackboard_key_exists(key)) + return ..() + return FALSE + +/datum/bt_node/ai_behavior/repairbot_speech/perform(seconds_per_tick, datum/ai_controller/controller) + var/datum/action/cooldown/bot_announcement/announcement = controller.blackboard[BB_ANNOUNCE_ABILITY] + // determine speech type: emagged -> emagged speech, otherwise normal + var/list/speech_to_pick_from + if(controller.blackboard_key_exists(BB_DECONSTRUCT_TARGET)) + speech_to_pick_from = controller.blackboard[BB_REPAIRBOT_EMAGGED_SPEECH] + else + speech_to_pick_from = controller.blackboard[BB_REPAIRBOT_NORMAL_SPEECH] + if(!length(speech_to_pick_from)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + announcement.announce(pick(speech_to_pick_from)) + controller.set_blackboard_key(BB_REPAIRBOT_SPEECH_COOLDOWN, world.time + REPAIRBOT_SPEECH_TIMER) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + + + +/datum/bt_node/ai_behavior/bot_interact/tip_robot + +/datum/bt_node/ai_behavior/bot_interact/tip_robot/setup(datum/ai_controller/controller) + var/mob/living/target = controller.blackboard[target_key] + var/mob/living/pawn = controller.pawn + if(QDELETED(target) || pawn.pulling != target) + return FALSE + return ..() + +/datum/bt_node/ai_behavior/bot_interact/tip_robot/finish_action(datum/ai_controller/controller, succeeded) + . = ..() + if(succeeded) + var/mob/living/living_pawn = controller.pawn + living_pawn.stop_pulling() + +/datum/bt_node/ai_behavior/bot_search/valid_robot + time_between_perform = 10 SECONDS + +/datum/bt_node/ai_behavior/bot_search/valid_robot/valid_target(datum/ai_controller/basic_controller/bot/controller, atom/my_target) + if(!istype(my_target, /mob/living/silicon/robot)) + return FALSE + return (!HAS_TRAIT(my_target, TRAIT_MOB_TIPPED)) && can_see(controller.pawn, my_target) + + + +/datum/bt_node/ai_behavior/bot_search/deconstructable + time_between_perform = 5 SECONDS + +/datum/bt_node/ai_behavior/bot_search/deconstructable/valid_target(datum/ai_controller/basic_controller/bot/controller, atom/my_target) + return (!(my_target.resistance_flags & INDESTRUCTIBLE) && !isgroundlessturf(my_target)) + +/datum/bt_node/ai_behavior/bot_search/deconstructable/perform(seconds_per_tick, datum/ai_controller/basic_controller/bot/controller) + var/static/list/things_to_deconstruct = typecacheof(list( + /obj/structure/window, + /turf/open/floor, + /turf/closed/wall, )) - if(!controller.blackboard_key_exists(BB_REFILLABLE_TARGET)) - controller.queue_behavior(/datum/ai_behavior/bot_search/refillable_target, BB_REFILLABLE_TARGET, refillable_items) - return - controller.queue_behavior(/datum/ai_behavior/bot_interact, BB_REFILLABLE_TARGET) - return SUBTREE_RETURN_FINISH_PLANNING + looking_for = things_to_deconstruct + return ..() -/datum/ai_behavior/bot_search/refillable_target - action_cooldown = 10 SECONDS -/datum/ai_behavior/bot_search/refillable_target/valid_target(datum/ai_controller/basic_controller/bot/controller, atom/my_target) + +/datum/bt_node/ai_behavior/bot_search/refillable_target + time_between_perform = 10 SECONDS + +/datum/bt_node/ai_behavior/bot_search/refillable_target/valid_target(datum/ai_controller/basic_controller/bot/controller, atom/my_target) var/static/list/desired_types = list( /obj/item/stack/sheet/iron, /obj/item/stack/sheet/glass, @@ -62,139 +114,45 @@ return TRUE return FALSE -/datum/ai_planning_subtree/mug_robot - -/datum/ai_planning_subtree/mug_robot/SelectBehaviors(datum/ai_controller/basic_controller/bot/controller, seconds_per_tick) - var/mob/living/basic/bot/living_bot = controller.pawn - if(!(living_bot.bot_access_flags & BOT_COVER_EMAGGED)) - return - var/static/list/robot_targets = typecacheof( - /mob/living/silicon/robot, - ) - if(!controller.blackboard_key_exists(BB_ROBOT_TARGET)) - controller.queue_behavior(/datum/ai_behavior/bot_search/valid_robot, BB_ROBOT_TARGET, robot_targets) - return - if(!living_bot.pulling) - controller.queue_behavior(/datum/ai_behavior/drag_target, BB_ROBOT_TARGET) - else - controller.queue_behavior(/datum/ai_behavior/bot_interact/tip_robot, BB_ROBOT_TARGET) - return SUBTREE_RETURN_FINISH_PLANNING - -/datum/ai_behavior/bot_search/valid_robot - action_cooldown = 10 SECONDS - -/datum/ai_behavior/bot_search/valid_robot/valid_target(datum/ai_controller/basic_controller/bot/controller, atom/my_target) - return (!HAS_TRAIT(my_target, TRAIT_MOB_TIPPED)) && can_see(controller.pawn, my_target) - -/datum/ai_behavior/bot_interact/tip_robot - -/datum/ai_behavior/bot_interact/tip_robot/finish_action(datum/ai_controller/controller, succeeded, target_key) - . = ..() - if(succeeded) - var/mob/living/living_pawn = controller.pawn - living_pawn.stop_pulling() - -///subtree to deconstruct things when we're emagged -/datum/ai_planning_subtree/repairbot_deconstruction - -/datum/ai_planning_subtree/repairbot_deconstruction/SelectBehaviors(datum/ai_controller/basic_controller/bot/controller, seconds_per_tick) - var/mob/living/basic/bot/living_bot = controller.pawn - if(!(living_bot.bot_access_flags & BOT_COVER_EMAGGED)) - return - var/static/list/things_to_deconstruct = typecacheof(list( - /obj/structure/window, - /turf/open/floor, - /turf/closed/wall, +/datum/bt_node/ai_behavior/bot_search/refillable_target/perform(seconds_per_tick, datum/ai_controller/basic_controller/bot/controller) + var/static/list/refillable_items = typecacheof(list( + /obj/item/stack/sheet/iron, + /obj/item/stack/sheet/glass, + /obj/item/stack/tile, )) - if(!controller.blackboard_key_exists(BB_DECONSTRUCT_TARGET)) - controller.queue_behavior(/datum/ai_behavior/bot_search/deconstructable, BB_DECONSTRUCT_TARGET, things_to_deconstruct) - return SUBTREE_RETURN_FINISH_PLANNING - controller.queue_behavior(/datum/ai_behavior/bot_interact, BB_DECONSTRUCT_TARGET) - return SUBTREE_RETURN_FINISH_PLANNING + looking_for = refillable_items + return ..() -/datum/ai_behavior/bot_search/deconstructable - action_cooldown = 5 SECONDS -/datum/ai_behavior/bot_search/deconstructable/valid_target(datum/ai_controller/basic_controller/bot/controller, atom/my_target) - return (!(my_target.resistance_flags & INDESTRUCTIBLE) && !isgroundlessturf(my_target)) +/datum/bt_node/ai_behavior/bot_search/valid_plateless_turf + turf_search = TRUE + time_between_perform = 5 SECONDS -///subtree to control bot speech -/datum/ai_planning_subtree/repairbot_speech +/datum/bt_node/ai_behavior/bot_search/valid_plateless_turf/proc/get_turf_type_filter() + return typecacheof(list(/turf/open/floor/plating)) -/datum/ai_planning_subtree/repairbot_speech/SelectBehaviors(datum/ai_controller/basic_controller/bot/controller, seconds_per_tick) - if(controller.blackboard[BB_REPAIRBOT_SPEECH_COOLDOWN] > world.time) - return - var/static/list/keys_to_look = list( - BB_WELDER_TARGET, - BB_WINDOW_FRAMETARGET, - BB_TILELESS_FLOOR, - BB_BREACHED_FLOOR, - BB_GIRDER_TO_WALL_TARGET, - BB_GIRDER_TARGET, - BB_DECONSTRUCT_TARGET, - ) - for(var/key in keys_to_look) - if(controller.blackboard_key_exists(key)) - controller.queue_behavior(/datum/ai_behavior/repairbot_speech, key) - return - -/datum/ai_behavior/repairbot_speech - behavior_flags = AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION - -/datum/ai_behavior/repairbot_speech/perform(seconds_per_tick, datum/ai_controller/controller, target_key) - var/datum/action/cooldown/bot_announcement/announcement = controller.blackboard[BB_ANNOUNCE_ABILITY] - var/list/speech_to_pick_from = (target_key == BB_DECONSTRUCT_TARGET) ? controller.blackboard[BB_REPAIRBOT_EMAGGED_SPEECH] : controller.blackboard[BB_REPAIRBOT_NORMAL_SPEECH] - if(!length(speech_to_pick_from)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - announcement.announce(pick(speech_to_pick_from)) - controller.set_blackboard_key(BB_REPAIRBOT_SPEECH_COOLDOWN, world.time + REPAIRBOT_SPEECH_TIMER) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -///subtree to replace iron platings -/datum/ai_planning_subtree/replace_floors - ///flag we check before executing - var/required_flag = REPAIRBOT_REPLACE_TILES - ///key of our floor target - var/floor_key = BB_TILELESS_FLOOR - ///type of tile we need to replace floors - var/needed_tile_type = /obj/item/stack/tile - ///type of floors we can replace - var/list/type_of_turf = list(/turf/open/floor/plating) - ///our searching behavior - var/search_behavior = /datum/ai_behavior/bot_search/valid_plateless_turf - -/datum/ai_planning_subtree/replace_floors/New() - . = ..() - type_of_turf = typecacheof(type_of_turf) - -/datum/ai_planning_subtree/replace_floors/SelectBehaviors(datum/ai_controller/basic_controller/bot/controller, seconds_per_tick) +/datum/bt_node/ai_behavior/bot_search/valid_plateless_turf/setup(datum/ai_controller/controller) var/mob/living/basic/bot/repairbot/bot_pawn = controller.pawn - if(!(bot_pawn.repairbot_flags & required_flag)) - return - if(!locate(needed_tile_type) in bot_pawn) - return - if(controller.blackboard_key_exists(floor_key)) - controller.queue_behavior(/datum/ai_behavior/bot_interact, floor_key) - return SUBTREE_RETURN_FINISH_PLANNING + if(!(bot_pawn.repairbot_flags & REPAIRBOT_REPLACE_TILES)) + return FALSE + if(!locate(/obj/item/stack/tile) in bot_pawn) + return FALSE + return TRUE - controller.queue_behavior(search_behavior, floor_key, type_of_turf, 5, 10, FALSE, TRUE) +/datum/bt_node/ai_behavior/bot_search/valid_plateless_turf/perform(seconds_per_tick, datum/ai_controller/basic_controller/bot/controller) + looking_for = get_turf_type_filter() + return ..() -/datum/ai_behavior/bot_search/valid_plateless_turf - action_cooldown = 5 SECONDS - -/datum/ai_behavior/bot_search/valid_plateless_turf/valid_target(datum/ai_controller/basic_controller/bot/controller, turf/open/my_target) +/datum/bt_node/ai_behavior/bot_search/valid_plateless_turf/valid_target(datum/ai_controller/basic_controller/bot/controller, turf/open/my_target) var/static/list/blacklist_objects = typecacheof(list( /obj/structure/window, /obj/structure/grille, )) - for(var/atom/possible_blacklisted in my_target.contents) if(is_type_in_typecache(possible_blacklisted, blacklist_objects)) return FALSE - if(istype(my_target, /turf/open/floor/plating) && !can_see(controller.pawn, my_target, 5)) return FALSE - var/static/list/blacklist_areas = typecacheof(list( /area/space, /area/station/maintenance, @@ -202,129 +160,133 @@ var/turf_area = get_area(my_target) return !(is_type_in_typecache(turf_area, blacklist_areas)) -///subtree to fix hull breaches -/datum/ai_planning_subtree/replace_floors/breaches - floor_key = BB_BREACHED_FLOOR - needed_tile_type = /obj/item/stack/tile/iron - type_of_turf = list(/turf/open/space) - required_flag = REPAIRBOT_FIX_BREACHES - search_behavior = /datum/ai_behavior/bot_search/valid_plateless_turf/breached +/// Breach variant: searches /turf/open/space instead, requires REPAIRBOT_FIX_BREACHES flag. +/datum/bt_node/ai_behavior/bot_search/valid_plateless_turf/breached -///exists as to not conflict with the base turf searching behavior cause of how the queue system works... -/datum/ai_behavior/bot_search/valid_plateless_turf/breached +/datum/bt_node/ai_behavior/bot_search/valid_plateless_turf/breached/setup(datum/ai_controller/controller) + var/mob/living/basic/bot/repairbot/bot_pawn = controller.pawn + if(!(bot_pawn.repairbot_flags & REPAIRBOT_FIX_BREACHES)) + return FALSE + if(!locate(/obj/item/stack/tile/iron) in bot_pawn) + return FALSE + return TRUE -///subtree to build girders -/datum/ai_planning_subtree/build_girder +/datum/bt_node/ai_behavior/bot_search/valid_plateless_turf/breached/get_turf_type_filter() + return typecacheof(list(/turf/open/space)) -/datum/ai_planning_subtree/build_girder/SelectBehaviors(datum/ai_controller/basic_controller/bot/controller, seconds_per_tick) +/datum/bt_node/ai_behavior/bot_search/valid_girder + time_between_perform = 5 SECONDS + +/datum/bt_node/ai_behavior/bot_search/valid_girder/setup(datum/ai_controller/controller) + var/mob/living/basic/bot/repairbot/bot_pawn = controller.pawn + if(!(bot_pawn.repairbot_flags & REPAIRBOT_FIX_GIRDERS)) + return FALSE + var/obj/item/stack/sheet/iron/my_iron = locate() in bot_pawn + if(isnull(my_iron) || my_iron.amount < 2) + return FALSE + return TRUE + +/datum/bt_node/ai_behavior/bot_search/valid_girder/perform(seconds_per_tick, datum/ai_controller/basic_controller/bot/controller) + var/static/list/searchable_girder = typecacheof(list(/obj/structure/girder)) + looking_for = searchable_girder + return ..() + +/datum/bt_node/ai_behavior/bot_search/valid_girder/valid_target(datum/ai_controller/basic_controller/bot/controller, obj/my_target) + if(!istype(my_target, /obj/structure/girder)) + return FALSE + return isfloorturf(my_target.loc) + + +/datum/bt_node/ai_behavior/targeted_mob_ability/build_girder + maximum_distance = 1 + +/datum/bt_node/ai_behavior/targeted_mob_ability/build_girder/setup(datum/ai_controller/controller) var/mob/living/basic/bot/repairbot/bot_pawn = controller.pawn if(!(bot_pawn.repairbot_flags & REPAIRBOT_BUILD_GIRDERS)) - return + return FALSE var/obj/item/stack/rods/my_rods = locate() in bot_pawn if(isnull(my_rods) || my_rods.amount < 2) - return - var/datum/action/cooldown/ability = controller.blackboard[BB_GIRDER_BUILD_ABILITY] + return FALSE + var/datum/action/cooldown/ability = controller.blackboard[ability_key] if(!ability?.IsAvailable()) - return - if(controller.blackboard_key_exists(BB_GIRDER_TARGET)) - controller.queue_behavior(/datum/ai_behavior/targeted_mob_ability/build_girder, BB_GIRDER_BUILD_ABILITY, BB_GIRDER_TARGET) - return SUBTREE_RETURN_FINISH_PLANNING + return FALSE + return ..() +/datum/bt_node/ai_behavior/targeted_mob_ability/build_girder/finish_action(datum/ai_controller/controller, succeeded) + . = ..() + controller.clear_blackboard_key(target_key) + +/// Search for open turfs adjacent to space (valid girder build locations). +/datum/bt_node/ai_behavior/bot_search/valid_wall_target + turf_search = TRUE + time_between_perform = 5 SECONDS + +/datum/bt_node/ai_behavior/bot_search/valid_wall_target/setup(datum/ai_controller/controller) + var/mob/living/basic/bot/repairbot/bot_pawn = controller.pawn + if(!(bot_pawn.repairbot_flags & REPAIRBOT_BUILD_GIRDERS)) + return FALSE + var/obj/item/stack/rods/my_rods = locate() in bot_pawn + if(isnull(my_rods) || my_rods.amount < 2) + return FALSE + return TRUE + +/datum/bt_node/ai_behavior/bot_search/valid_wall_target/perform(seconds_per_tick, datum/ai_controller/basic_controller/bot/controller) var/static/list/searchable_turfs = typecacheof(list(/turf/open)) - controller.queue_behavior(/datum/ai_behavior/bot_search/valid_wall_target, BB_GIRDER_TARGET, searchable_turfs, 5, 10, FALSE, TRUE) + looking_for = searchable_turfs + return ..() -/datum/ai_behavior/bot_search/valid_wall_target - action_cooldown = 5 SECONDS - -/datum/ai_behavior/bot_search/valid_wall_target/valid_target(datum/ai_controller/basic_controller/bot/controller, turf/my_target) +/datum/bt_node/ai_behavior/bot_search/valid_wall_target/valid_target(datum/ai_controller/basic_controller/bot/controller, turf/my_target) + if(!istype(my_target, /turf/open)) + return FALSE if(istype(get_area(my_target), /area/space) || isgroundlessturf(my_target) || my_target.is_blocked_turf()) return FALSE - var/static/list/blacklist_objects = list( + var/static/list/blacklist_objects = typecacheof(list( /obj/machinery/door, /obj/structure/grille, - ) - + )) for(var/atom/contents in my_target) if(is_type_in_typecache(contents, blacklist_objects)) return FALSE - var/turf/adjacent_turfs = get_adjacent_open_turfs(my_target) for(var/turf/possible_spaced_turf as anything in adjacent_turfs) if(isspaceturf(possible_spaced_turf) && istype(get_area(possible_spaced_turf), /area/space)) return TRUE return FALSE -/datum/ai_behavior/targeted_mob_ability/build_girder - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION +/datum/bt_node/ai_behavior/bot_search/valid_grille_target + time_between_perform = 5 SECONDS -/datum/ai_behavior/targeted_mob_ability/build_girder/setup(datum/ai_controller/controller, ability_key, target_key) - . = ..() - var/atom/target = controller.blackboard[target_key] - if(QDELETED(target)) +/datum/bt_node/ai_behavior/bot_search/valid_grille_target/setup(datum/ai_controller/controller) + var/mob/living/basic/bot/repairbot/bot_pawn = controller.pawn + if(!(bot_pawn.repairbot_flags & REPAIRBOT_REPLACE_WINDOWS)) return FALSE - set_movement_target(controller, target) + if(!locate(/obj/item/stack/sheet/glass) in bot_pawn) + return FALSE + return TRUE -/datum/ai_behavior/targeted_mob_ability/build_girder/finish_action(datum/ai_controller/controller, succeeded, ability_key, target_key) - . = ..() - controller.clear_blackboard_key(target_key) - -///subtree to place glass on windows -/datum/ai_planning_subtree/replace_window - -/datum/ai_planning_subtree/replace_window/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/mob/living/basic/bot/repairbot/living_pawn = controller.pawn - if(!(living_pawn.repairbot_flags & REPAIRBOT_REPLACE_WINDOWS)) - return - if(!locate(/obj/item/stack/sheet/glass) in living_pawn) - return - if(controller.blackboard_key_exists(BB_WINDOW_FRAMETARGET)) - controller.queue_behavior(/datum/ai_behavior/bot_interact, BB_WINDOW_FRAMETARGET) - return SUBTREE_RETURN_FINISH_PLANNING +/datum/bt_node/ai_behavior/bot_search/valid_grille_target/perform(seconds_per_tick, datum/ai_controller/basic_controller/bot/controller) var/static/list/searchable_grilles = typecacheof(list(/obj/structure/grille)) - controller.queue_behavior(/datum/ai_behavior/bot_search/valid_grille_target, BB_WINDOW_FRAMETARGET, searchable_grilles) + looking_for = searchable_grilles + return ..() -/datum/ai_behavior/bot_search/valid_grille_target/valid_target(datum/ai_controller/basic_controller/bot/controller, obj/structure/my_target) +/datum/bt_node/ai_behavior/bot_search/valid_grille_target/valid_target(datum/ai_controller/basic_controller/bot/controller, obj/structure/my_target) + if(!istype(my_target, /obj/structure/grille)) + return FALSE if(locate(/obj/structure/window) in get_turf(my_target)) return FALSE return (!istype(get_area(my_target), /area/space)) +/datum/bt_node/ai_behavior/bot_search/valid_window_fix + time_between_perform = 5 SECONDS -///subtree to place iron on girders -/datum/ai_planning_subtree/wall_girder - -/datum/ai_planning_subtree/wall_girder/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/mob/living/basic/bot/repairbot/living_pawn = controller.pawn - if(!(living_pawn.repairbot_flags & REPAIRBOT_FIX_GIRDERS)) - return - var/obj/item/stack/sheet/iron/my_iron = locate() in living_pawn - if(isnull(my_iron) || my_iron.amount < 2) - return - if(controller.blackboard_key_exists(BB_GIRDER_TO_WALL_TARGET)) - controller.queue_behavior(/datum/ai_behavior/bot_interact, BB_GIRDER_TO_WALL_TARGET) - return SUBTREE_RETURN_FINISH_PLANNING - var/static/list/searchable_girder = typecacheof(list(/obj/structure/girder)) - controller.queue_behavior(/datum/ai_behavior/bot_search/valid_girder, BB_GIRDER_TO_WALL_TARGET, searchable_girder) - -/datum/ai_behavior/bot_search/valid_girder - action_cooldown = 5 SECONDS - -/datum/ai_behavior/bot_search/valid_girder/valid_target(datum/ai_controller/basic_controller/bot/controller, obj/my_target) - return isfloorturf(my_target.loc) - -///subtree to repair machines with welders -/datum/ai_planning_subtree/fix_window - -/datum/ai_planning_subtree/fix_window/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(controller.blackboard_key_exists(BB_WELDER_TARGET)) - controller.queue_behavior(/datum/ai_behavior/bot_interact, BB_WELDER_TARGET) - return SUBTREE_RETURN_FINISH_PLANNING +/datum/bt_node/ai_behavior/bot_search/valid_window_fix/perform(seconds_per_tick, datum/ai_controller/basic_controller/bot/controller) var/static/list/searchable_objects = typecacheof(list(/obj/structure/window)) - controller.queue_behavior(/datum/ai_behavior/bot_search/valid_window_fix, BB_WELDER_TARGET, searchable_objects) + looking_for = searchable_objects + return ..() -/datum/ai_behavior/bot_search/valid_window_fix - action_cooldown = 5 SECONDS - -/datum/ai_behavior/bot_search/valid_window_fix/valid_target(datum/ai_controller/basic_controller/bot/controller, obj/my_target) +/datum/bt_node/ai_behavior/bot_search/valid_window_fix/valid_target(datum/ai_controller/basic_controller/bot/controller, obj/my_target) + if(!istype(my_target, /obj/structure/window)) + return FALSE return (my_target.get_integrity() < my_target.max_integrity || !my_target.anchored) #undef REPAIRBOT_SPEECH_TIMER diff --git a/code/modules/mob/living/basic/bots/repairbot/repairbot_emagged.bt.json b/code/modules/mob/living/basic/bots/repairbot/repairbot_emagged.bt.json new file mode 100644 index 00000000000..3970e4e04af --- /dev/null +++ b/code/modules/mob/living/basic/bots/repairbot/repairbot_emagged.bt.json @@ -0,0 +1,71 @@ +{ + "dm_type": "/datum/bt_node/subtree/repairbot_emagged", + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_ROBOT_TARGET" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_ROBOT_TARGET", + "required_dist": 0, + "finish_on_arrival": true, + "movement_type": "/datum/ai_movement/basic_avoidance" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/bot_interact/tip_robot", + "vars": { + "target_key": "BB_ROBOT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/grab_target", + "vars": { + "target_key": "BB_ROBOT_TARGET" + } + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_DECONSTRUCT_TARGET" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_DECONSTRUCT_TARGET", + "required_dist": 0, + "finish_on_arrival": true, + "movement_type": "/datum/ai_movement/basic_avoidance" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/bot_interact", + "vars": { + "target_key": "BB_DECONSTRUCT_TARGET" + } + } + ] + } + } + ] +} diff --git a/code/modules/mob/living/basic/bots/repairbot/repairbot_find_target.bt.json b/code/modules/mob/living/basic/bots/repairbot/repairbot_find_target.bt.json new file mode 100644 index 00000000000..b9d7b7f5c7f --- /dev/null +++ b/code/modules/mob/living/basic/bots/repairbot/repairbot_find_target.bt.json @@ -0,0 +1,143 @@ +{ + "dm_type": "/datum/bt_node/subtree/repairbot_find_target", + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bot_is_emagged", + "vars": { + "invert": false + }, + "child": { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/bot_search/valid_robot", + "vars": { + "target_key": "BB_ROBOT_TARGET", + "minimum_distance": 1 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/bot_search/deconstructable", + "vars": { + "target_key": "BB_DECONSTRUCT_TARGET", + "minimum_distance": 1 + } + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_NONE", + "invert": true, + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "selector", + "children": [ + { + "type": "sequence", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/bot_search/valid_plateless_turf/breached", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "minimum_distance": 1 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/bot_search/refillable_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "minimum_distance": 1 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/bot_search/valid_grille_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "minimum_distance": 1 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/bot_search/valid_plateless_turf", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "minimum_distance": 1 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/bot_search/valid_window_fix", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "minimum_distance": 1 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/bot_search/valid_girder", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "minimum_distance": 1 + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/set_bb_key", + "vars": { + "target_key": "BB_REPAIRBOT_INTERACTION_TYPE", + "value": "REPAIRBOT_INTERACTION_INTERACT" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/cancel_current_plan" + } + ] + }, + { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/bot_search/valid_wall_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "minimum_distance": 1 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/set_bb_key", + "vars": { + "target_key": "BB_REPAIRBOT_INTERACTION_TYPE", + "value": "REPAIRBOT_INTERACTION_BUILD_GIRDERS" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/cancel_current_plan" + } + ] + } + ] + } + } + ] +} diff --git a/code/modules/mob/living/basic/bots/repairbot/repairbot_repair_target.bt.json b/code/modules/mob/living/basic/bots/repairbot/repairbot_repair_target.bt.json new file mode 100644 index 00000000000..d8e44907e6c --- /dev/null +++ b/code/modules/mob/living/basic/bots/repairbot/repairbot_repair_target.bt.json @@ -0,0 +1,88 @@ +{ + "dm_type": "/datum/bt_node/subtree/repairbot_repair_target", + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bot_is_emagged", + "vars": { + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/repairbot_emagged" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bot_is_emagged", + "vars": { + "observer_abort": "BT_ABORT_SELF", + "invert": true + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": true, + "movement_type": "/datum/ai_movement/jps" + } + }, + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_equals", + "vars": { + "key": "BB_REPAIRBOT_INTERACTION_TYPE", + "value": "REPAIRBOT_INTERACTION_INTERACT" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/bot_interact", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_equals", + "vars": { + "key": "BB_REPAIRBOT_INTERACTION_TYPE", + "value": "REPAIRBOT_INTERACTION_BUILD_GIRDERS" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability/build_girder", + "vars": { + "ability_key": "BB_GIRDER_BUILD_ABILITY", + "target_key": "BB_CURRENT_TARGET" + } + } + } + ] + } + ] + } + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/bot_patrol" + } + ] +} diff --git a/code/modules/mob/living/basic/bots/secbot/secbot.bt.json b/code/modules/mob/living/basic/bots/secbot/secbot.bt.json new file mode 100644 index 00000000000..e7908f713ef --- /dev/null +++ b/code/modules/mob/living/basic/bots/secbot/secbot.bt.json @@ -0,0 +1,85 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/bot/secbot", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity/pacifist" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/bot_respond_to_summon" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": true, + "movement_type": "/datum/ai_movement/basic_avoidance" + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/bot_patrol" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/bots/secbot/secbot.dm b/code/modules/mob/living/basic/bots/secbot/secbot.dm index 46b2835764c..df9633d9265 100644 --- a/code/modules/mob/living/basic/bots/secbot/secbot.dm +++ b/code/modules/mob/living/basic/bots/secbot/secbot.dm @@ -198,7 +198,7 @@ /mob/living/basic/bot/secbot/proc/on_entered(datum/source, atom/movable/to_be_tripped) SIGNAL_HANDLER - var/mob/living/possible_target = ai_controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] + var/mob/living/possible_target = ai_controller.blackboard[BB_CURRENT_TARGET] if(!has_gravity() || !ismob(to_be_tripped) || !possible_target) return var/mob/living/carbon/tripped_mob = to_be_tripped diff --git a/code/modules/mob/living/basic/bots/secbot/secbot_ai.dm b/code/modules/mob/living/basic/bots/secbot/secbot_ai.dm index 6aadd73fd21..2de48a12639 100644 --- a/code/modules/mob/living/basic/bots/secbot/secbot_ai.dm +++ b/code/modules/mob/living/basic/bots/secbot/secbot_ai.dm @@ -1,23 +1,17 @@ /datum/ai_controller/basic_controller/bot/secbot + behavior_tree_json = "code/modules/mob/living/basic/bots/secbot/secbot.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/secbot, BB_UNREACHABLE_LIST_COOLDOWN = 1 MINUTES, BB_ALWAYS_IGNORE_FACTION = TRUE, ) - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity/pacifist, - /datum/ai_planning_subtree/respond_to_summon, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/arrest_target, - /datum/ai_planning_subtree/find_patrol_beacon, - ) reset_keys = list( BB_BEACON_TARGET, BB_PREVIOUS_BEACON_TARGET, BB_BOT_SUMMON_TARGET, ) -/datum/targeting_strategy/basic/secbot/can_attack(mob/living/living_mob, atom/the_target, vision_range) +/datum/targeting_strategy/basic/secbot/is_valid_target(mob/living/living_mob, atom/the_target, vision_range, datum/ai_controller/controller = null) var/datum/ai_controller/basic_controller/bot/my_controller = living_mob.ai_controller if(isnull(my_controller)) return FALSE @@ -37,13 +31,12 @@ my_controller.set_blackboard_key(BB_CURRENT_CRIMINAL_ASSESSMENT, assessed_threat) return (assessed_threat > THREAT_ASSESS_DANGEROUS) - /datum/ai_controller/basic_controller/bot/secbot/TryPossessPawn(atom/new_pawn) . = ..() if(. & AI_CONTROLLER_INCOMPATIBLE) return - RegisterSignal(new_pawn, COMSIG_AI_BLACKBOARD_KEY_SET(BB_BASIC_MOB_CURRENT_TARGET), PROC_REF(on_target_set)) - RegisterSignal(new_pawn, COMSIG_AI_BLACKBOARD_KEY_CLEARED(BB_BASIC_MOB_CURRENT_TARGET), PROC_REF(on_clear_target)) + RegisterSignal(new_pawn, COMSIG_AI_BLACKBOARD_KEY_SET(BB_CURRENT_TARGET), PROC_REF(on_target_set)) + RegisterSignal(new_pawn, COMSIG_AI_BLACKBOARD_KEY_CLEARED(BB_CURRENT_TARGET), PROC_REF(on_clear_target)) /datum/ai_controller/basic_controller/bot/secbot/proc/on_target_set() SIGNAL_HANDLER @@ -61,31 +54,3 @@ /datum/ai_controller/basic_controller/bot/secbot/proc/on_clear_target() SIGNAL_HANDLER clear_blackboard_key(BB_CURRENT_CRIMINAL_ASSESSMENT) - -/datum/ai_planning_subtree/arrest_target - ///what behavior do we use when arresting? - var/datum/ai_behavior/arrest_behavior = /datum/ai_behavior/basic_melee_attack/interact_once/bot - -/datum/ai_planning_subtree/arrest_target/SelectBehaviors(datum/ai_controller/basic_controller/bot/controller, seconds_per_tick) - var/mob/living/carbon/my_target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] - if(QDELETED(my_target) || !istype(my_target) || my_target.handcuffed) - controller.clear_blackboard_key(BB_BASIC_MOB_CURRENT_TARGET) - return - - var/mob/living/basic/bot/secbot/my_bot = controller.pawn - var/bot_flags = my_bot.security_mode_flags - if(my_target.IsParalyzed() && !(bot_flags & SECBOT_HANDCUFF_TARGET)) - controller.clear_blackboard_key(BB_BASIC_MOB_CURRENT_TARGET) - return - - controller.queue_behavior(arrest_behavior, BB_BASIC_MOB_CURRENT_TARGET, BB_TARGETING_STRATEGY) - return SUBTREE_RETURN_FINISH_PLANNING - -/datum/ai_behavior/basic_melee_attack/interact_once/bot - movement_behavior = /datum/ai_movement/basic_avoidance - -/datum/ai_behavior/basic_melee_attack/interact_once/bot/finish_action(datum/ai_controller/controller, succeeded, target_key, targeting_strategy_key, hiding_location_key) - var/mob/living/carbon/human/human_target = controller.blackboard[target_key] - if(!isnull(human_target) && human_target.handcuffed) - controller.remove_from_blackboard_lazylist_key(BB_BASIC_MOB_RETALIATE_LIST, human_target) - return ..() diff --git a/code/modules/mob/living/basic/bots/secbot/super_beepsky.dm b/code/modules/mob/living/basic/bots/secbot/super_beepsky.dm index 1f82db49c7a..f3dcd1c5bf8 100644 --- a/code/modules/mob/living/basic/bots/secbot/super_beepsky.dm +++ b/code/modules/mob/living/basic/bots/secbot/super_beepsky.dm @@ -57,7 +57,7 @@ /mob/living/basic/bot/secbot/grievous/on_entered(datum/source, atom/movable/movable_target) . = ..() - if(!ismob(movable_target) || !ai_controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] == movable_target) + if(!ismob(movable_target) || !ai_controller.blackboard[BB_CURRENT_TARGET] == movable_target) return visible_message(span_warning("[src] flails his swords and cuts [movable_target]!")) playsound(src, 'sound/mobs/non-humanoids/beepsky/beepskyspinsabre.ogg' , 100, TRUE, -1) diff --git a/code/modules/mob/living/basic/bots/secbot/super_beepsky_ai.dm b/code/modules/mob/living/basic/bots/secbot/super_beepsky_ai.dm index effd9a98cab..93ab2932f48 100644 --- a/code/modules/mob/living/basic/bots/secbot/super_beepsky_ai.dm +++ b/code/modules/mob/living/basic/bots/secbot/super_beepsky_ai.dm @@ -1,16 +1,8 @@ /datum/ai_controller/basic_controller/bot/secbot/super_beepsky - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity/pacifist, - /datum/ai_planning_subtree/respond_to_summon, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/find_patrol_beacon, - ) - /datum/ai_controller/basic_controller/bot/secbot/super_beepsky/on_target_set() . = ..() var/mob/living/basic/bot/secbot/grievous/super_beeps = pawn if(!super_beeps.sword_active) INVOKE_ASYNC(super_beeps.weapon, TYPE_PROC_REF(/obj/item, attack_self), super_beeps) - super_beeps.visible_message("[super_beeps] points at [blackboard[BB_BASIC_MOB_CURRENT_TARGET]]!") + super_beeps.visible_message("[super_beeps] points at [blackboard[BB_CURRENT_TARGET]]!") diff --git a/code/modules/mob/living/basic/bots/vibebot/vibebot.bt.json b/code/modules/mob/living/basic/bots/vibebot/vibebot.bt.json new file mode 100644 index 00000000000..696062b91c0 --- /dev/null +++ b/code/modules/mob/living/basic/bots/vibebot/vibebot.bt.json @@ -0,0 +1,74 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/bot/vibebot", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity/pacifist" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/bot_respond_to_summon" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_VIBEBOT_PARTY_TARGET" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_VIBEBOT_PARTY_TARGET", + "required_dist": 1, + "finish_on_arrival": true + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/vibebot_party", + "vars": { + "ability_key": "BB_VIBEBOT_PARTY_ABILITY", + "target_key": "BB_VIBEBOT_PARTY_TARGET" + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/bot_patrol" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_VIBEBOT_PARTY_TARGET", + "target_source": "/datum/target_source/oview_single_type/human_mob", + "targeting_strategy": "/datum/targeting_strategy/conscious_human/party_friend", + "ignore_list_key": "BB_TEMPORARY_IGNORE_LIST", + "vision_range": 5, + "time_between_perform": "5 SECONDS" + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/bots/vibebot/vibebot_ai.dm b/code/modules/mob/living/basic/bots/vibebot/vibebot_ai.dm index 77773746b08..6b4aaa9efd7 100644 --- a/code/modules/mob/living/basic/bots/vibebot/vibebot_ai.dm +++ b/code/modules/mob/living/basic/bots/vibebot/vibebot_ai.dm @@ -1,16 +1,11 @@ /datum/ai_controller/basic_controller/bot/vibebot + behavior_tree_json = "code/modules/mob/living/basic/bots/vibebot/vibebot.bt.json" blackboard = list( BB_UNREACHABLE_LIST_COOLDOWN = 2 MINUTES, BB_VIBEBOT_HAPPY_SONG = VIBEBOT_CHEER_SONG, BB_VIBEBOT_GRIM_SONG = VIBEBOT_GRIM_MUSIC, BB_VIBEBOT_BIRTHDAY_SONG = VIBEBOT_HAPPY_BIRTHDAY, ) - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity/pacifist, - /datum/ai_planning_subtree/respond_to_summon, - /datum/ai_planning_subtree/find_party_friends, - /datum/ai_planning_subtree/find_patrol_beacon, - ) reset_keys = list( BB_BEACON_TARGET, BB_PREVIOUS_BEACON_TARGET, @@ -45,43 +40,36 @@ song.start_playing(pawn) addtimer(CALLBACK(song, TYPE_PROC_REF(/datum/song, stop_playing)), 10 SECONDS) //in 10 seconds, stop playing music -///subtree we use to find party friends in general -/datum/ai_planning_subtree/find_party_friends -/datum/ai_planning_subtree/find_party_friends/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/static/list/type_to_search = typecacheof(list(/mob/living/carbon/human)) - if(!controller.blackboard_key_exists(BB_VIBEBOT_PARTY_TARGET)) - controller.queue_behavior(/datum/ai_behavior/bot_search/party_friends, BB_VIBEBOT_PARTY_TARGET, type_to_search) - return +/// Valid if the target is a conscious human who's in a bad mood or having a birthday someone who could use cheering up. +/datum/targeting_strategy/conscious_human/party_friend/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + var/mob/living/carbon/human/human_target = target + return human_target.mob_mood?.mood_level < MOOD_LEVEL_NEUTRAL || HAS_TRAIT(human_target, TRAIT_BIRTHDAY_BOY) - controller.queue_behavior(/datum/ai_behavior/targeted_mob_ability/and_clear_target/vibebot_party, BB_VIBEBOT_PARTY_ABILITY, BB_VIBEBOT_PARTY_TARGET) - return SUBTREE_RETURN_FINISH_PLANNING +/datum/bt_node/ai_behavior/vibebot_party + var/ability_key + var/target_key -///behavior we use to party with people -/datum/ai_behavior/targeted_mob_ability/and_clear_target/vibebot_party - behavior_flags = AI_BEHAVIOR_REQUIRE_REACH | AI_BEHAVIOR_REQUIRE_MOVEMENT +/datum/bt_node/ai_behavior/vibebot_party/perform(seconds_per_tick, datum/ai_controller/basic_controller/bot/controller) + var/mob/living/living_target = controller.blackboard[target_key] + if(QDELETED(living_target)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + if(get_dist(controller.pawn, living_target) > 1) + return AI_BEHAVIOR_INSTANT + var/datum/action/cooldown/ability = controller.blackboard[ability_key] + if(ability) + INVOKE_ASYNC(ability, TYPE_PROC_REF(/datum/action, Trigger), living_target) + var/mob/living/living_pawn = controller.pawn + living_pawn.manual_emote("celebrates with [living_target]!") + INVOKE_ASYNC(living_pawn, TYPE_PROC_REF(/mob, emote), "flip") + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED -/datum/ai_behavior/targeted_mob_ability/and_clear_target/vibebot_party/setup(datum/ai_controller/controller, ability_key, target_key) +/datum/bt_node/ai_behavior/vibebot_party/finish_action(datum/ai_controller/basic_controller/bot/controller, succeeded) . = ..() var/atom/target = controller.blackboard[target_key] - if(QDELETED(target)) - return FALSE - set_movement_target(controller, target) - -/datum/ai_behavior/targeted_mob_ability/and_clear_target/vibebot_party/finish_action(datum/ai_controller/basic_controller/bot/controller, succeeded, ability_key, target_key) - var/atom/target = controller.blackboard[target_key] - controller.add_to_blacklist(target) - if(succeeded) - var/mob/living/living_pawn = controller.pawn - living_pawn.manual_emote("celebrates with [target]!") - living_pawn.emote("flip") - return ..() - -///behavior that searches for party friends -/datum/ai_behavior/bot_search/party_friends - action_cooldown = 5 SECONDS - -/datum/ai_behavior/bot_search/party_friends/valid_target(datum/ai_controller/basic_controller/bot/controller, mob/living/carbon/human/my_target) - if(my_target.stat != CONSCIOUS || isnull(my_target.mind)) - return FALSE - return (my_target.mob_mood.mood_level < MOOD_LEVEL_NEUTRAL || HAS_TRAIT(my_target, TRAIT_BIRTHDAY_BOY)) + if(!isnull(target)) + controller.add_to_blacklist(target) + controller.clear_blackboard_key(target_key) diff --git a/code/modules/mob/living/basic/clown/clown.bt.json b/code/modules/mob/living/basic/clown/clown.bt.json new file mode 100644 index 00000000000..bbfc9f477e5 --- /dev/null +++ b/code/modules/mob/living/basic/clown/clown.bt.json @@ -0,0 +1,28 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/clown", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "1 SECONDS", + "finish_on_primary": true, + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_retaliate_combat" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop" + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/clown/clown_ai.dm b/code/modules/mob/living/basic/clown/clown_ai.dm index dfa9b71a40e..091e14ef167 100644 --- a/code/modules/mob/living/basic/clown/clown_ai.dm +++ b/code/modules/mob/living/basic/clown/clown_ai.dm @@ -1,17 +1,11 @@ /datum/ai_controller/basic_controller/clown + behavior_tree_json = "code/modules/mob/living/basic/clown/clown.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_BASIC_MOB_SPEAK_LINES = null, ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/target_retaliate, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/random_speech/blackboard, - ) /datum/ai_controller/basic_controller/clown/murder blackboard = list( diff --git a/code/modules/mob/living/basic/cult/constructs/artificer.bt.json b/code/modules/mob/living/basic/cult/constructs/artificer.bt.json new file mode 100644 index 00000000000..f0ed947ff49 --- /dev/null +++ b/code/modules/mob/living/basic/cult/constructs/artificer.bt.json @@ -0,0 +1,83 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/artificer", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1 + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/run_away_from_target" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets/most_wounded", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list", + "vars": { + "target_key": "BB_BASIC_MOB_FLEE_TARGET", + "targeting_strategy": "BB_FLEE_TARGETING_STRATEGY", + "hiding_location_key": "BB_BASIC_MOB_FLEE_TARGET_HIDING_LOCATION" + } + } + ] +} diff --git a/code/modules/mob/living/basic/cult/constructs/construct_ai.dm b/code/modules/mob/living/basic/cult/constructs/construct_ai.dm index c7ea47a83ae..6f333bd20c4 100644 --- a/code/modules/mob/living/basic/cult/constructs/construct_ai.dm +++ b/code/modules/mob/living/basic/cult/constructs/construct_ai.dm @@ -5,6 +5,7 @@ * If there is no one to heal, they will run away from any non-allied mobs. */ /datum/ai_controller/basic_controller/artificer + behavior_tree_json = "code/modules/mob/living/basic/cult/constructs/artificer.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/same_faction/construct, BB_FLEE_TARGETING_STRATEGY = /datum/targeting_strategy/basic, @@ -12,14 +13,6 @@ ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/simple_find_wounded_target, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/target_retaliate/to_flee, - /datum/ai_planning_subtree/flee_target/from_flee_key, - ) /** * Juggernauts @@ -27,19 +20,13 @@ * Juggernauts slowly walk toward non-allied mobs and pummel them to death. */ /datum/ai_controller/basic_controller/juggernaut + behavior_tree_json = "code/modules/mob/living/basic/cult/constructs/juggernaut.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_TARGET_MINIMUM_STAT = HARD_CRIT, ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) /** * Proteons @@ -47,6 +34,7 @@ * Proteons perform cowardly hit-and-run attacks, fleeing melee when struck but returning to fight again. */ /datum/ai_controller/basic_controller/proteon + behavior_tree_json = "code/modules/mob/living/basic/cult/constructs/proteon.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_TARGET_MINIMUM_STAT = HARD_CRIT, @@ -54,15 +42,6 @@ ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/target_retaliate/to_flee, - /datum/ai_planning_subtree/flee_target/from_flee_key, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) /** * Wraiths @@ -70,25 +49,17 @@ * Wraiths seek out the most injured non-allied mob to beat to death. */ /datum/ai_controller/basic_controller/wraith + behavior_tree_json = "code/modules/mob/living/basic/cult/constructs/wraith.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_TARGET_MINIMUM_STAT = HARD_CRIT, ) - ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/simple_find_wounded_target, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) - /// Targeting strategy that will only allow mobs that constructs can heal. /datum/targeting_strategy/basic/same_faction/construct target_wounded_key = BB_TARGET_WOUNDED_ONLY -/datum/targeting_strategy/basic/same_faction/construct/can_attack(mob/living/living_mob, atom/the_target, vision_range, check_faction = TRUE) +/datum/targeting_strategy/basic/same_faction/construct/is_valid_target(mob/living/living_mob, atom/the_target, vision_range, datum/ai_controller/controller = null) if(isconstruct(the_target) || istype(the_target, /mob/living/basic/shade)) return ..() return FALSE diff --git a/code/modules/mob/living/basic/cult/constructs/juggernaut.bt.json b/code/modules/mob/living/basic/cult/constructs/juggernaut.bt.json new file mode 100644 index 00000000000..9cbc8c4bd8e --- /dev/null +++ b/code/modules/mob/living/basic/cult/constructs/juggernaut.bt.json @@ -0,0 +1,5 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/juggernaut", + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_hostile_obstacles_combat" +} diff --git a/code/modules/mob/living/basic/cult/constructs/proteon.bt.json b/code/modules/mob/living/basic/cult/constructs/proteon.bt.json new file mode 100644 index 00000000000..df911230d81 --- /dev/null +++ b/code/modules/mob/living/basic/cult/constructs/proteon.bt.json @@ -0,0 +1,14 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/proteon", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/skittish_brawler_combat" + } + ] +} diff --git a/code/modules/mob/living/basic/cult/constructs/wraith.bt.json b/code/modules/mob/living/basic/cult/constructs/wraith.bt.json new file mode 100644 index 00000000000..139ac744781 --- /dev/null +++ b/code/modules/mob/living/basic/cult/constructs/wraith.bt.json @@ -0,0 +1,83 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/wraith", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/attack_obstructions", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1 + } + } + ] + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets/most_wounded", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/cytology/vatbeast.bt.json b/code/modules/mob/living/basic/cytology/vatbeast.bt.json new file mode 100644 index 00000000000..2e5924a2fdc --- /dev/null +++ b/code/modules/mob/living/basic/cytology/vatbeast.bt.json @@ -0,0 +1,137 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/vatbeast", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/move_to_and_eat" + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/attack_obstructions", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_at_least", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "key": "BB_BASIC_MOB_HAS_TARGET_TIME", + "minimum": "10 SECONDS" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability/melee", + "vars": { + "ability_key": "BB_GENERIC_ACTION", + "target_key": "BB_CURRENT_TARGET" + } + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_TARGET_FOOD", + "targeting_strategy": "/datum/targeting_strategy/pickup_item", + "target_source": "/datum/target_source/oview_typed/from_bb_key/basic_foods" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION", + "check_faction": true + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/cytology/vatbeast.dm b/code/modules/mob/living/basic/cytology/vatbeast.dm index a52411f0d82..b0de4c1d754 100644 --- a/code/modules/mob/living/basic/cytology/vatbeast.dm +++ b/code/modules/mob/living/basic/cytology/vatbeast.dm @@ -47,7 +47,7 @@ var/datum/action/cooldown/tentacle_slap/slapper = new (src) slapper.Grant(src) - ai_controller.set_blackboard_key(BB_TARGETED_ACTION, slapper) + ai_controller.set_blackboard_key(BB_GENERIC_ACTION, slapper) ai_controller.set_blackboard_key(BB_BASIC_FOODS, typecacheof(enjoyed_food)) /mob/living/basic/vatbeast/tamed(mob/living/tamer, obj/item/food) @@ -64,34 +64,12 @@ /// Attack people and slap them /datum/ai_controller/basic_controller/vatbeast + behavior_tree_json = "code/modules/mob/living/basic/cytology/vatbeast.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/target_retaliate, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/find_food, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/targeted_mob_ability/vatbeast_slap, - /datum/ai_planning_subtree/basic_melee_attack_subtree - ) - -/// Only do this if we are adjacent to target and have been mad at the same guy for at least 10 seconds -/// That slap REALLY hurts -/datum/ai_planning_subtree/targeted_mob_ability/vatbeast_slap - operational_datums = list(/datum/component/ai_target_timer) - -/datum/ai_planning_subtree/targeted_mob_ability/vatbeast_slap/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/mob/living/target = controller.blackboard[target_key] - if (!isliving(target) || !controller.pawn.Adjacent(target)) - return - var/time_on_target = controller.blackboard[BB_BASIC_MOB_HAS_TARGET_TIME] || 0 - if (time_on_target < 10 SECONDS) - return - return ..() /// Ability that allows the owner to slap other mobs a short distance away. /// For vatbeats, this ability is shared with the rider. diff --git a/code/modules/mob/living/basic/farm_animals/bee/_bee.dm b/code/modules/mob/living/basic/farm_animals/bee/_bee.dm index 409035c9683..ccd0a0b04da 100644 --- a/code/modules/mob/living/basic/farm_animals/bee/_bee.dm +++ b/code/modules/mob/living/basic/farm_animals/bee/_bee.dm @@ -55,7 +55,7 @@ /datum/pet_command/free, /datum/pet_command/beehive/enter, /datum/pet_command/beehive/exit, - /datum/pet_command/follow/bee, + /datum/pet_command/follow, /datum/pet_command/attack/swirl, /datum/pet_command/scatter, ) diff --git a/code/modules/mob/living/basic/farm_animals/bee/bee.bt.json b/code/modules/mob/living/basic/farm_animals/bee/bee.bt.json new file mode 100644 index 00000000000..727a2cfd8d5 --- /dev/null +++ b/code/modules/mob/living/basic/farm_animals/bee/bee.bt.json @@ -0,0 +1,86 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/bee", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "1 SECONDS", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree", + "override_id": "SUBPLAN_ID_PET_COMMAND" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/find_hive" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/transition_hive_status" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "1 SECONDS", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/pollinate_target" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_hostile_combat" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/enter_exit_hive" + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/random_chance", + "vars": { + "chance": 0.85 + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_TARGET_HYDRO", + "target_source": "/datum/target_source/oview_single_type/hydroponics", + "targeting_strategy": "/datum/targeting_strategy/pollinatable_hydro", + "vision_range": 10, + "time_between_perform": "10 SECONDS" + } + } + } + ] + } + ] + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "invert": true, + "key": "BB_TARGET_HOME" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/find_hive" + } + } + ] +} diff --git a/code/modules/mob/living/basic/farm_animals/bee/bee_ai_behavior.dm b/code/modules/mob/living/basic/farm_animals/bee/bee_ai_behavior.dm index 2a6ff31981c..a5fdb64085f 100644 --- a/code/modules/mob/living/basic/farm_animals/bee/bee_ai_behavior.dm +++ b/code/modules/mob/living/basic/farm_animals/bee/bee_ai_behavior.dm @@ -1,87 +1,9 @@ /// if we have a hive, this will be our aggro distance #define AGGRO_DISTANCE_FROM_HIVE 2 -/datum/ai_behavior/hunt_target/pollinate - always_reset_target = TRUE - -/datum/ai_behavior/hunt_target/pollinate/target_caught(mob/living/hunter, obj/machinery/hydroponics/hydro_target) - var/datum/callback/callback = CALLBACK(hunter, TYPE_PROC_REF(/mob/living/basic/bee, pollinate), hydro_target) - callback.Invoke() - -/datum/ai_behavior/find_hunt_target/pollinate - action_cooldown = 10 SECONDS - -/datum/ai_behavior/find_hunt_target/pollinate/valid_dinner(mob/living/source, obj/machinery/hydroponics/dinner, radius) - if(!dinner.can_bee_pollinate()) - return FALSE - return can_see(source, dinner, radius) - -/datum/ai_behavior/enter_exit_hive - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH - action_cooldown = 10 SECONDS - -/datum/ai_behavior/enter_exit_hive/setup(datum/ai_controller/controller, target_key, attack_key) - . = ..() - var/atom/target = controller.blackboard[target_key] - if(QDELETED(target)) - return FALSE - set_movement_target(controller, target) - -/datum/ai_behavior/enter_exit_hive/perform(seconds_per_tick, datum/ai_controller/controller, target_key, attack_key) - var/obj/structure/beebox/current_home = controller.blackboard[target_key] - var/atom/attack_target = controller.blackboard[attack_key] - - if(attack_target) // forget about who we attacking when we go home - controller.clear_blackboard_key(attack_key) - - controller.ai_interact(target = current_home, combat_mode = FALSE) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -/datum/ai_behavior/inhabit_hive - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH - -/datum/ai_behavior/inhabit_hive/setup(datum/ai_controller/controller, target_key) - . = ..() - var/atom/target = controller.blackboard[target_key] - if(QDELETED(target)) - return FALSE - set_movement_target(controller, target) - -/datum/ai_behavior/inhabit_hive/perform(seconds_per_tick, datum/ai_controller/controller, target_key) - var/obj/structure/beebox/potential_home = controller.blackboard[target_key] - var/mob/living/bee_pawn = controller.pawn - - if(!potential_home.habitable(bee_pawn)) //the house become full before we get to it - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - controller.ai_interact(target = potential_home, combat_mode = FALSE) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -/datum/ai_behavior/inhabit_hive/finish_action(datum/ai_controller/controller, succeeded, target_key) - . = ..() - if(!succeeded) - controller.clear_blackboard_key(target_key) //failed to make it our home so find another - -/datum/ai_behavior/find_and_set/bee_hive - action_cooldown = 10 SECONDS - -/datum/ai_behavior/find_and_set/bee_hive/search_tactic(datum/ai_controller/controller, locate_path, search_range = SEARCH_TACTIC_DEFAULT_RANGE) - var/list/valid_hives = list() - var/mob/living/bee_pawn = controller.pawn - - if(istype(bee_pawn.loc, /obj/structure/beebox)) - return bee_pawn.loc //for premade homes - - for(var/obj/structure/beebox/potential_home in oview(search_range, bee_pawn)) - if(!potential_home.habitable(bee_pawn)) - continue - valid_hives += potential_home - - if(valid_hives.len) - return pick(valid_hives) /datum/targeting_strategy/basic/bee -/datum/targeting_strategy/basic/bee/can_attack(mob/living/owner, atom/target, vision_range) +/datum/targeting_strategy/basic/bee/is_valid_target(mob/living/owner, atom/target, vision_range, datum/ai_controller/controller = null) if(!isliving(target)) return FALSE . = ..() @@ -104,14 +26,6 @@ return !(mob_target.bee_friendly()) -///pet commands -/datum/pet_command/follow/bee - ///the behavior we use to follow - follow_behavior = /datum/ai_behavior/pet_follow_friend/bee - -/datum/ai_behavior/pet_follow_friend/bee - required_distance = 0 - ///swirl around the owner in menacing fashion /datum/pet_command/attack/swirl command_name = "Swirl" @@ -138,49 +52,7 @@ /datum/pet_command/attack/swirl/execute_action(datum/ai_controller/controller) if(controller.blackboard_key_exists(BB_CURRENT_PET_TARGET)) return ..() - controller.queue_behavior(/datum/ai_behavior/swirl_around_target, BB_SWARM_TARGET) - return SUBTREE_RETURN_FINISH_PLANNING - -/datum/ai_behavior/swirl_around_target - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_MOVE_AND_PERFORM - required_distance = 0 - ///chance to swirl - var/swirl_chance = 60 - -/datum/ai_behavior/swirl_around_target/setup(datum/ai_controller/controller, target_key) - . = ..() - var/atom/target = controller.blackboard[target_key] - if(QDELETED(target)) - return FALSE - set_movement_target(controller, target) - -/datum/ai_behavior/swirl_around_target/perform(seconds_per_tick, datum/ai_controller/controller, target_key) - var/atom/target = controller.blackboard[target_key] - var/mob/living/living_pawn = controller.pawn - - if(QDELETED(target)) - return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED - - if(get_dist(target, living_pawn) > 1) - set_movement_target(controller, target) - return AI_BEHAVIOR_DELAY - - if(!SPT_PROB(swirl_chance, seconds_per_tick)) - return AI_BEHAVIOR_DELAY - - var/list/possible_turfs = list() - - for(var/turf/possible_turf in oview(2, target)) - if(possible_turf.is_blocked_turf(source_atom = living_pawn)) - continue - possible_turfs += possible_turf - - if(!length(possible_turfs)) - return AI_BEHAVIOR_DELAY - - if(isnull(controller.movement_target_source) || controller.movement_target_source == type) - set_movement_target(controller, pick(possible_turfs)) - return AI_BEHAVIOR_DELAY + controller.set_behavior_tree_override(SUBPLAN_ID_PET_COMMAND, /datum/bt_node/subtree/pet_command/swirl) /datum/pet_command/beehive @@ -205,9 +77,7 @@ return /datum/pet_command/beehive/execute_action(datum/ai_controller/controller) - controller.queue_behavior(/datum/ai_behavior/enter_exit_hive, BB_CURRENT_HOME) - controller.clear_blackboard_key(BB_ACTIVE_PET_COMMAND) - return SUBTREE_RETURN_FINISH_PLANNING + controller.set_behavior_tree_override(SUBPLAN_ID_PET_COMMAND, /datum/bt_node/subtree/pet_command/beehive) /datum/pet_command/beehive/enter command_name = "Enter beehive" @@ -237,11 +107,7 @@ set_command_target(parent, commander) /datum/pet_command/scatter/execute_action(datum/ai_controller/controller) - controller.queue_behavior(/datum/ai_behavior/run_away_from_target/scatter, BB_CURRENT_PET_TARGET) - controller.clear_blackboard_key(BB_ACTIVE_PET_COMMAND) - return SUBTREE_RETURN_FINISH_PLANNING + controller.set_behavior_tree_override(SUBPLAN_ID_PET_COMMAND, /datum/bt_node/subtree/pet_command/scatter) -/datum/ai_behavior/run_away_from_target/scatter - run_distance = 4 #undef AGGRO_DISTANCE_FROM_HIVE diff --git a/code/modules/mob/living/basic/farm_animals/bee/bee_ai_subtree.dm b/code/modules/mob/living/basic/farm_animals/bee/bee_ai_subtree.dm index 3af1bb7e52c..4047ad5f6a5 100644 --- a/code/modules/mob/living/basic/farm_animals/bee/bee_ai_subtree.dm +++ b/code/modules/mob/living/basic/farm_animals/bee/bee_ai_subtree.dm @@ -6,22 +6,7 @@ ai_traits = PASSIVE_AI_FLAGS ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - - planning_subtrees = list( - /datum/ai_planning_subtree/pet_planning, - /datum/ai_planning_subtree/find_valid_home, - /datum/ai_planning_subtree/enter_exit_home, - /datum/ai_planning_subtree/find_and_hunt_target/pollinate, - /datum/ai_planning_subtree/simple_find_target/bee, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) - -/datum/ai_planning_subtree/simple_find_target/bee/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/atom/hydro_target = controller.blackboard[BB_TARGET_HYDRO] - if(hydro_target) - return SUBTREE_RETURN_FINISH_PLANNING - return ..() + behavior_tree_json = "code/modules/mob/living/basic/farm_animals/bee/bee.bt.json" /datum/ai_controller/basic_controller/queen_bee blackboard = list( @@ -30,70 +15,14 @@ ai_traits = PASSIVE_AI_FLAGS ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - - planning_subtrees = list( - /datum/ai_planning_subtree/find_valid_home, - /datum/ai_planning_subtree/enter_exit_home/queen, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) + behavior_tree_json = "code/modules/mob/living/basic/farm_animals/bee/queen_bee.bt.json" -/datum/ai_planning_subtree/find_valid_home +/datum/bt_node/subtree/pollinate_target + behavior_tree_json = "code/modules/mob/living/basic/farm_animals/bee/pollinate_target.bt.json" -/datum/ai_planning_subtree/find_valid_home/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/mob/living/work_bee = controller.pawn +/datum/bt_node/subtree/find_hive + behavior_tree_json = "code/modules/mob/living/basic/farm_animals/bee/find_hive.bt.json" - var/obj/structure/beebox/current_home = controller.blackboard[BB_CURRENT_HOME] - - if(QDELETED(current_home)) - controller.queue_behavior(/datum/ai_behavior/find_and_set/bee_hive, BB_CURRENT_HOME, /obj/structure/beebox) - return - - if(work_bee in current_home.bees) - return - - controller.queue_behavior(/datum/ai_behavior/inhabit_hive, BB_CURRENT_HOME) - return SUBTREE_RETURN_FINISH_PLANNING - -/datum/ai_planning_subtree/enter_exit_home - ///chance we go back home - var/flyback_chance = 15 - ///chance we exit the home - var/exit_chance = 35 - -/datum/ai_planning_subtree/enter_exit_home/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - - var/obj/structure/beebox/current_home = controller.blackboard[BB_CURRENT_HOME] - - if(QDELETED(current_home)) - return - - var/mob/living/bee_pawn = controller.pawn - var/action_prob = (bee_pawn.loc == current_home) ? exit_chance : flyback_chance - - if(!SPT_PROB(action_prob, seconds_per_tick)) - return - - controller.queue_behavior(/datum/ai_behavior/enter_exit_hive, BB_CURRENT_HOME, BB_BASIC_MOB_CURRENT_TARGET) - return SUBTREE_RETURN_FINISH_PLANNING - -//the queen spend more time in the hive -/datum/ai_planning_subtree/enter_exit_home/queen - flyback_chance = 85 - exit_chance = 5 - -/datum/ai_planning_subtree/find_and_hunt_target/pollinate - target_key = BB_TARGET_HYDRO - hunting_behavior = /datum/ai_behavior/hunt_target/pollinate - finding_behavior = /datum/ai_behavior/find_hunt_target/pollinate - hunt_targets = list(/obj/machinery/hydroponics) - hunt_range = 10 - hunt_chance = 85 - -/datum/ai_planning_subtree/find_and_hunt_target/pollinate/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/atom/atom_pawn = controller.pawn - if(!isturf(atom_pawn.loc)) - return - return ..() +/datum/bt_node/subtree/transition_hive_status + behavior_tree_json = "code/modules/mob/living/basic/farm_animals/bee/transition_hive_status.bt.json" diff --git a/code/modules/mob/living/basic/farm_animals/bee/bee_bt.dm b/code/modules/mob/living/basic/farm_animals/bee/bee_bt.dm new file mode 100644 index 00000000000..62da5e54ef5 --- /dev/null +++ b/code/modules/mob/living/basic/farm_animals/bee/bee_bt.dm @@ -0,0 +1,132 @@ + + +/** + * Searches for a valid beebox home. Skipped if the bee is already inside its home. + * Sets BB_CURRENT_HOME when found. + */ +/datum/bt_node/ai_behavior/find_hive + +/datum/bt_node/ai_behavior/find_hive/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/basic/bee/bee_pawn = controller.pawn + var/obj/structure/beebox/current_home = controller.blackboard[BB_CURRENT_HOME] + + if(!QDELETED(current_home)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED // already have a home; let the inhabit sequence handle it + + if(istype(bee_pawn.loc, /obj/structure/beebox)) + controller.set_blackboard_key(BB_CURRENT_HOME, bee_pawn.loc) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED + + for(var/obj/structure/beebox/potential_home in oview(10, bee_pawn)) + if(!potential_home.habitable(bee_pawn)) + continue + controller.set_blackboard_key(BB_TARGET_HOME, potential_home) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + +/** + * Moves to BB_CURRENT_HOME and inhabits it (ai_interact). Clears BB_CURRENT_HOME on failure. + * Must be in range (adjacent) to work. Returns FAILURE if home is gone or full. + */ +/datum/bt_node/ai_behavior/inhabit_hive + time_between_perform = 10 SECONDS + +/datum/bt_node/ai_behavior/inhabit_hive/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/basic/bee/bee_pawn = controller.pawn + var/obj/structure/beebox/home = controller.blackboard[BB_CURRENT_HOME] + if(QDELETED(home)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + if(!home.habitable(bee_pawn)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + if(!bee_pawn.Adjacent(home)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + INVOKE_ASYNC(controller, TYPE_PROC_REF(/datum/ai_controller, ai_interact), home, FALSE) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + +/datum/bt_node/ai_behavior/inhabit_hive/finish_action(datum/ai_controller/controller, succeeded, ...) + . = ..() + if(!succeeded) + controller.clear_blackboard_key(BB_CURRENT_HOME) + +///Chance to leave or enter hive +/datum/bt_node/ai_behavior/enter_exit_hive + time_between_perform = 1 SECONDS + var/flyback_chance = 15 + var/exit_chance = 35 + +/datum/bt_node/ai_behavior/enter_exit_hive/perform(seconds_per_tick, datum/ai_controller/controller) + var/obj/structure/beebox/home = controller.blackboard[BB_CURRENT_HOME] + if(QDELETED(home)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + var/mob/living/bee_pawn = controller.pawn + var/prob_to_use = (bee_pawn.loc == home) ? exit_chance : flyback_chance + + if(!SPT_PROB(prob_to_use, seconds_per_tick)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + controller.set_blackboard_key(BB_WANTS_TO_TRANSITION_HIVE, TRUE) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + +/// Queen variant: strongly prefers staying in hive. +/datum/bt_node/ai_behavior/enter_exit_hive/queen + flyback_chance = 85 + exit_chance = 5 + + +/// Pollinates the hydro tray at BB_TARGET_HYDRO. Must be adjacent. +/datum/bt_node/ai_behavior/pollinate_hydro + time_between_perform = 5 SECONDS + +/datum/bt_node/ai_behavior/pollinate_hydro/perform(seconds_per_tick, datum/ai_controller/controller) + var/obj/machinery/hydroponics/tray = controller.blackboard[BB_TARGET_HYDRO] + var/mob/living/basic/bee/bee_pawn = controller.pawn + if(QDELETED(tray) || !bee_pawn.Adjacent(tray)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + bee_pawn.pollinate(tray) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + +/datum/bt_node/ai_behavior/pollinate_hydro/finish_action(datum/ai_controller/controller, succeeded, ...) + . = ..() + controller.clear_blackboard_key(BB_TARGET_HYDRO) + + +/// Picks a random turf near BB_SWARM_TARGET and stores it in BB_SWIRL_TURF for move_to_target. +/// Succeeds when a turf is found; fails otherwise so the parent sequence fails and retries. +/datum/bt_node/ai_behavior/swirl_around_target + var/swirl_chance = 60 + +/datum/bt_node/ai_behavior/swirl_around_target/perform(seconds_per_tick, datum/ai_controller/controller) + var/atom/target = controller.blackboard[BB_SWARM_TARGET] + var/mob/living/bee_pawn = controller.pawn + if(QDELETED(target)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + + if(!SPT_PROB(swirl_chance, seconds_per_tick)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + var/list/possible_turfs = list() + for(var/turf/possible_turf in oview(2, target)) + if(!possible_turf.is_blocked_turf(source_atom = bee_pawn)) + possible_turfs += possible_turf + + if(!length(possible_turfs)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + controller.set_blackboard_key(BB_SWIRL_TURF, pick(possible_turfs)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED + + +/// Scatter command: runs away from BB_CURRENT_PET_TARGET then clears the command. +/datum/bt_node/subtree/pet_command/scatter + behavior_tree_json = "code/datums/ai/basic_mobs/pet_commands/pet_command_scatter.bt.json" + +/// Swirl command: swarm around BB_SWARM_TARGET continuously (no auto-clear). +/datum/bt_node/subtree/pet_command/swirl + behavior_tree_json = "code/datums/ai/basic_mobs/pet_commands/pet_command_swirl.bt.json" + +/// Beehive command: move to hive and enter/exit it. +/datum/bt_node/subtree/pet_command/beehive + behavior_tree_json = "code/datums/ai/basic_mobs/pet_commands/pet_command_beehive.bt.json" diff --git a/code/modules/mob/living/basic/farm_animals/bee/find_hive.bt.json b/code/modules/mob/living/basic/farm_animals/bee/find_hive.bt.json new file mode 100644 index 00000000000..f6f7442f982 --- /dev/null +++ b/code/modules/mob/living/basic/farm_animals/bee/find_hive.bt.json @@ -0,0 +1,43 @@ +{ + "dm_type": "/datum/bt_node/subtree/find_hive", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "invert": false, + "key": "BB_TARGET_HOME" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CURRENT_HOME", + "invert": true + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_HOME", + "required_dist": 1, + "finish_on_arrival": false + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/inhabit_hive" + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/clear_key", + "vars": { + "key": "BB_TARGET_HOME" + } + } + ] + } + } +} diff --git a/code/modules/mob/living/basic/farm_animals/bee/pollinate_target.bt.json b/code/modules/mob/living/basic/farm_animals/bee/pollinate_target.bt.json new file mode 100644 index 00000000000..0489fac6fb7 --- /dev/null +++ b/code/modules/mob/living/basic/farm_animals/bee/pollinate_target.bt.json @@ -0,0 +1,27 @@ +{ + "dm_type": "/datum/bt_node/subtree/pollinate_target", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "key": "BB_TARGET_HYDRO" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_TARGET_HYDRO", + "required_dist": 1, + "finish_on_arrival": true + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/pollinate_hydro" + } + ] + } +} diff --git a/code/modules/mob/living/basic/farm_animals/bee/queen_bee.bt.json b/code/modules/mob/living/basic/farm_animals/bee/queen_bee.bt.json new file mode 100644 index 00000000000..be98e5038ef --- /dev/null +++ b/code/modules/mob/living/basic/farm_animals/bee/queen_bee.bt.json @@ -0,0 +1,58 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/queen_bee", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": false, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": false, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/find_hive" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/pollinate_target" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/transition_hive_status" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "1 SECONDS", + "finish_on_primary": true, + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_hostile_combat" + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/enter_exit_hive/queen" + } + ] + } + ] + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "invert": true, + "key": "BB_TARGET_HOME" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/find_hive" + } + } + ] +} diff --git a/code/modules/mob/living/basic/farm_animals/bee/transition_hive_status.bt.json b/code/modules/mob/living/basic/farm_animals/bee/transition_hive_status.bt.json new file mode 100644 index 00000000000..612f7d683ae --- /dev/null +++ b/code/modules/mob/living/basic/farm_animals/bee/transition_hive_status.bt.json @@ -0,0 +1,45 @@ +{ + "dm_type": "/datum/bt_node/subtree/transition_hive_status", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_equals", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "key": "BB_WANTS_TO_TRANSITION_HIVE", + "value": true + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_HOME", + "required_dist": 1 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/ai_interact", + "vars": { + "target_key": "BB_CURRENT_HOME" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/clear_key", + "vars": { + "key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/set_bb_key", + "vars": { + "target_key": "BB_WANTS_TO_TRANSITION_HIVE", + "value": false + } + } + ] + } +} diff --git a/code/modules/mob/living/basic/farm_animals/chicken/chick.bt.json b/code/modules/mob/living/basic/farm_animals/chicken/chick.bt.json new file mode 100644 index 00000000000..4d7a67fca03 --- /dev/null +++ b/code/modules/mob/living/basic/farm_animals/chicken/chick.bt.json @@ -0,0 +1,87 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/chick", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_FOUND_MOM", + "observer_abort": "BT_ABORT_SELF" + }, + "child": { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/is_at_distance", + "vars": { + "target_key": "BB_FOUND_MOM", + "min_distance": 2 + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_FOUND_MOM", + "required_dist": 1, + "finish_on_arrival": true + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/random_chance", + "vars": { + "chance": 0.15 + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/look_to_parent", + "vars": { + "parent_key": "BB_FOUND_MOM" + } + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + } + ] + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_FOUND_MOM", + "invert": true + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/find_mom" + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop" + } + ] +} diff --git a/code/modules/mob/living/basic/farm_animals/chicken/chick.dm b/code/modules/mob/living/basic/farm_animals/chicken/chick.dm index 44d2c4a00ee..407556f807e 100644 --- a/code/modules/mob/living/basic/farm_animals/chicken/chick.dm +++ b/code/modules/mob/living/basic/farm_animals/chicken/chick.dm @@ -81,15 +81,18 @@ grow_as = null /datum/ai_controller/basic_controller/chick + behavior_tree_json = "code/modules/mob/living/basic/farm_animals/chicken/chick.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_FIND_MOM_TYPES = list(/mob/living/basic/chicken), + BB_BASIC_MOB_SPEAK_LINES = list( + BB_EMOTE_SAY = list("Cherp.", "Cherp?", "Chirrup.", "Cheep!"), + BB_EMOTE_HEAR = list("cheeps."), + BB_EMOTE_SEE = list("pecks at the ground.","flaps her tiny wings."), + BB_EMOTE_SOUND = list('sound/mobs/non-humanoids/chicken/chick_peep.ogg'), + BB_SPEAK_CHANCE = 4, + ), ) ai_traits = PASSIVE_AI_FLAGS ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - - planning_subtrees = list( - /datum/ai_planning_subtree/look_for_adult, - ) diff --git a/code/modules/mob/living/basic/farm_animals/chicken/chicken.bt.json b/code/modules/mob/living/basic/farm_animals/chicken/chicken.bt.json new file mode 100644 index 00000000000..fc59585dab7 --- /dev/null +++ b/code/modules/mob/living/basic/farm_animals/chicken/chicken.bt.json @@ -0,0 +1,5 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/chicken", + "type": "subtree", + "subtype": "/datum/bt_node/subtree/skittish_and_speak" +} diff --git a/code/modules/mob/living/basic/farm_animals/chicken/chicken.dm b/code/modules/mob/living/basic/farm_animals/chicken/chicken.dm index b8dbefd78ab..3d2bb5b0996 100644 --- a/code/modules/mob/living/basic/farm_animals/chicken/chicken.dm +++ b/code/modules/mob/living/basic/farm_animals/chicken/chicken.dm @@ -94,17 +94,18 @@ GLOBAL_VAR_INIT(chicken_count, 0) ) /datum/ai_controller/basic_controller/chicken + behavior_tree_json = "code/modules/mob/living/basic/farm_animals/chicken/chicken.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_BASIC_MOB_SPEAK_LINES = list( + BB_EMOTE_SAY = list("Cluck!", "BWAAAAARK BWAK BWAK BWAK!", "Bwaak bwak."), + BB_EMOTE_HEAR = list("clucks.", "croons."), + BB_EMOTE_SEE = list("pecks at the ground.", "flaps her wings viciously."), + BB_EMOTE_SOUND = list('sound/mobs/non-humanoids/chicken/clucks.ogg', 'sound/mobs/non-humanoids/chicken/bagawk.ogg'), + BB_SPEAK_CHANCE = 15, + ), ) ai_traits = PASSIVE_AI_FLAGS ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - - planning_subtrees = list( - /datum/ai_planning_subtree/find_nearest_thing_which_attacked_me_to_flee, - /datum/ai_planning_subtree/flee_target, - /datum/ai_planning_subtree/random_speech/chicken, - ) diff --git a/code/modules/mob/living/basic/farm_animals/cow/_cow.dm b/code/modules/mob/living/basic/farm_animals/cow/_cow.dm index 3c6b1e81422..7fcbb5eb84e 100644 --- a/code/modules/mob/living/basic/farm_animals/cow/_cow.dm +++ b/code/modules/mob/living/basic/farm_animals/cow/_cow.dm @@ -90,7 +90,7 @@ * tipper - the mob who tipped us */ /mob/living/basic/cow/proc/after_cow_tipped(mob/living/carbon/tipper) - addtimer(CALLBACK(src, PROC_REF(set_tip_react_blackboard), tipper), rand(10 SECONDS, 20 SECONDS)) + addtimer(CALLBACK(src, PROC_REF(set_tip_react_blackboard), tipper), rand(10 SECONDS, 20 SECONDS)) /* * We've been waiting long enough, we're going to tell our AI to begin pleading. diff --git a/code/modules/mob/living/basic/farm_animals/cow/cow.bt.json b/code/modules/mob/living/basic/farm_animals/cow/cow.bt.json new file mode 100644 index 00000000000..f1a812b7e67 --- /dev/null +++ b/code/modules/mob/living/basic/farm_animals/cow/cow.bt.json @@ -0,0 +1,48 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/cow", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/tip_reaction" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/move_to_and_eat" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + } + ] + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/find_food" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop" + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/farm_animals/cow/cow_ai.dm b/code/modules/mob/living/basic/farm_animals/cow/cow_ai.dm index df485197d33..ece35ce9657 100644 --- a/code/modules/mob/living/basic/farm_animals/cow/cow_ai.dm +++ b/code/modules/mob/living/basic/farm_animals/cow/cow_ai.dm @@ -1,15 +1,21 @@ /datum/ai_controller/basic_controller/cow + behavior_tree_json = "code/modules/mob/living/basic/farm_animals/cow/cow.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_BASIC_MOB_TIP_REACTING = FALSE, BB_BASIC_MOB_TIPPER = null, + BB_BASIC_MOB_SPEAK_LINES = list( + BB_EMOTE_SAY = list("moo?", "moo", "MOOOOOO"), + BB_EMOTE_HEAR = list("brays."), + BB_EMOTE_SEE = list("shakes her head."), + BB_EMOTE_SOUND = list('sound/mobs/non-humanoids/cow/cow.ogg'), + BB_SPEAK_CHANCE = 1, + ), ) ai_traits = PASSIVE_AI_FLAGS ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/tip_reaction, - /datum/ai_planning_subtree/find_food, - /datum/ai_planning_subtree/random_speech/cow, - ) + +/// While we're tipped over, plead with nearby people instead of doing anything else. +/datum/bt_node/subtree/tip_reaction + behavior_tree_json = "code/datums/ai/basic_mobs/basic_subtrees/tip_reaction.bt.json" diff --git a/code/modules/mob/living/basic/farm_animals/cow/cow_moonicorn.bt.json b/code/modules/mob/living/basic/farm_animals/cow/cow_moonicorn.bt.json new file mode 100644 index 00000000000..dd56e0306bf --- /dev/null +++ b/code/modules/mob/living/basic/farm_animals/cow/cow_moonicorn.bt.json @@ -0,0 +1,96 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/cow/moonicorn", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/tip_reaction" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/move_to_and_eat" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/find_food" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop" + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/farm_animals/cow/cow_moonicorn.dm b/code/modules/mob/living/basic/farm_animals/cow/cow_moonicorn.dm index 25879d07d5c..5ee91b3a913 100644 --- a/code/modules/mob/living/basic/farm_animals/cow/cow_moonicorn.dm +++ b/code/modules/mob/living/basic/farm_animals/cow/cow_moonicorn.dm @@ -40,27 +40,24 @@ APPLY_FACTION_AND_ALLIES_FROM(src, tamer) /datum/ai_controller/basic_controller/cow/moonicorn + behavior_tree_json = "code/modules/mob/living/basic/farm_animals/cow/cow_moonicorn.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/allow_items/moonicorn, BB_BASIC_MOB_TIP_REACTING = FALSE, BB_BASIC_MOB_TIPPER = null, - ) - - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/tip_reaction, - /datum/ai_planning_subtree/random_speech/cow, - //finds someone to kill - /datum/ai_planning_subtree/simple_find_target, - //...or something to eat, possibly. both types of target handled by melee attack subtree - /datum/ai_planning_subtree/find_food, - /datum/ai_planning_subtree/basic_melee_attack_subtree, + BB_BASIC_MOB_SPEAK_LINES = list( + BB_EMOTE_SAY = list("moo?", "moo", "MOOOOOO"), + BB_EMOTE_HEAR = list("brays."), + BB_EMOTE_SEE = list("shakes her head."), + BB_EMOTE_SOUND = list('sound/mobs/non-humanoids/cow/cow.ogg'), + BB_SPEAK_CHANCE = 1, + ), ) ///moonicorns will not attack people holding something that could tame them. /datum/targeting_strategy/basic/allow_items/moonicorn -/datum/targeting_strategy/basic/allow_items/moonicorn/can_attack(mob/living/living_mob, atom/the_target, vision_range) +/datum/targeting_strategy/basic/allow_items/moonicorn/is_valid_target(mob/living/living_mob, atom/the_target, vision_range, datum/ai_controller/controller = null) . = ..() if(!.) return FALSE diff --git a/code/modules/mob/living/basic/farm_animals/cow/cow_wisdom.bt.json b/code/modules/mob/living/basic/farm_animals/cow/cow_wisdom.bt.json new file mode 100644 index 00000000000..300def27d68 --- /dev/null +++ b/code/modules/mob/living/basic/farm_animals/cow/cow_wisdom.bt.json @@ -0,0 +1,28 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/cow/wisdom", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/tip_reaction" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "1 SECONDS", + "finish_on_primary": true, + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop" + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/farm_animals/cow/cow_wisdom.dm b/code/modules/mob/living/basic/farm_animals/cow/cow_wisdom.dm index 4452ae3100f..ef782795eb6 100644 --- a/code/modules/mob/living/basic/farm_animals/cow/cow_wisdom.dm +++ b/code/modules/mob/living/basic/farm_animals/cow/cow_wisdom.dm @@ -12,6 +12,10 @@ /mob/living/basic/cow/wisdom/Initialize(mapload, granted_wisdom, granted_experience = 500, milked_reagent = null) src.milked_reagent = milked_reagent . = ..() + ai_controller.set_blackboard_key(BB_BASIC_MOB_SPEAK_LINES, list( + BB_EMOTE_SAY = GLOB.wisdoms, + BB_SPEAK_CHANCE = 15, + )) src.granted_wisdom = granted_wisdom if(!granted_wisdom) src.granted_wisdom = pick(GLOB.skill_types) @@ -28,15 +32,12 @@ return ..() /datum/ai_controller/basic_controller/cow/wisdom + behavior_tree_json = "code/modules/mob/living/basic/farm_animals/cow/cow_wisdom.bt.json" //don't give a targeting strategy blackboard = list( BB_BASIC_MOB_TIP_REACTING = FALSE, BB_BASIC_MOB_TIPPER = null, - ) - - planning_subtrees = list( - /datum/ai_planning_subtree/tip_reaction, - /datum/ai_planning_subtree/random_speech/cow/wisdom, + BB_BASIC_MOB_SPEAK_LINES = null, ) ///Give intense wisdom to the attacker if they're being friendly about it diff --git a/code/modules/mob/living/basic/farm_animals/deer/deer.bt.json b/code/modules/mob/living/basic/farm_animals/deer/deer.bt.json new file mode 100644 index 00000000000..a3620e19a8d --- /dev/null +++ b/code/modules/mob/living/basic/farm_animals/deer/deer.bt.json @@ -0,0 +1,372 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/deer", + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CURRENT_TARGET", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/run_away_from_target" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_STATIONARY_CAUSE", + "observer_abort": "BT_ABORT_LOWER_PRIORITY" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/stop_and_stare" + } + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "1 SECONDS", + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/key_off_cooldown", + "vars": { + "cooldown_key": "BB_DEER_NEXT_REST_TIMER" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_SELF", + "key": "BB_DEER_TREEHOME" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_DEER_TREEHOME", + "required_dist": 1 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/deer_rest" + } + ] + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_DEER_PLAYFRIEND" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_DEER_PLAYFRIEND", + "required_dist": 1 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/set_bb_cooldown", + "vars": { + "cooldown_key": "BB_DEER_PLAY_COOLDOWN", + "cooldown_duration": "10 SECONDS" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/deer_play" + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "key": "BB_DEER_TREE_TARGET" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_DEER_TREE_TARGET", + "required_dist": 1 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/hunt_target/deer_mark", + "vars": { + "target_key": "BB_DEER_TREE_TARGET", + "cooldown_key": "BB_DEER_MARK_COOLDOWN" + } + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_DEER_GRASS_TARGET" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_DEER_GRASS_TARGET", + "required_dist": 0 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/hunt_target/deer_graze", + "vars": { + "target_key": "BB_DEER_GRASS_TARGET", + "cooldown_key": "BB_DEER_GRAZE_COOLDOWN" + } + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_DEER_WATER_TARGET" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_DEER_WATER_TARGET", + "required_dist": 1 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/hunt_target/deer_drink", + "vars": { + "target_key": "BB_DEER_WATER_TARGET", + "cooldown_key": "BB_DEER_DRINK_COOLDOWN" + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/random_speech_blackboard" + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/key_off_cooldown", + "vars": { + "cooldown_key": "BB_DEER_PLAY_COOLDOWN" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/random_chance", + "vars": { + "chance": 0.03 + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/set_bb_key", + "vars": { + "target_key": "BB_DEER_WANTS_TO_PLAY", + "value": true + } + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_true", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_DEER_WANTS_TO_PLAY" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_DEER_PLAYFRIEND", + "target_source": "/datum/target_source/oview_single_type/deer_animals", + "targeting_strategy": "/datum/targeting_strategy/playable_deer" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/set_bb_key", + "vars": { + "target_key": "BB_DEER_WANTS_TO_PLAY", + "value": false + } + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "invert": true, + "key": "BB_DEER_TREEHOME" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "invert": true, + "key": "BB_DEER_TREE_TARGET" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/key_off_cooldown", + "vars": { + "cooldown_key": "BB_DEER_MARK_COOLDOWN" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_DEER_TREE_TARGET", + "target_source": "/datum/target_source/oview_single_type/flora_tree", + "targeting_strategy": "/datum/targeting_strategy/anything" + } + } + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "invert": true, + "key": "BB_DEER_GRASS_TARGET" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/key_off_cooldown", + "vars": { + "cooldown_key": "BB_DEER_GRAZE_COOLDOWN" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_DEER_GRASS_TARGET", + "target_source": "/datum/target_source/range_turfs/typecache_visible/deer_grass", + "targeting_strategy": "/datum/targeting_strategy/anything" + } + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "invert": true, + "key": "BB_DEER_WATER_TARGET" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/key_off_cooldown", + "vars": { + "cooldown_key": "BB_DEER_DRINK_COOLDOWN" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_DEER_WATER_TARGET", + "target_source": "/datum/target_source/range_turfs/typecache_visible/deer_water", + "targeting_strategy": "/datum/targeting_strategy/anything" + } + } + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list/nearest", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_STATIONARY_CAUSE", + "target_source": "/datum/target_source/oview_typed/from_bb_key/stationary_targets", + "targeting_strategy": "/datum/targeting_strategy/anything" + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/farm_animals/deer/deer_ai.dm b/code/modules/mob/living/basic/farm_animals/deer/deer_ai.dm index a654e0c29d0..0506827d589 100644 --- a/code/modules/mob/living/basic/farm_animals/deer/deer_ai.dm +++ b/code/modules/mob/living/basic/farm_animals/deer/deer_ai.dm @@ -1,156 +1,95 @@ /datum/ai_controller/basic_controller/deer + behavior_tree_json = "code/modules/mob/living/basic/farm_animals/deer/deer.bt.json" blackboard = list( BB_STATIONARY_MOVE_TO_TARGET = TRUE, BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_BASIC_MOB_SPEAK_LINES = list( + BB_EMOTE_SAY = list("Weeeeeeee?", "Weeee", "WEOOOOOOOOOO"), + BB_EMOTE_HEAR = list("brays."), + BB_EMOTE_SEE = list("shakes her head."), + BB_SPEAK_CHANCE = 1, + ), ) ai_traits = PASSIVE_AI_FLAGS ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/random_speech/deer, - /datum/ai_planning_subtree/stare_at_thing, - /datum/ai_planning_subtree/find_nearest_thing_which_attacked_me_to_flee, - /datum/ai_planning_subtree/flee_target, - /datum/ai_planning_subtree/rest_at_home, - /datum/ai_planning_subtree/play_with_friends, - /datum/ai_planning_subtree/find_and_hunt_target/mark_territory, - /datum/ai_planning_subtree/find_and_hunt_target/graze, - /datum/ai_planning_subtree/find_and_hunt_target/drink_water, - ) - -///subtree to go around drinking water -/datum/ai_planning_subtree/find_and_hunt_target/drink_water - target_key = BB_DEER_WATER_TARGET - finding_behavior = /datum/ai_behavior/find_and_set/in_list/turf_types - hunting_behavior = /datum/ai_behavior/hunt_target/drink_water - hunt_targets = list(/turf/open/water) - hunt_range = 7 - hunt_chance = 5 - - -/datum/ai_behavior/hunt_target/drink_water - always_reset_target = TRUE - hunt_cooldown = 20 SECONDS - - -/datum/ai_behavior/hunt_target/drink_water/target_caught(mob/living/hunter, atom/hunted) - var/static/list/possible_emotes = list("drinks the water!", "dances in the water!", "splashes around happily!") - hunter.manual_emote(pick(possible_emotes)) - - -///subtree to go around grazing -/datum/ai_planning_subtree/find_and_hunt_target/graze - target_key = BB_DEER_GRASS_TARGET - finding_behavior = /datum/ai_behavior/find_and_set/in_list/turf_types - hunting_behavior = /datum/ai_behavior/hunt_target/eat_grass - hunt_targets = list(/turf/open/floor/grass, /turf/open/misc/grass) - hunt_range = 7 - hunt_chance = 45 - - -/datum/ai_behavior/hunt_target/eat_grass +/// Munches grass with a happy little emote. +/datum/bt_node/ai_behavior/hunt_target/deer_graze always_reset_target = TRUE hunt_cooldown = 15 SECONDS - -/datum/ai_behavior/hunt_target/eat_grass/target_caught(mob/living/hunter, atom/hunted) +/datum/bt_node/ai_behavior/hunt_target/deer_graze/target_caught(mob/living/hunter, atom/hunted) var/static/list/possible_emotes = list("eats the grass!", "munches down the grass!", "chews on the grass!") hunter.manual_emote(pick(possible_emotes)) -///subtree to go around playing with other deers -/datum/ai_planning_subtree/play_with_friends +/// Splashes happily in the water. +/datum/bt_node/ai_behavior/hunt_target/deer_drink + always_reset_target = TRUE + hunt_cooldown = 20 SECONDS + +/datum/bt_node/ai_behavior/hunt_target/deer_drink/target_caught(mob/living/hunter, atom/hunted) + var/static/list/possible_emotes = list("drinks the water!", "dances in the water!", "splashes around happily!") + hunter.manual_emote(pick(possible_emotes)) -/datum/ai_planning_subtree/play_with_friends/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/static/list/emote_list = list("plays with", "dances with", "celebrates with") - var/static/list/friend_types = typecacheof(list(/mob/living/basic/deer)) - if(controller.blackboard_key_exists(BB_DEER_PLAYFRIEND)) - controller.queue_behavior(/datum/ai_behavior/emote_on_target, BB_DEER_PLAYFRIEND, emote_list) - if(SPT_PROB(3, seconds_per_tick)) - controller.queue_behavior(/datum/ai_behavior/find_hunt_target/valid_deer, BB_DEER_PLAYFRIEND, friend_types) - return SUBTREE_RETURN_FINISH_PLANNING - - -/datum/ai_behavior/emote_on_target/deer_play - - -/datum/ai_behavior/emote_on_target/deer_play/run_emote(mob/living/living_pawn, atom/target, list/emote_list) - . = ..() - living_pawn.spin(spintime = 4, speed = 1) - - -/datum/ai_behavior/find_hunt_target/valid_deer/valid_dinner(mob/living/source, mob/living/deer, radius, datum/ai_controller/controller, seconds_per_tick) - if(deer.stat == DEAD) - return FALSE - if(!can_see(source, deer, radius)) - return FALSE - deer.ai_controller?.set_blackboard_key(BB_DEER_PLAYFRIEND, source) - return can_see(source, deer, radius) - - -///subtree to mark trees as territories -/datum/ai_planning_subtree/find_and_hunt_target/mark_territory - target_key = BB_DEER_TREE_TARGET - finding_behavior = /datum/ai_behavior/find_hunt_target - hunting_behavior = /datum/ai_behavior/hunt_target/mark_territory - hunt_targets = list(/obj/structure/flora/tree) - hunt_range = 7 - hunt_chance = 75 - - -/datum/ai_behavior/hunt_target/mark_territory +/// Claims a tree as home territory. +/datum/bt_node/ai_behavior/hunt_target/deer_mark always_reset_target = TRUE hunt_cooldown = 15 SECONDS - -/datum/ai_behavior/hunt_target/mark_territory/target_caught(mob/living/hunter, atom/hunted) +/datum/bt_node/ai_behavior/hunt_target/deer_mark/target_caught(mob/living/hunter, atom/hunted) hunter.manual_emote("marks [hunted] with its hooves!") - hunter.ai_controller.set_blackboard_key(BB_DEER_TREEHOME, hunted) + hunter.ai_controller?.set_blackboard_key(BB_DEER_TREEHOME, hunted) -/datum/ai_planning_subtree/find_and_hunt_target/mark_territory/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(controller.blackboard_key_exists(BB_DEER_TREEHOME)) //already found our home, abort! - return - return ..() - - -/datum/ai_planning_subtree/rest_at_home/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(controller.blackboard[BB_DEER_RESTING] > world.time) //we're resting for now, nothing more to do - return SUBTREE_RETURN_FINISH_PLANNING - if(!controller.blackboard_key_exists(BB_DEER_TREEHOME) || controller.blackboard[BB_DEER_NEXT_REST_TIMER] > world.time) - return - controller.queue_behavior(/datum/ai_behavior/return_home, BB_DEER_TREEHOME) - - -/datum/ai_behavior/return_home - required_distance = 0 - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION - ///minimum time till next rest +/// Rests by its home tree, then won't feel the need to rest again for a while. +/datum/bt_node/ai_behavior/deer_rest + /// How long we stand resting once we arrive (kept RUNNING via the cooldown). + var/rest_duration = 15 SECONDS + /// Minimum/maximum time before we want to rest again. var/minimum_time = 2 MINUTES - ///maximum time till next rest var/maximum_time = 4 MINUTES + ///ID for the rest timer + var/timerid + ///Are we napping? + var/sleeping - -/datum/ai_behavior/return_home/setup(datum/ai_controller/controller, target_key) +/datum/bt_node/ai_behavior/deer_rest/setup(datum/ai_controller/controller) . = ..() - var/atom/target = controller.blackboard[target_key] - if(QDELETED(target)) - return FALSE - var/list/possible_turfs = get_adjacent_open_turfs(target) - shuffle_inplace(possible_turfs) - for(var/turf/possible_turf as anything in possible_turfs) - if(!possible_turf.is_blocked_turf()) - set_movement_target(controller, possible_turf) - return TRUE - return FALSE + timerid = addtimer(CALLBACK(src, PROC_REF(finish_action), controller, TRUE), rest_duration, TIMER_UNIQUE | TIMER_STOPPABLE) - -/datum/ai_behavior/return_home/perform(seconds_per_tick, datum/ai_controller/controller, target_key) +/datum/bt_node/ai_behavior/deer_rest/perform(seconds_per_tick, datum/ai_controller/controller) + if(sleeping) + return AI_BEHAVIOR_DELAY var/mob/living/living_pawn = controller.pawn var/static/list/possible_emotes = list("rests its legs...", "yawns and naps...", "curls up and rests...") living_pawn.manual_emote(pick(possible_emotes)) - controller.set_blackboard_key(BB_DEER_RESTING, world.time + 15 SECONDS) + sleeping = TRUE + return AI_BEHAVIOR_DELAY + +/datum/bt_node/ai_behavior/deer_rest/finish_action(datum/ai_controller/controller, succeeded) + . = ..() + deltimer(timerid) + timerid = null + sleeping = FALSE controller.set_blackboard_key(BB_DEER_NEXT_REST_TIMER, world.time + rand(minimum_time, maximum_time)) + +/// Plays with another deer, spinning around them. +/datum/bt_node/ai_behavior/deer_play + /// Blackboard key holding the deer we're playing with. + var/friend_key = BB_DEER_PLAYFRIEND + +/datum/bt_node/ai_behavior/deer_play/perform(seconds_per_tick, datum/ai_controller/controller) + var/atom/friend = controller.blackboard[friend_key] + if(QDELETED(friend)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + var/static/list/possible_emotes = list("plays with", "dances with", "celebrates with") + var/mob/living/living_pawn = controller.pawn + living_pawn.manual_emote("[pick(possible_emotes)] [friend]!") + living_pawn.spin(spintime = 4, speed = 1) return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + +/datum/bt_node/ai_behavior/deer_play/finish_action(datum/ai_controller/controller, succeeded) + . = ..() + controller.clear_blackboard_key(friend_key) diff --git a/code/modules/mob/living/basic/farm_animals/goat/goat.bt.json b/code/modules/mob/living/basic/farm_animals/goat/goat.bt.json new file mode 100644 index 00000000000..dcbaa93499f --- /dev/null +++ b/code/modules/mob/living/basic/farm_animals/goat/goat.bt.json @@ -0,0 +1,26 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/goat", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "1 SECONDS", + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/forage_and_retaliate" + } + ] + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop" + } + ] +} diff --git a/code/modules/mob/living/basic/farm_animals/goat/goat_ai.dm b/code/modules/mob/living/basic/farm_animals/goat/goat_ai.dm index af409c5763d..2a6edae756a 100644 --- a/code/modules/mob/living/basic/farm_animals/goat/goat_ai.dm +++ b/code/modules/mob/living/basic/farm_animals/goat/goat_ai.dm @@ -1,23 +1,15 @@ /// Goats are normally content to sorta hang around and crunch any plant in sight, but they will go ape on someone who attacks them. /datum/ai_controller/basic_controller/goat + behavior_tree_json = "code/modules/mob/living/basic/farm_animals/goat/goat.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_BASIC_MOB_SPEAK_LINES = list( + BB_EMOTE_SAY = list("EHEHEHEHEH", "eh?"), + BB_EMOTE_HEAR = list("brays."), + BB_EMOTE_SEE = list("shakes their head.", "stamps a foot.", "glares around."), + BB_SPEAK_CHANCE = 3, + ), ) ai_traits = DEFAULT_AI_FLAGS | STOP_MOVING_WHEN_PULLED ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - - planning_subtrees = list( - /datum/ai_planning_subtree/capricious_retaliate, // Capricious like Capra, get it? - /datum/ai_planning_subtree/target_retaliate, - /datum/ai_planning_subtree/find_food, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/random_speech/goat, - ) - -/datum/ai_planning_subtree/random_speech/goat - speech_chance = 3 - emote_hear = list("brays.") - emote_see = list("shakes their head.", "stamps a foot.", "glares around.") - speak = list("EHEHEHEHEH", "eh?") diff --git a/code/modules/mob/living/basic/farm_animals/goose/forage_for_goose_food.bt.json b/code/modules/mob/living/basic/farm_animals/goose/forage_for_goose_food.bt.json new file mode 100644 index 00000000000..f7fe2284a28 --- /dev/null +++ b/code/modules/mob/living/basic/farm_animals/goose/forage_for_goose_food.bt.json @@ -0,0 +1,11 @@ +{ + "dm_type": "/datum/bt_node/subtree/forage_for_goose_food", + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_TARGET_FOOD", + "targeting_strategy": "/datum/targeting_strategy/goose_edible", + "target_source": "/datum/target_source/oview_items", + "vision_range": 1 + } +} diff --git a/code/modules/mob/living/basic/farm_animals/goose/goose.bt.json b/code/modules/mob/living/basic/farm_animals/goose/goose.bt.json new file mode 100644 index 00000000000..3319b58f849 --- /dev/null +++ b/code/modules/mob/living/basic/farm_animals/goose/goose.bt.json @@ -0,0 +1,38 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/goose", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "1 SECONDS", + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/forage_and_retaliate", + "bindings": { + "q8w3rtv1": "/datum/bt_node/subtree/forage_for_goose_food" + } + } + ] + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop" + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/farm_animals/goose/goose.dm b/code/modules/mob/living/basic/farm_animals/goose/goose.dm index aa25ca7e9d7..935b3af656e 100644 --- a/code/modules/mob/living/basic/farm_animals/goose/goose.dm +++ b/code/modules/mob/living/basic/farm_animals/goose/goose.dm @@ -30,8 +30,6 @@ gold_core_spawnable = HOSTILE_SPAWN /// Do we actually destroy food we eat? var/conserve_food = FALSE - /// Unfortunately, geese want to eat every item - var/static/list/item_typecache = typecacheof(/obj/item) /mob/living/basic/goose/Initialize(mapload) . = ..() @@ -42,8 +40,6 @@ RegisterSignal(src, COMSIG_MOB_PRE_EAT, PROC_REF(on_tried_gobbling)) RegisterSignal(src, COMSIG_MOB_ATE, PROC_REF(on_gobbled)) - ai_controller.set_blackboard_key(BB_BASIC_FOODS, item_typecache) - /mob/living/basic/goose/death(gibbed) if (!gibbed && length(contents)) var/turf/drop_turf = drop_location() @@ -157,8 +153,7 @@ remove_status_effect(/datum/status_effect/goose_choking) // We're going to cough it out /mob/living/basic/goose/vomit/proc/stop_deadchat_plays() - var/initial_behaviour = initial(ai_controller?.idle_behavior) - ai_controller?.idle_behavior = SSidle_ai_behaviors.idle_behaviors[initial_behaviour] + ai_controller?.clear_blackboard_key(BB_DISABLE_IDLE) /mob/living/basic/goose/vomit/deadchat_plays(mode = ANARCHY_MODE, cooldown = 12 SECONDS) var/list/goose_inputs = list( @@ -172,6 +167,6 @@ return // Stop automated movement, retain the other behaviour so you can lead the horse to plastic and have it drink. - ai_controller?.idle_behavior = null + ai_controller?.set_blackboard_key(BB_DISABLE_IDLE, TRUE) #undef GOOSE_SATIATED diff --git a/code/modules/mob/living/basic/farm_animals/goose/goose_ai.dm b/code/modules/mob/living/basic/farm_animals/goose/goose_ai.dm index 94202a90df3..43412f19164 100644 --- a/code/modules/mob/living/basic/farm_animals/goose/goose_ai.dm +++ b/code/modules/mob/living/basic/farm_animals/goose/goose_ai.dm @@ -1,83 +1,38 @@ /// Geese like to eat random objects and kill themselves, and occasionally get pissed off for no reason /datum/ai_controller/basic_controller/goose + behavior_tree_json = "code/modules/mob/living/basic/farm_animals/goose/goose.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_SEARCH_RANGE = 1, BB_EAT_FOOD_COOLDOWN = 10 SECONDS, - BB_EAT_EMOTES = list() + BB_EAT_EMOTES = list(), + BB_BASIC_MOB_SPEAK_LINES = list( + BB_EMOTE_SAY = list("Honk!"), + BB_EMOTE_HEAR = list("honks.", "honks loudly.", "honks aggressively."), + BB_EMOTE_SEE = list("flaps.", "preens.", "glares around."), + BB_SPEAK_CHANCE = 3, + ), ) ai_traits = PASSIVE_AI_FLAGS ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk/goose - - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/random_speech/goose, - /datum/ai_planning_subtree/capricious_retaliate, - /datum/ai_planning_subtree/target_retaliate, - /datum/ai_planning_subtree/find_food/goose, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) /// Goose who doesn't randomly retaliate but does still try to die by eating random items /datum/ai_controller/basic_controller/goose/calm + behavior_tree_json = "code/modules/mob/living/basic/farm_animals/goose/goose_calm.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_SEARCH_RANGE = 1, BB_EAT_FOOD_COOLDOWN = 0.5 SECONDS, // Uh oh - BB_EAT_EMOTES = list() - ) - planning_subtrees = list( - /datum/ai_planning_subtree/random_speech/goose, - /datum/ai_planning_subtree/use_mob_ability/goose_vomit, - /datum/ai_planning_subtree/target_retaliate, - /datum/ai_planning_subtree/find_food/goose, - /datum/ai_planning_subtree/basic_melee_attack_subtree, + BB_EAT_EMOTES = list(), + BB_BASIC_MOB_SPEAK_LINES = list( + BB_EMOTE_SAY = list("Honk!"), + BB_EMOTE_HEAR = list("honks.", "honks loudly.", "honks aggressively."), + BB_EMOTE_SEE = list("flaps.", "preens.", "glares around."), + BB_SPEAK_CHANCE = 3, + ), ) -/// Walk more if we're choking or vomiting -/datum/idle_behavior/idle_random_walk/goose - -/datum/idle_behavior/idle_random_walk/goose/perform_idle_behavior(seconds_per_tick, datum/ai_controller/controller) - walk_chance = controller.blackboard[BB_GOOSE_PANICKED] ? 100 : 25 // I think this sets it for every goose but that's fine because it'll reset it before using it - return ..() - -/// Only look for things geese will try to eat -/datum/ai_planning_subtree/find_food/goose - finding_behavior = /datum/ai_behavior/find_and_set/in_list/goose_food - -/datum/ai_planning_subtree/find_food/goose/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if (controller.blackboard[BB_GOOSE_PANICKED]) - return // Don't look for food while choking or vomiting - return ..() - -/// Only set things geese will try to eat -/datum/ai_behavior/find_and_set/in_list/goose_food - -/datum/ai_behavior/find_and_set/in_list/goose_food/search_tactic(datum/ai_controller/controller, locate_paths, search_range = SEARCH_TACTIC_DEFAULT_RANGE) - var/list/found = typecache_filter_list(oview(search_range, controller.pawn), locate_paths) - if(!length(found)) - return - - var/list/filtered = list() - for (var/obj/item/thing as anything in found) - if (IS_EDIBLE(thing) || thing.has_material_type(/datum/material/plastic)) - filtered += thing - - if(length(filtered)) - return pick(filtered) - -/// Use this ability only if we roll a dice correctly -/datum/ai_planning_subtree/use_mob_ability/goose_vomit - -/datum/ai_planning_subtree/use_mob_ability/goose_vomit/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/trigger_prob = controller.blackboard[BB_GOOSE_VOMIT_CHANCE] || 0 - if (prob(trigger_prob)) - return ..() - -/datum/ai_planning_subtree/random_speech/goose - speech_chance = 3 - emote_hear = list("honks.", "honks loudly.", "honks aggressively.") - emote_see = list("flaps.", "preens.", "glares around.") - speak = list("Honk!") +/// Geese are picky: they only forage for edible items and plastic, and only when it's right next to them. +/datum/bt_node/subtree/forage_for_goose_food + behavior_tree_json = "code/modules/mob/living/basic/farm_animals/goose/forage_for_goose_food.bt.json" diff --git a/code/modules/mob/living/basic/farm_animals/goose/goose_calm.bt.json b/code/modules/mob/living/basic/farm_animals/goose/goose_calm.bt.json new file mode 100644 index 00000000000..c6069c54714 --- /dev/null +++ b/code/modules/mob/living/basic/farm_animals/goose/goose_calm.bt.json @@ -0,0 +1,30 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/goose/calm", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "1 SECONDS", + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/forage_and_retaliate", + "bindings": { + "q8w3rtv1": "/datum/bt_node/subtree/forage_for_goose_food", + "z4n9bk7p": "/datum/bt_node/subtree/pick_retaliate_target" + } + } + ] + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop" + } + ] +} diff --git a/code/modules/mob/living/basic/farm_animals/gorilla/gorilla.bt.json b/code/modules/mob/living/basic/farm_animals/gorilla/gorilla.bt.json new file mode 100644 index 00000000000..f49bf37a5da --- /dev/null +++ b/code/modules/mob/living/basic/farm_animals/gorilla/gorilla.bt.json @@ -0,0 +1,130 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/gorilla", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/attack_obstructions", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "can_attack_turfs": true + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/move_to_and_eat" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/find_food" + }, + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "loop_delay": "1 SECONDS", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/random_chance_from_key", + "vars": { + "chance_key": "BB_EMOTE_CHANCE" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/run_emote", + "vars": { + "emote_key": "BB_EMOTE_KEY" + } + } + } + ] + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/farm_animals/gorilla/gorilla_ai.dm b/code/modules/mob/living/basic/farm_animals/gorilla/gorilla_ai.dm index ba9584f4bc1..98b6f6da905 100644 --- a/code/modules/mob/living/basic/farm_animals/gorilla/gorilla_ai.dm +++ b/code/modules/mob/living/basic/farm_animals/gorilla/gorilla_ai.dm @@ -1,5 +1,6 @@ /// Pretty basic, just click people to death. Also hunt and eat bananas. /datum/ai_controller/basic_controller/gorilla + behavior_tree_json = "code/modules/mob/living/basic/farm_animals/gorilla/gorilla.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_TARGET_MINIMUM_STAT = UNCONSCIOUS, @@ -9,22 +10,6 @@ ai_traits = DEFAULT_AI_FLAGS | STOP_MOVING_WHEN_PULLED ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/run_emote, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/attack_obstacle_in_path/gorilla, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/find_food, - ) - -/datum/ai_planning_subtree/attack_obstacle_in_path/gorilla - attack_behaviour = /datum/ai_behavior/attack_obstructions/gorilla - -/datum/ai_behavior/attack_obstructions/gorilla - can_attack_turfs = TRUE /datum/ai_controller/basic_controller/gorilla/lesser blackboard = list( diff --git a/code/modules/mob/living/basic/farm_animals/pig.bt.json b/code/modules/mob/living/basic/farm_animals/pig.bt.json new file mode 100644 index 00000000000..5cb9d774e7b --- /dev/null +++ b/code/modules/mob/living/basic/farm_animals/pig.bt.json @@ -0,0 +1,26 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/pig", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "1 SECONDS", + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/skittish_brawler_combat" + } + ] + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop" + } + ] +} diff --git a/code/modules/mob/living/basic/farm_animals/pig.dm b/code/modules/mob/living/basic/farm_animals/pig.dm index 39008cab867..6ba56fe5a19 100644 --- a/code/modules/mob/living/basic/farm_animals/pig.dm +++ b/code/modules/mob/living/basic/farm_animals/pig.dm @@ -59,18 +59,17 @@ visible_message(span_notice("[src] snorts respectfully.")) /datum/ai_controller/basic_controller/pig + behavior_tree_json = "code/modules/mob/living/basic/farm_animals/pig.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_BASIC_MOB_SPEAK_LINES = list( + BB_EMOTE_SAY = list("oink?", "oink", "snurf"), + BB_EMOTE_HEAR = list("snorts."), + BB_EMOTE_SEE = list("sniffs around."), + BB_EMOTE_SOUND = list('sound/mobs/non-humanoids/pig/pig1.ogg', 'sound/mobs/non-humanoids/pig/pig2.ogg'), + BB_SPEAK_CHANCE = 3, + ), ) ai_traits = PASSIVE_AI_FLAGS ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - - planning_subtrees = list( - /datum/ai_planning_subtree/find_nearest_thing_which_attacked_me_to_flee, - /datum/ai_planning_subtree/flee_target, - /datum/ai_planning_subtree/target_retaliate, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/random_speech/pig, - ) diff --git a/code/modules/mob/living/basic/farm_animals/pony.bt.json b/code/modules/mob/living/basic/farm_animals/pony.bt.json new file mode 100644 index 00000000000..88a87c3274e --- /dev/null +++ b/code/modules/mob/living/basic/farm_animals/pony.bt.json @@ -0,0 +1,43 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/pony", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "1 SECONDS", + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_TAMED", + "observer_abort": "BT_ABORT_LOWER_PRIORITY" + }, + "child": { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_skittish_combat" + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/skittish_brawler_combat" + } + ] + } + ] + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop" + } + ] +} diff --git a/code/modules/mob/living/basic/farm_animals/pony.dm b/code/modules/mob/living/basic/farm_animals/pony.dm index 3e9ad63ad39..ad34e95fabb 100644 --- a/code/modules/mob/living/basic/farm_animals/pony.dm +++ b/code/modules/mob/living/basic/farm_animals/pony.dm @@ -65,12 +65,13 @@ AddElement(/datum/element/ridable, /datum/component/riding/creature/pony) visible_message(span_notice("[src] snorts happily.")) new /obj/effect/temp_visual/heart(loc) - - ai_controller.replace_planning_subtrees(list( - /datum/ai_planning_subtree/find_nearest_thing_which_attacked_me_to_flee, - /datum/ai_planning_subtree/flee_target, - /datum/ai_planning_subtree/random_speech/pony/tamed, - )) + var/static/list/tamed_emotes = list( + BB_EMOTE_HEAR = list("snorts."), + BB_EMOTE_SEE = list("snorts."), + BB_EMOTE_SOUND = list('sound/mobs/non-humanoids/pony/snort.ogg'), + BB_SPEAK_CHANCE = 3, + ) + ai_controller.override_blackboard_key(BB_BASIC_MOB_SPEAK_LINES, tamed_emotes) if(unique_tamer) my_owner = WEAKREF(tamer) @@ -124,21 +125,19 @@ . |= SHOVE_CAN_STAGGER /datum/ai_controller/basic_controller/pony + behavior_tree_json = "code/modules/mob/living/basic/farm_animals/pony.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_BASIC_MOB_SPEAK_LINES = list( + BB_EMOTE_HEAR = list("whinnies!"), + BB_EMOTE_SEE = list("horses around."), + BB_EMOTE_SOUND = list('sound/mobs/non-humanoids/pony/whinny01.ogg', 'sound/mobs/non-humanoids/pony/whinny02.ogg', 'sound/mobs/non-humanoids/pony/whinny03.ogg'), + BB_SPEAK_CHANCE = 3, + ), ) ai_traits = PASSIVE_AI_FLAGS ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - - planning_subtrees = list( - /datum/ai_planning_subtree/find_nearest_thing_which_attacked_me_to_flee, - /datum/ai_planning_subtree/flee_target, - /datum/ai_planning_subtree/target_retaliate, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/random_speech/pony, - ) // A stronger horse is required for our strongest cowboys. /mob/living/basic/pony/syndicate diff --git a/code/modules/mob/living/basic/farm_animals/rabbit.bt.json b/code/modules/mob/living/basic/farm_animals/rabbit.bt.json new file mode 100644 index 00000000000..5414e42ca1b --- /dev/null +++ b/code/modules/mob/living/basic/farm_animals/rabbit.bt.json @@ -0,0 +1,5 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/rabbit", + "type": "subtree", + "subtype": "/datum/bt_node/subtree/skittish_and_speak" +} diff --git a/code/modules/mob/living/basic/farm_animals/rabbit.dm b/code/modules/mob/living/basic/farm_animals/rabbit.dm index 42c3f083222..c9433bbe923 100644 --- a/code/modules/mob/living/basic/farm_animals/rabbit.dm +++ b/code/modules/mob/living/basic/farm_animals/rabbit.dm @@ -59,17 +59,18 @@ name = "bunny" /datum/ai_controller/basic_controller/rabbit + behavior_tree_json = "code/modules/mob/living/basic/farm_animals/rabbit.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_BASIC_MOB_SPEAK_LINES = list( + BB_EMOTE_SAY = list("Mrrp.", "CHIRP!", "Mrrp?"), + BB_EMOTE_HEAR = list("hops."), + BB_EMOTE_SEE = list("hops around.", "bounces up and down."), + BB_SPEAK_CHANCE = 10, + ), ) ai_traits = PASSIVE_AI_FLAGS ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/random_speech/rabbit, - /datum/ai_planning_subtree/find_nearest_thing_which_attacked_me_to_flee, - /datum/ai_planning_subtree/flee_target, - ) /// The easter subtype of rabbits, will lay eggs and say Eastery catchphrases. @@ -100,11 +101,15 @@ ) /datum/ai_controller/basic_controller/rabbit/easter - planning_subtrees = list( - /datum/ai_planning_subtree/random_speech/rabbit/easter, - /datum/ai_planning_subtree/find_nearest_thing_which_attacked_me_to_flee, - /datum/ai_planning_subtree/flee_target, - ) + blackboard = list( + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_BASIC_MOB_SPEAK_LINES = list( + BB_EMOTE_SAY = list("Hop into Easter!", "Come get your eggs!", "Prizes for everyone!"), + BB_EMOTE_HEAR = list("hops."), + BB_EMOTE_SEE = list("hops around.", "bounces up and down."), + BB_SPEAK_CHANCE = 10, + ), + ) /// Same deal as the standard easter subtype, but these ones are able to brave the cold of space with their handy gas mask. @@ -120,8 +125,12 @@ unsuitable_cold_damage = 0 // Zero because we are meant to survive in space. /datum/ai_controller/basic_controller/rabbit/easter/space - planning_subtrees = list( - /datum/ai_planning_subtree/random_speech/rabbit/easter/space, - /datum/ai_planning_subtree/find_nearest_thing_which_attacked_me_to_flee, - /datum/ai_planning_subtree/flee_target, - ) + blackboard = list( + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_BASIC_MOB_SPEAK_LINES = list( + BB_EMOTE_SAY = list("Hmph mmph mmmph!", "Mmphe mmphe mmphe!", "Hmm mmm mmm!"), + BB_EMOTE_HEAR = list("hops."), + BB_EMOTE_SEE = list("hops around.", "bounces up and down."), + BB_SPEAK_CHANCE = 10, + ), + ) diff --git a/code/modules/mob/living/basic/farm_animals/sheep.bt.json b/code/modules/mob/living/basic/farm_animals/sheep.bt.json new file mode 100644 index 00000000000..d1d6c6a9973 --- /dev/null +++ b/code/modules/mob/living/basic/farm_animals/sheep.bt.json @@ -0,0 +1,5 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/sheep", + "type": "subtree", + "subtype": "/datum/bt_node/subtree/skittish_and_speak" +} diff --git a/code/modules/mob/living/basic/farm_animals/sheep.dm b/code/modules/mob/living/basic/farm_animals/sheep.dm index bbbf7e58875..3b02f151706 100644 --- a/code/modules/mob/living/basic/farm_animals/sheep.dm +++ b/code/modules/mob/living/basic/farm_animals/sheep.dm @@ -80,14 +80,16 @@ update_appearance(UPDATE_ICON) /datum/ai_controller/basic_controller/sheep + behavior_tree_json = "code/modules/mob/living/basic/farm_animals/sheep.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_BASIC_MOB_SPEAK_LINES = list( + BB_EMOTE_SAY = list("baaa", "baaaAAAAAH!", "baaah"), + BB_EMOTE_HEAR = list("bleats."), + BB_EMOTE_SEE = list("shakes her head.", "stares into the distance."), + BB_EMOTE_SOUND = list('sound/mobs/non-humanoids/sheep/sheep1.ogg', 'sound/mobs/non-humanoids/sheep/sheep2.ogg', 'sound/mobs/non-humanoids/sheep/sheep3.ogg'), + BB_SPEAK_CHANCE = 5, + ), ) ai_traits = PASSIVE_AI_FLAGS ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/random_speech/sheep, - /datum/ai_planning_subtree/find_nearest_thing_which_attacked_me_to_flee, - /datum/ai_planning_subtree/flee_target, - ) diff --git a/code/modules/mob/living/basic/festivus_pole.bt.json b/code/modules/mob/living/basic/festivus_pole.bt.json new file mode 100644 index 00000000000..c3d84d6af3f --- /dev/null +++ b/code/modules/mob/living/basic/festivus_pole.bt.json @@ -0,0 +1,122 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/festivus_pole", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_LOW_PRIORITY_HUNTING_TARGET" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_LOW_PRIORITY_HUNTING_TARGET", + "required_dist": 1 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/hunt_target/use_ability_on_target", + "vars": { + "target_key": "BB_LOW_PRIORITY_HUNTING_TARGET", + "ability_key": "BB_FESTIVE_APC" + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk", + "bindings": { + "bf07i8ep": "10" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_LOW_PRIORITY_HUNTING_TARGET", + "target_source": "/datum/target_source/oview_single_type/apc", + "targeting_strategy": "/datum/targeting_strategy/chargeable_apc", + "vision_range": 6 + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/festivus_pole.dm b/code/modules/mob/living/basic/festivus_pole.dm index 19a3cd2bf58..593f0e34fb7 100644 --- a/code/modules/mob/living/basic/festivus_pole.dm +++ b/code/modules/mob/living/basic/festivus_pole.dm @@ -51,19 +51,13 @@ grant_actions_by_list(list(/datum/action/cooldown/mob_cooldown/charge_apc = BB_FESTIVE_APC)) /datum/ai_controller/basic_controller/festivus_pole + behavior_tree_json = "code/modules/mob/living/basic/festivus_pole.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_LOW_PRIORITY_HUNTING_TARGET = null, // APCs ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk/less_walking - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/find_and_hunt_target/look_for_apcs, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) /mob/living/basic/festivus/attack_hand(mob/living/carbon/human/user, list/modifiers) . = ..() @@ -84,41 +78,4 @@ if(apc_target.cell) apc_target.cell.give(FESTIVUS_RECHARGE_VALUE) -/datum/ai_planning_subtree/find_and_hunt_target/look_for_apcs - hunting_behavior = /datum/ai_behavior/hunt_target/apcs - hunt_targets = list(/obj/machinery/power/apc) - hunt_range = 6 - - -/datum/ai_planning_subtree/find_and_hunt_target/look_for_apcs - target_key = BB_LOW_PRIORITY_HUNTING_TARGET - finding_behavior = /datum/ai_behavior/find_hunt_target/apcs - hunting_behavior = /datum/ai_behavior/hunt_target/apcs - hunt_targets = list(/obj/machinery/power/apc) - hunt_range = 6 - -/datum/ai_behavior/hunt_target/apcs - hunt_cooldown = 15 SECONDS - always_reset_target = TRUE - -/datum/ai_behavior/hunt_target/apcs/target_caught(mob/living/basic/hunter, obj/machinery/power/apc/hunted) - var/datum/action/cooldown/mob_cooldown/charge_ability = hunter.ai_controller.blackboard[BB_FESTIVE_APC] - if(isnull(charge_ability)) - return - charge_ability.Activate(hunted) - - -/datum/ai_behavior/find_hunt_target/apcs - -/datum/ai_behavior/find_hunt_target/apcs/valid_dinner(mob/living/source, obj/machinery/power/apc/dinner, radius) - if(istype(dinner, /obj/machinery/power/apc)) - var/obj/machinery/power/apc/apc_target = dinner - if(!apc_target.cell) - return FALSE - var/obj/item/stock_parts/power_store/cell/apc_cell = apc_target.cell - if(apc_cell.charge == apc_cell.maxcharge) //if its full charge we no longer feed it - return FALSE - - return can_see(source, dinner, radius) - #undef FESTIVUS_RECHARGE_VALUE diff --git a/code/modules/mob/living/basic/heretic/flesh_stalker.dm b/code/modules/mob/living/basic/heretic/flesh_stalker.dm index 5cee158463a..c7c9eb77c54 100644 --- a/code/modules/mob/living/basic/heretic/flesh_stalker.dm +++ b/code/modules/mob/living/basic/heretic/flesh_stalker.dm @@ -25,18 +25,10 @@ /// Changes shape and lies in wait when it has no target, uses EMP and attacks once it does /datum/ai_controller/basic_controller/stalker + behavior_tree_json = "code/modules/mob/living/basic/heretic/stalker.bt.json" ai_traits = CAN_ACT_IN_STASIS blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/shapechange_ambush, - /datum/ai_planning_subtree/use_mob_ability, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) diff --git a/code/modules/mob/living/basic/heretic/raw_prophet.bt.json b/code/modules/mob/living/basic/heretic/raw_prophet.bt.json new file mode 100644 index 00000000000..66da01c6cf6 --- /dev/null +++ b/code/modules/mob/living/basic/heretic/raw_prophet.bt.json @@ -0,0 +1,14 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/raw_prophet", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_ability_ranged_combat" + } + ] +} diff --git a/code/modules/mob/living/basic/heretic/raw_prophet.dm b/code/modules/mob/living/basic/heretic/raw_prophet.dm index 8c5da925c06..9773fdc5d6c 100644 --- a/code/modules/mob/living/basic/heretic/raw_prophet.dm +++ b/code/modules/mob/living/basic/heretic/raw_prophet.dm @@ -85,16 +85,9 @@ /// Walk and attack people, blind them when we can /datum/ai_controller/basic_controller/raw_prophet + behavior_tree_json = "code/modules/mob/living/basic/heretic/raw_prophet.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/targeted_mob_ability, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) diff --git a/code/modules/mob/living/basic/heretic/rust_walker.bt.json b/code/modules/mob/living/basic/heretic/rust_walker.bt.json new file mode 100644 index 00000000000..4f69b8a51d9 --- /dev/null +++ b/code/modules/mob/living/basic/heretic/rust_walker.bt.json @@ -0,0 +1,126 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/rust_walker", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/ability_available", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/pawn_turf_has_trait", + "vars": { + "observer_abort": "BT_ABORT_NONE", + "trait": "TRAIT_RUSTY" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/use_mob_ability" + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/attack_obstructions", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability", + "vars": { + "ability_key": "BB_TARGETED_ACTION", + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_FAIL_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/idle_random_walk/rust" + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/heretic/rust_walker.dm b/code/modules/mob/living/basic/heretic/rust_walker.dm index a2cdfc43250..24e826cfdc5 100644 --- a/code/modules/mob/living/basic/heretic/rust_walker.dm +++ b/code/modules/mob/living/basic/heretic/rust_walker.dm @@ -54,39 +54,9 @@ /// Converts unconverted terrain, sprays pocket sand around /datum/ai_controller/basic_controller/rust_walker + behavior_tree_json = "code/modules/mob/living/basic/heretic/rust_walker.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk/rust - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/use_mob_ability/rust_walker, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/targeted_mob_ability, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) - -/// Moves a lot if healthy and on rust (to find more tiles to rust) or unhealthy and not on rust (to find healing rust) -/// Still moving in random directions though we're not really seeking it out -/datum/idle_behavior/idle_random_walk/rust - -/datum/idle_behavior/idle_random_walk/rust/perform_idle_behavior(seconds_per_tick, datum/ai_controller/controller) - var/mob/living/our_mob = controller.pawn - var/turf/our_turf = get_turf(our_mob) - if (HAS_TRAIT(our_turf, TRAIT_RUSTY)) - walk_chance = (our_mob.health < our_mob.maxHealth) ? 10 : 50 - else - walk_chance = (our_mob.health < our_mob.maxHealth) ? 50 : 10 - return ..() - -/// Use if we're not stood on rust right now -/datum/ai_planning_subtree/use_mob_ability/rust_walker - -/datum/ai_planning_subtree/use_mob_ability/rust_walker/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/turf/our_turf = get_turf(controller.pawn) - if (HAS_TRAIT(our_turf, TRAIT_RUSTY)) - return - return ..() diff --git a/code/modules/mob/living/basic/heretic/stalker.bt.json b/code/modules/mob/living/basic/heretic/stalker.bt.json new file mode 100644 index 00000000000..aabce450422 --- /dev/null +++ b/code/modules/mob/living/basic/heretic/stalker.bt.json @@ -0,0 +1,147 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/stalker", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/pawn_inside_mob", + "vars": { + "invert": true + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "invert": true, + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/use_mob_ability/shapeshift" + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/pawn_inside_mob", + "vars": { + "observer_abort": "BT_ABORT_SELF" + }, + "child": { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "invert": false, + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_at_least", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "key": "BB_BASIC_MOB_HAS_TARGET_TIME", + "minimum": "8 SECONDS" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/use_mob_ability/shapeshift" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/wait" + } + ] + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/wait" + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/heretic/star_gazer.bt.json b/code/modules/mob/living/basic/heretic/star_gazer.bt.json new file mode 100644 index 00000000000..80167946253 --- /dev/null +++ b/code/modules/mob/living/basic/heretic/star_gazer.bt.json @@ -0,0 +1,19 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/star_gazer", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree", + "override_id": "SUBPLAN_ID_PET_COMMAND" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_hostile_obstacles_combat" + } + ] +} diff --git a/code/modules/mob/living/basic/heretic/star_gazer.dm b/code/modules/mob/living/basic/heretic/star_gazer.dm index 6ea05af3665..e230ec1ba5a 100644 --- a/code/modules/mob/living/basic/heretic/star_gazer.dm +++ b/code/modules/mob/living/basic/heretic/star_gazer.dm @@ -131,7 +131,7 @@ target.apply_damage(damage = 5, damagetype = BURN) var/datum/targeting_strategy/target_confirmer = GET_TARGETING_STRATEGY(ai_controller.blackboard[BB_TARGETING_STRATEGY]) for(var/mob/living/nearby_mob in range(1, src)) - if(target == nearby_mob || !target_confirmer?.can_attack(src, nearby_mob)) + if(target == nearby_mob || !target_confirmer?.is_valid_target(src, nearby_mob)) continue nearby_mob.apply_status_effect(/datum/status_effect/star_mark) nearby_mob.apply_damage(10) @@ -402,30 +402,10 @@ ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/attack_obstacle_in_path/pet_target/star_gazer, - /datum/ai_planning_subtree/pet_planning, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/attack_obstacle_in_path/star_gazer, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) - -/datum/ai_planning_subtree/attack_obstacle_in_path/star_gazer - attack_behaviour = /datum/ai_behavior/attack_obstructions/star_gazer - -/datum/ai_planning_subtree/attack_obstacle_in_path/pet_target/star_gazer - attack_behaviour = /datum/ai_behavior/attack_obstructions/star_gazer - -/datum/ai_behavior/attack_obstructions/star_gazer - action_cooldown = 0.4 SECONDS - can_attack_turfs = TRUE - can_attack_dense_objects = TRUE + behavior_tree_json = "code/modules/mob/living/basic/heretic/star_gazer.bt.json" /datum/pet_command/attack/star_gazer speech_commands = list("attack", "sic", "kill", "slash them") command_feedback = "stares!" pointed_reaction = "stares intensely!" refuse_reaction = "..." - attack_behaviour = /datum/ai_behavior/basic_melee_attack diff --git a/code/modules/mob/living/basic/icemoon/ice_demon/ice_demon.bt.json b/code/modules/mob/living/basic/icemoon/ice_demon/ice_demon.bt.json new file mode 100644 index 00000000000..fb3ed54b6eb --- /dev/null +++ b/code/modules/mob/living/basic/icemoon/ice_demon/ice_demon.bt.json @@ -0,0 +1,54 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/ice_demon", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/ice_demon_flee_from_fire" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/ice_demon_combat" + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/icemoon/ice_demon/ice_demon_afterimage.bt.json b/code/modules/mob/living/basic/icemoon/ice_demon/ice_demon_afterimage.bt.json new file mode 100644 index 00000000000..7ab3b485eb5 --- /dev/null +++ b/code/modules/mob/living/basic/icemoon/ice_demon/ice_demon_afterimage.bt.json @@ -0,0 +1,18 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/ice_demon/afterimage", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/ice_demon_flee_from_fire" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_hostile_combat" + } + ] +} diff --git a/code/modules/mob/living/basic/icemoon/ice_demon/ice_demon_ai.dm b/code/modules/mob/living/basic/icemoon/ice_demon/ice_demon_ai.dm index 642061ed782..155896915cc 100644 --- a/code/modules/mob/living/basic/icemoon/ice_demon/ice_demon_ai.dm +++ b/code/modules/mob/living/basic/icemoon/ice_demon/ice_demon_ai.dm @@ -1,4 +1,5 @@ /datum/ai_controller/basic_controller/ice_demon + behavior_tree_json = "code/modules/mob/living/basic/icemoon/ice_demon/ice_demon.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_RANGED_SKIRMISH_MAX_DISTANCE = 7, @@ -9,104 +10,14 @@ ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/flee_target/ice_demon, - /datum/ai_planning_subtree/ranged_skirmish/ice_demon, - /datum/ai_planning_subtree/maintain_distance/cover_minimum_distance, - /datum/ai_planning_subtree/teleport_away_from_target, - /datum/ai_planning_subtree/find_and_hunt_target/teleport_destination, - /datum/ai_planning_subtree/targeted_mob_ability/summon_afterimages, - ) - -/datum/ai_planning_subtree/teleport_away_from_target - ability_key = BB_DEMON_TELEPORT_ABILITY - -/datum/ai_planning_subtree/find_and_hunt_target/teleport_destination - target_key = BB_TELEPORT_DESTINATION - hunting_behavior = /datum/ai_behavior/hunt_target/use_ability_on_target/demon_teleport - finding_behavior = /datum/ai_behavior/find_valid_teleport_location - hunt_targets = list(/turf/open) - hunt_range = 3 - finish_planning = FALSE - -/datum/ai_planning_subtree/find_and_hunt_target/teleport_destination/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(!controller.blackboard_key_exists(BB_BASIC_MOB_CURRENT_TARGET)) - return - if(controller.blackboard_key_exists(BB_ESCAPE_DESTINATION)) - controller.clear_blackboard_key(BB_TELEPORT_DESTINATION) - return - var/datum/action/cooldown/ability = controller.blackboard[BB_DEMON_TELEPORT_ABILITY] - if(!ability?.IsAvailable()) - return - return ..() - -/datum/ai_behavior/find_valid_teleport_location - -/datum/ai_behavior/find_valid_teleport_location/perform(seconds_per_tick, datum/ai_controller/controller, hunting_target_key, types_to_hunt, hunt_range) - var/mob/living/target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] - var/list/possible_turfs = list() - - if(QDELETED(target)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - for(var/turf/open/potential_turf in oview(hunt_range, target)) //we check for turfs around the target - if(potential_turf.is_blocked_turf()) - continue - if(!can_see(target, potential_turf, hunt_range)) - continue - possible_turfs += potential_turf - - if(!length(possible_turfs)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - controller.set_blackboard_key(hunting_target_key, pick(possible_turfs)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -/datum/ai_behavior/hunt_target/use_ability_on_target/demon_teleport - hunt_cooldown = 2 SECONDS - ability_key = BB_DEMON_TELEPORT_ABILITY - behavior_flags = NONE - -/datum/ai_planning_subtree/targeted_mob_ability/summon_afterimages - ability_key = BB_DEMON_CLONE_ABILITY - -/datum/ai_planning_subtree/targeted_mob_ability/summon_afterimages/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/mob/living/living_pawn = controller.pawn - if(living_pawn.health / living_pawn.maxHealth > 0.5) //only use this ability when under half health - return - return ..() - -/datum/ai_planning_subtree/flee_target/ice_demon - -/datum/ai_planning_subtree/flee_target/ice_demon/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/atom/target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] - if(QDELETED(target)) - return - if(!iscarbon(target)) - return - var/mob/living/carbon/human_target = target - - for(var/obj/held_item in human_target.held_items) - if(!is_type_in_list(held_item, controller.blackboard[BB_LIST_SCARY_ITEMS])) - continue - if(!held_item.light_on) - continue - var/datum/action/cooldown/slip_ability = controller.blackboard[BB_DEMON_SLIP_ABILITY] - if(slip_ability?.IsAvailable()) - controller.queue_behavior(/datum/ai_behavior/use_mob_ability, BB_DEMON_SLIP_ABILITY) - return ..() - -/datum/ai_planning_subtree/ranged_skirmish/ice_demon - min_range = 0 /datum/ai_controller/basic_controller/ice_demon/afterimage - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/flee_target/ice_demon, //even the afterimages are afraid of flames! - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) + behavior_tree_json = "code/modules/mob/living/basic/icemoon/ice_demon/ice_demon_afterimage.bt.json" +/// Flees from a target holding a lit scary item, slipping them on the way out. +/datum/bt_node/subtree/ice_demon_flee_from_fire + behavior_tree_json = "code/modules/mob/living/basic/icemoon/ice_demon/ice_demon_flee_from_fire.bt.json" + + +/datum/bt_node/subtree/ice_demon_combat + behavior_tree_json = "code/modules/mob/living/basic/icemoon/ice_demon/ice_demon_combat.bt.json" diff --git a/code/modules/mob/living/basic/icemoon/ice_demon/ice_demon_combat.bt.json b/code/modules/mob/living/basic/icemoon/ice_demon/ice_demon_combat.bt.json new file mode 100644 index 00000000000..9a5556b1ece --- /dev/null +++ b/code/modules/mob/living/basic/icemoon/ice_demon/ice_demon_combat.bt.json @@ -0,0 +1,127 @@ +{ + "dm_type": "/datum/bt_node/subtree/ice_demon_combat", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/is_at_distance", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "maximum_distance": 1 + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/ability_available", + "vars": { + "ability_key": "BB_DEMON_TELEPORT_ABILITY" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/find_furthest_turf_from_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "set_key": "BB_ESCAPE_DESTINATION", + "range": 7 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability/and_clear_target", + "vars": { + "ability_key": "BB_DEMON_TELEPORT_ABILITY", + "target_key": "BB_ESCAPE_DESTINATION" + } + } + ] + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/pawn_health_below", + "vars": { + "health_threshold": 75 + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/ability_available", + "vars": { + "ability_key": "BB_DEMON_CLONE_ABILITY" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability", + "vars": { + "ability_key": "BB_DEMON_CLONE_ABILITY", + "target_key": "BB_CURRENT_TARGET" + } + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/ability_available", + "vars": { + "ability_key": "BB_DEMON_TELEPORT_ABILITY" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/find_valid_teleport_location", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "set_key": "BB_TELEPORT_DESTINATION", + "range": 3 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability/and_clear_target", + "vars": { + "ability_key": "BB_DEMON_TELEPORT_ABILITY", + "target_key": "BB_TELEPORT_DESTINATION" + } + } + ] + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_ranged_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION", + "max_range": 9 + } + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/maintain_distance/cover_minimum_distance", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + } + ] +} diff --git a/code/modules/mob/living/basic/icemoon/ice_demon/ice_demon_flee_from_fire.bt.json b/code/modules/mob/living/basic/icemoon/ice_demon/ice_demon_flee_from_fire.bt.json new file mode 100644 index 00000000000..ee77b66b8cb --- /dev/null +++ b/code/modules/mob/living/basic/icemoon/ice_demon/ice_demon_flee_from_fire.bt.json @@ -0,0 +1,37 @@ +{ + "dm_type": "/datum/bt_node/subtree/ice_demon_flee_from_fire", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/target_holding_lit_item", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET", + "item_types_key": "BB_LIST_SCARY_ITEMS" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/run_away_from_target" + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/ability_available", + "vars": { + "ability_key": "BB_DEMON_SLIP_ABILITY" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/use_mob_ability", + "vars": { + "ability_key": "BB_DEMON_SLIP_ABILITY" + } + } + } + ] + } +} diff --git a/code/modules/mob/living/basic/icemoon/ice_whelp/ice_whelp.bt.json b/code/modules/mob/living/basic/icemoon/ice_whelp/ice_whelp.bt.json new file mode 100644 index 00000000000..d8de7a1f0a6 --- /dev/null +++ b/code/modules/mob/living/basic/icemoon/ice_whelp/ice_whelp.bt.json @@ -0,0 +1,284 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/ice_whelp", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/attack_obstructions", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability", + "vars": { + "ability_key": "BB_WHELP_WIDESPREAD_FIRE", + "target_key": "BB_CURRENT_TARGET", + "maximum_distance": 3 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability", + "vars": { + "ability_key": "BB_WHELP_STRAIGHTLINE_FIRE", + "target_key": "BB_CURRENT_TARGET", + "maximum_distance": 7 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "key": "BB_TARGET_CANNIBAL" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_TARGET_CANNIBAL", + "required_dist": 1 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/ai_interact", + "vars": { + "target_key": "BB_TARGET_CANNIBAL" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/clear_key", + "vars": { + "key": "BB_TARGET_CANNIBAL" + } + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "key": "BB_TARGET_ROCK" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_TARGET_ROCK", + "required_dist": 0 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/ai_interact", + "vars": { + "target_key": "BB_TARGET_ROCK", + "combat_mode": false + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/set_bb_cooldown", + "vars": { + "cooldown_key": "BB_WHELP_SCULPT_COOLDOWN", + "cooldown_duration": "5 MINUTES" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/clear_key", + "vars": { + "key": "BB_TARGET_ROCK" + } + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "key": "BB_TARGET_TREE" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_TARGET_TREE", + "required_dist": 2 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability/and_clear_target", + "vars": { + "ability_key": "BB_WHELP_STRAIGHTLINE_FIRE", + "target_key": "BB_TARGET_TREE", + "maximum_distance": 2 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/set_bb_cooldown", + "vars": { + "cooldown_key": "BB_WHELP_BURN_COOLDOWN", + "cooldown_duration": "2 MINUTES" + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_TARGET_CANNIBAL", + "target_source": "/datum/target_source/oview_single_type/ice_whelp", + "targeting_strategy": "/datum/targeting_strategy/dead_mob/not_pulled", + "vision_range": 10 + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/key_off_cooldown", + "vars": { + "cooldown_key": "BB_WHELP_SCULPT_COOLDOWN" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_TARGET_ROCK", + "target_source": "/datum/target_source/oview_single_type/icy_rock", + "targeting_strategy": "/datum/targeting_strategy/anything" + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/key_off_cooldown", + "vars": { + "cooldown_key": "d" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/ability_available", + "vars": { + "ability_key": "BB_WHELP_STRAIGHTLINE_FIRE" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_TARGET_TREE", + "target_source": "/datum/target_source/oview_single_type/flora_tree", + "targeting_strategy": "/datum/targeting_strategy/non_stump_tree", + "vision_range": 9 + } + } + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/icemoon/ice_whelp/ice_whelp.dm b/code/modules/mob/living/basic/icemoon/ice_whelp/ice_whelp.dm index 7d4c74747a3..66e99d25bd2 100644 --- a/code/modules/mob/living/basic/icemoon/ice_whelp/ice_whelp.dm +++ b/code/modules/mob/living/basic/icemoon/ice_whelp/ice_whelp.dm @@ -47,7 +47,6 @@ AddElement(/datum/element/footstep, FOOTSTEP_MOB_HEAVY) grant_actions_by_list(innate_actions) - ai_controller.set_blackboard_key(BB_TARGETED_ACTION, ai_controller.blackboard[BB_WHELP_STRAIGHTLINE_FIRE]) /mob/living/basic/mining/ice_whelp/early_melee_attack(atom/target, list/modifiers, ignore_cooldown) . = ..() diff --git a/code/modules/mob/living/basic/icemoon/ice_whelp/ice_whelp_ai.dm b/code/modules/mob/living/basic/icemoon/ice_whelp/ice_whelp_ai.dm index 34a4b363136..c84ad2f6222 100644 --- a/code/modules/mob/living/basic/icemoon/ice_whelp/ice_whelp_ai.dm +++ b/code/modules/mob/living/basic/icemoon/ice_whelp/ice_whelp_ai.dm @@ -5,154 +5,4 @@ ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/targeted_mob_ability/ice_whelp, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/basic_melee_attack_subtree/ice_whelp, - /datum/ai_planning_subtree/sculpt_statues, - /datum/ai_planning_subtree/find_and_hunt_target/corpses/ice_whelp, - /datum/ai_planning_subtree/burn_trees, - ) - -/// Cancel melee attacks when we have our breath weapon -/datum/ai_planning_subtree/basic_melee_attack_subtree/ice_whelp - melee_attack_behavior = /datum/ai_behavior/basic_melee_attack/ice_whelp - -/// Cancel melee attacks when we have our breath weapon -/datum/ai_behavior/basic_melee_attack/ice_whelp - -/datum/ai_behavior/basic_melee_attack/ice_whelp/perform(seconds_per_tick, datum/ai_controller/controller, target_key, targeting_strategy_key, hiding_location_key) - var/datum/action/cooldown/breath_weapon = controller.blackboard[BB_TARGETED_ACTION] - if (breath_weapon?.IsAvailable()) - return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED - - return ..() - -/// Find other tasty dragons -/datum/ai_planning_subtree/find_and_hunt_target/corpses/ice_whelp - target_key = BB_TARGET_CANNIBAL - finding_behavior = /datum/ai_behavior/find_hunt_target/corpses/dragon_corpse - hunting_behavior = /datum/ai_behavior/hunt_target/interact_with_target/dragon_cannibalise - hunt_targets = list(/mob/living/basic/mining/ice_whelp) - hunt_range = 10 - -/datum/ai_behavior/find_hunt_target/corpses/dragon_corpse - -/datum/ai_behavior/find_hunt_target/corpses/dragon_corpse/valid_dinner(mob/living/source, mob/living/dinner, radius) - if(dinner.pulledby) //someone already got him before us - return FALSE - return ..() - -/// Eat other dragons -/datum/ai_behavior/hunt_target/interact_with_target/dragon_cannibalise - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION - -/datum/ai_behavior/hunt_target/interact_with_target/dragon_cannibalise/perform(seconds_per_tick, datum/ai_controller/controller, target_key, attack_key) - var/mob/living/target = controller.blackboard[target_key] - if(QDELETED(target) || target.stat != DEAD || target.pulledby) //we were too slow - return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED - return ..() - -/datum/ai_behavior/cannibalize/finish_action(datum/ai_controller/controller, succeeded, target_key) - . = ..() - controller.clear_blackboard_key(target_key) - -///subtree to find icy rocks and create sculptures out of them -/datum/ai_planning_subtree/sculpt_statues - -/datum/ai_planning_subtree/sculpt_statues/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(controller.blackboard_key_exists(BB_TARGET_ROCK)) - controller.queue_behavior(/datum/ai_behavior/sculpt_statue, BB_TARGET_ROCK) - return SUBTREE_RETURN_FINISH_PLANNING - controller.queue_behavior(/datum/ai_behavior/find_and_set, BB_TARGET_ROCK, /obj/structure/flora/rock/icy) - -/datum/ai_behavior/sculpt_statue - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION - action_cooldown = 5 MINUTES - -/datum/ai_behavior/sculpt_statue/setup(datum/ai_controller/controller, target_key) - . = ..() - var/obj/target = controller.blackboard[target_key] - if(QDELETED(target)) - return FALSE - set_movement_target(controller, target) - -/datum/ai_behavior/sculpt_statue/perform(seconds_per_tick, datum/ai_controller/controller, target_key) - if(!controller.ai_interact(target = target_key, combat_mode = FALSE)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -/datum/ai_behavior/sculpt_statue/finish_action(datum/ai_controller/controller, succeeded, target_key) - . = ..() - controller.clear_blackboard_key(target_key) - -/// Only use ability if we are within range -/datum/ai_planning_subtree/targeted_mob_ability/ice_whelp - use_ability_behaviour = /datum/ai_behavior/targeted_mob_ability/ice_whelp - -/datum/ai_planning_subtree/targeted_mob_ability/ice_whelp/additional_ability_checks(datum/ai_controller/controller, datum/action/cooldown/using_action) - var/atom/target = controller.blackboard[target_key] - var/range_to_target = get_dist(controller.pawn, target) - return range_to_target < /datum/action/cooldown/mob_cooldown/fire_breath/ice::fire_range - -/// Select appropriate ability based on range -/datum/ai_behavior/targeted_mob_ability/ice_whelp - -/datum/ai_behavior/targeted_mob_ability/ice_whelp/perform(seconds_per_tick, datum/ai_controller/controller, ability_key, target_key) - var/mob/living/target = controller.blackboard[target_key] - if (isnull(target)) - return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED - - var/dist_to_target = get_dist(controller.pawn, target) - var/datum/action/cooldown/mob_cooldown/fire_breath/ice/short_range_ability = controller.blackboard[BB_WHELP_WIDESPREAD_FIRE] - if (isnull(short_range_ability) || dist_to_target > short_range_ability.fire_range) - controller.set_blackboard_key(BB_TARGETED_ACTION, controller.blackboard[BB_WHELP_STRAIGHTLINE_FIRE]) - return ..() - - controller.set_blackboard_key(BB_TARGETED_ACTION, controller.blackboard[BB_WHELP_WIDESPREAD_FIRE]) - return ..() - -///subtree to look for trees and burn them with our flamethrower -/datum/ai_planning_subtree/burn_trees - -/datum/ai_planning_subtree/burn_trees/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/datum/action/cooldown/using_action = controller.blackboard[BB_WHELP_STRAIGHTLINE_FIRE] - if (!using_action?.IsAvailable()) - return - - if(controller.blackboard_key_exists(BB_TARGET_TREE)) - controller.queue_behavior(/datum/ai_behavior/targeted_mob_ability/and_clear_target/burn_trees, BB_WHELP_STRAIGHTLINE_FIRE, BB_TARGET_TREE) - return SUBTREE_RETURN_FINISH_PLANNING - controller.queue_behavior(/datum/ai_behavior/set_target_tree, BB_TARGET_TREE) - -/datum/ai_behavior/set_target_tree - -/datum/ai_behavior/set_target_tree/perform(seconds_per_tick, datum/ai_controller/controller, tree_key) - var/mob/living_pawn = controller.pawn - var/list/possible_trees = list() - - for(var/obj/structure/flora/tree/possible_tree in oview(9, living_pawn)) - if(istype(possible_tree, /obj/structure/flora/tree/stump)) //no leaves to burn - continue - possible_trees += possible_tree - - if(!length(possible_trees)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - controller.set_blackboard_key(tree_key, pick(possible_trees)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -/datum/ai_behavior/targeted_mob_ability/and_clear_target/burn_trees - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION - required_distance = 2 - action_cooldown = 2 MINUTES - -/datum/ai_behavior/targeted_mob_ability/and_clear_target/burn_trees/setup(datum/ai_controller/controller, ability_key, target_key) - . = ..() - var/obj/target = controller.blackboard[target_key] - if(QDELETED(target)) - return FALSE - set_movement_target(controller, target) + behavior_tree_json = "code/modules/mob/living/basic/icemoon/ice_whelp/ice_whelp.bt.json" diff --git a/code/modules/mob/living/basic/icemoon/polar_bear/polar.bt.json b/code/modules/mob/living/basic/icemoon/polar_bear/polar.bt.json new file mode 100644 index 00000000000..25907280dd2 --- /dev/null +++ b/code/modules/mob/living/basic/icemoon/polar_bear/polar.bt.json @@ -0,0 +1,40 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/polar", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_hostile_combat", + "bindings": { + "bbrbyj7y": "/datum/bt_node/ai_behavior/random_speech/bear" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/enrage" + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/icemoon/polar_bear/polar_bear.dm b/code/modules/mob/living/basic/icemoon/polar_bear/polar_bear.dm index 5d838db8fe8..a13bdc5faec 100644 --- a/code/modules/mob/living/basic/icemoon/polar_bear/polar_bear.dm +++ b/code/modules/mob/living/basic/icemoon/polar_bear/polar_bear.dm @@ -53,6 +53,7 @@ faction = list(FACTION_NEUTRAL) /datum/ai_controller/basic_controller/polar + behavior_tree_json = "code/modules/mob/living/basic/icemoon/polar_bear/polar.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_TARGET_MINIMUM_STAT = HARD_CRIT, @@ -60,12 +61,3 @@ ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/enrage, - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/target_retaliate, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/random_speech/bear, - ) diff --git a/code/modules/mob/living/basic/icemoon/wolf/wolf.bt.json b/code/modules/mob/living/basic/icemoon/wolf/wolf.bt.json new file mode 100644 index 00000000000..930b82e9049 --- /dev/null +++ b/code/modules/mob/living/basic/icemoon/wolf/wolf.bt.json @@ -0,0 +1,120 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/wolf", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree", + "override_id": "SUBPLAN_ID_PET_COMMAND" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/run_away_from_target" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/attack_obstructions", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/cooldown", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "cooldown_key": "BB_BASIC_MOB_REINFORCEMENTS_COOLDOWN", + "cooldown_duration": "30 SECONDS", + "lock_on_succeed": true + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/call_reinforcements" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/icemoon/wolf/wolf_ai.dm b/code/modules/mob/living/basic/icemoon/wolf/wolf_ai.dm index 28618bfebca..a30270070c9 100644 --- a/code/modules/mob/living/basic/icemoon/wolf/wolf_ai.dm +++ b/code/modules/mob/living/basic/icemoon/wolf/wolf_ai.dm @@ -16,27 +16,6 @@ ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk + behavior_tree_json = "code/modules/mob/living/basic/icemoon/wolf/wolf.bt.json" - //reinforcements needs to be skipped over entirely on tamed wolves because it causes them to attack their owner and then themselves - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/pet_planning, - /datum/ai_planning_subtree/call_reinforcements/wolf, - /datum/ai_planning_subtree/simple_find_nearest_target_to_flee, - /datum/ai_planning_subtree/flee_target, - /datum/ai_planning_subtree/target_retaliate, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) - -/datum/ai_planning_subtree/call_reinforcements/wolf - -/datum/ai_planning_subtree/call_reinforcements/wolf/decide_to_call(datum/ai_controller/controller) - //only call reinforcements if the person who just smacked us isn't a friend to avoid hitting them once, then killing ourselves if we've been tamed - if (controller.blackboard_key_exists(BB_BASIC_MOB_CURRENT_TARGET) && istype(controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET], /mob)) - return !(controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] in controller.blackboard[BB_FRIENDS_LIST]) - else - return FALSE diff --git a/code/modules/mob/living/basic/illusion/escape.bt.json b/code/modules/mob/living/basic/illusion/escape.bt.json new file mode 100644 index 00000000000..5fbf7dc4fb3 --- /dev/null +++ b/code/modules/mob/living/basic/illusion/escape.bt.json @@ -0,0 +1,28 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/illusion/escape", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/run_away_from_target" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/pick_retaliate_target" + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/illusion/illlusion_ai.dm b/code/modules/mob/living/basic/illusion/illlusion_ai.dm index 48270fc2583..60580df19f0 100644 --- a/code/modules/mob/living/basic/illusion/illlusion_ai.dm +++ b/code/modules/mob/living/basic/illusion/illlusion_ai.dm @@ -2,42 +2,20 @@ /// For the time being however, the AI is very simple and doesn't rely on any advanced tactics. Just go to thing it was assigned to attack and attack it (if assigned, else wander around) /// However, the action we undergo is based on the subtype of illusion we are and that's done on the mob subtype level. /datum/ai_controller/basic_controller/illusion + behavior_tree_json = "code/datums/ai/basic_mobs/simple_hostile.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, ) ai_traits = DEFAULT_AI_FLAGS ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) /// Escape subtype of illusions are made to flee from threats rather than attack them. They do not undergo any retaliation behavior. /// We also want to account for the possibility of new threats attacking us and fleeing from those too, more randomness is ideal. /datum/ai_controller/basic_controller/illusion/escape - blackboard = list( - BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, // we don't need the special illusion one here - BB_FLEE_TARGETING_STRATEGY = /datum/targeting_strategy/basic, - ) - - ai_traits = DEFAULT_AI_FLAGS - ai_movement = /datum/ai_movement/basic_avoidance - - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/target_retaliate/to_flee, - /datum/ai_planning_subtree/flee_target/from_flee_key, - ) + behavior_tree_json = "code/modules/mob/living/basic/illusion/escape.bt.json" /// Retaliate subtypes of escape illusions can fight back against threats that attack them, making them more dangerous. /datum/ai_controller/basic_controller/illusion/escape/retaliate - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/flee_target, - /datum/ai_planning_subtree/target_retaliate, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) + behavior_tree_json = "code/modules/mob/living/basic/illusion/retaliate.bt.json" diff --git a/code/modules/mob/living/basic/illusion/illusion.dm b/code/modules/mob/living/basic/illusion/illusion.dm index 97a61e653f0..3ae32ca3a48 100644 --- a/code/modules/mob/living/basic/illusion/illusion.dm +++ b/code/modules/mob/living/basic/illusion/illusion.dm @@ -26,7 +26,7 @@ /// Prob of getting a clone on attack var/multiply_chance = 0 /// The blackboard key we want to set for our target - var/target_key = BB_BASIC_MOB_CURRENT_TARGET + var/target_key = BB_CURRENT_TARGET /mob/living/basic/illusion/Initialize(mapload) . = ..() diff --git a/code/modules/mob/living/basic/illusion/retaliate.bt.json b/code/modules/mob/living/basic/illusion/retaliate.bt.json new file mode 100644 index 00000000000..a9e61267291 --- /dev/null +++ b/code/modules/mob/living/basic/illusion/retaliate.bt.json @@ -0,0 +1,28 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/illusion/escape/retaliate", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/run_away_from_target" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/pick_retaliate_target" + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/jungle/human_trap.bt.json b/code/modules/mob/living/basic/jungle/human_trap.bt.json new file mode 100644 index 00000000000..ac0c6499b8c --- /dev/null +++ b/code/modules/mob/living/basic/jungle/human_trap.bt.json @@ -0,0 +1,5 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/human_trap", + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_ability_ranged_combat" +} diff --git a/code/modules/mob/living/basic/jungle/leaper/leaper.bt.json b/code/modules/mob/living/basic/jungle/leaper/leaper.bt.json new file mode 100644 index 00000000000..2dda6824c59 --- /dev/null +++ b/code/modules/mob/living/basic/jungle/leaper/leaper.bt.json @@ -0,0 +1,122 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/leaper", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree", + "override_id": "SUBPLAN_ID_PET_COMMAND" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability", + "vars": { + "ability_key": "BB_LEAPER_BUBBLE", + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability", + "vars": { + "ability_key": "BB_LEAPER_FLOP", + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability", + "vars": { + "ability_key": "BB_LEAPER_VOLLEY", + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability", + "vars": { + "ability_key": "BB_LEAPER_SUMMON", + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/jungle/leaper/leaper_ai.dm b/code/modules/mob/living/basic/jungle/leaper/leaper_ai.dm index 1e7a6dee29e..27c517ff482 100644 --- a/code/modules/mob/living/basic/jungle/leaper/leaper_ai.dm +++ b/code/modules/mob/living/basic/jungle/leaper/leaper_ai.dm @@ -5,40 +5,7 @@ ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk/less_walking - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/pet_planning, - /datum/ai_planning_subtree/targeted_mob_ability/pointed_bubble, - /datum/ai_planning_subtree/targeted_mob_ability/flop, - /datum/ai_planning_subtree/targeted_mob_ability/volley, - /datum/ai_planning_subtree/targeted_mob_ability/summon, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/go_for_swim, - ) - -/datum/ai_planning_subtree/targeted_mob_ability/pointed_bubble - ability_key = BB_LEAPER_BUBBLE - finish_planning = FALSE - -/datum/ai_planning_subtree/targeted_mob_ability/flop - ability_key = BB_LEAPER_FLOP - finish_planning = FALSE - -/datum/ai_planning_subtree/targeted_mob_ability/flop/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/atom/current_target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] - if(isclosedturf(current_target) || isspaceturf(current_target)) - return - return ..() - -/datum/ai_planning_subtree/targeted_mob_ability/volley - ability_key = BB_LEAPER_VOLLEY - finish_planning = FALSE - -/datum/ai_planning_subtree/targeted_mob_ability/summon - ability_key = BB_LEAPER_SUMMON - finish_planning = FALSE + behavior_tree_json = "code/modules/mob/living/basic/jungle/leaper/leaper.bt.json" /datum/pet_command/use_ability/flop command_name = "Flop" diff --git a/code/modules/mob/living/basic/jungle/mega_arachnid/mega_arachnid.bt.json b/code/modules/mob/living/basic/jungle/mega_arachnid/mega_arachnid.bt.json new file mode 100644 index 00000000000..333cf00241c --- /dev/null +++ b/code/modules/mob/living/basic/jungle/mega_arachnid/mega_arachnid.bt.json @@ -0,0 +1,96 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/mega_arachnid", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/mega_arachnid_combat" + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_SURVEILLANCE_TARGET", + "observer_abort": "BT_ABORT_SELF" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_SURVEILLANCE_TARGET", + "required_dist": 1, + "finish_on_arrival": true + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/ai_interact", + "vars": { + "target_key": "BB_SURVEILLANCE_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/clear_key", + "vars": { + "key": "BB_SURVEILLANCE_TARGET" + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/climb_tree" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_SURVEILLANCE_TARGET", + "target_source": "/datum/target_source/oview_typed/surveillance_equipment", + "targeting_strategy": "/datum/targeting_strategy/working_machine", + "vision_range": 7 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_CLIMBED_TREE", + "target_source": "/datum/target_source/oview_single_type/flora_tree", + "targeting_strategy": "/datum/targeting_strategy/anything" + } + } + ] +} diff --git a/code/modules/mob/living/basic/jungle/mega_arachnid/mega_arachnid_ai.dm b/code/modules/mob/living/basic/jungle/mega_arachnid/mega_arachnid_ai.dm index fbe737eb2b2..15a47f283ca 100644 --- a/code/modules/mob/living/basic/jungle/mega_arachnid/mega_arachnid_ai.dm +++ b/code/modules/mob/living/basic/jungle/mega_arachnid/mega_arachnid_ai.dm @@ -1,79 +1,15 @@ /datum/ai_controller/basic_controller/mega_arachnid + behavior_tree_json = "code/modules/mob/living/basic/jungle/mega_arachnid/mega_arachnid.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_BASIC_MOB_FLEE_DISTANCE = 5, ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/targeted_mob_ability/arachnid_restrain, - /datum/ai_planning_subtree/basic_melee_attack_subtree/mega_arachnid, - /datum/ai_planning_subtree/flee_target/mega_arachnid, - /datum/ai_planning_subtree/climb_trees, - /datum/ai_planning_subtree/find_and_hunt_target/destroy_surveillance, - ) -///destroy surveillance objects to boost our stealth -/datum/ai_planning_subtree/find_and_hunt_target/destroy_surveillance - target_key = BB_SURVEILLANCE_TARGET - finding_behavior = /datum/ai_behavior/find_hunt_target/find_active_surveillance - hunting_behavior = /datum/ai_behavior/hunt_target/interact_with_target - hunt_targets = list(/obj/machinery/camera, /obj/machinery/light) - hunt_range = 7 +/datum/target_source/oview_typed/surveillance_equipment + typecache = list(/obj/machinery/camera = TRUE, /obj/machinery/light = TRUE) -/datum/ai_behavior/find_hunt_target/find_active_surveillance -/datum/ai_behavior/find_hunt_target/find_active_camera/valid_dinner(mob/living/source, obj/machinery/dinner, radius) - if(dinner.machine_stat & BROKEN) - return FALSE - - return can_see(source, dinner, radius) - -///spray slippery acid as we flee! -/datum/ai_planning_subtree/flee_target/mega_arachnid - flee_behaviour = /datum/ai_behavior/run_away_from_target/mega_arachnid - -/datum/ai_planning_subtree/flee_target/mega_arachnid/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(controller.blackboard[BB_BASIC_MOB_STOP_FLEEING]) - return - var/datum/action/cooldown/slip_acid = controller.blackboard[BB_ARACHNID_SLIP] - - if(!QDELETED(slip_acid) && slip_acid.IsAvailable()) - controller.queue_behavior(/datum/ai_behavior/use_mob_ability, BB_ARACHNID_SLIP) - - return ..() - -/datum/ai_behavior/run_away_from_target/mega_arachnid - clear_failed_targets = FALSE - -///only engage in melee combat against cuffed targets, otherwise keep throwing restraints at them -/datum/ai_planning_subtree/basic_melee_attack_subtree/mega_arachnid - ///minimum health our target must be before we can attack them - var/minimum_health = 50 - -/datum/ai_planning_subtree/basic_melee_attack_subtree/mega_arachnid/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/atom/target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] - if(!ishuman(target)) - return ..() - - var/mob/living/carbon/human_target = target - if(!human_target.legcuffed && human_target.health > minimum_health) - return - - return ..() - -/datum/ai_planning_subtree/targeted_mob_ability/arachnid_restrain - ability_key = BB_ARACHNID_RESTRAIN - -/// only fire ability at humans if they are not cuffed -/datum/ai_planning_subtree/targeted_mob_ability/arachnid_restrain/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/mob/living/target = controller.blackboard[target_key] - if(!ishuman(target)) - return - var/mob/living/carbon/human_target = target - if(human_target.legcuffed) - return - return ..() +/datum/bt_node/subtree/mega_arachnid_combat + behavior_tree_json = "code/modules/mob/living/basic/jungle/mega_arachnid/mega_arachnid_combat.bt.json" diff --git a/code/modules/mob/living/basic/jungle/mega_arachnid/mega_arachnid_combat.bt.json b/code/modules/mob/living/basic/jungle/mega_arachnid/mega_arachnid_combat.bt.json new file mode 100644 index 00000000000..bb4cdb6b865 --- /dev/null +++ b/code/modules/mob/living/basic/jungle/mega_arachnid/mega_arachnid_combat.bt.json @@ -0,0 +1,147 @@ +{ + "dm_type": "/datum/bt_node/subtree/mega_arachnid_combat", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CURRENT_TARGET", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "selector", + "children": [ + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/run_away_from_target" + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/ability_available", + "vars": { + "ability_key": "BB_ARACHNID_SLIP" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/use_mob_ability", + "vars": { + "ability_key": "BB_ARACHNID_SLIP" + } + } + } + ] + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/target_is_type", + "vars": { + "target_type": "/mob/living/carbon/human" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/target_legcuffed", + "vars": { + "invert": true + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/ability_available", + "vars": { + "ability_key": "BB_ARACHNID_RESTRAIN" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability", + "vars": { + "ability_key": "BB_ARACHNID_RESTRAIN", + "target_key": "BB_CURRENT_TARGET" + } + } + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/target_is_type", + "vars": { + "target_type": "/mob/living/carbon/human", + "invert": true + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/target_legcuffed", + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/target_health_below_fraction", + "vars": { + "key": "BB_CURRENT_TARGET", + "fraction": 0.5 + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + ] + } +} diff --git a/code/modules/mob/living/basic/jungle/seedling/seedling.bt.json b/code/modules/mob/living/basic/jungle/seedling/seedling.bt.json new file mode 100644 index 00000000000..8fff5f8770d --- /dev/null +++ b/code/modules/mob/living/basic/jungle/seedling/seedling.bt.json @@ -0,0 +1,286 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/seedling", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity/pacifist" + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/override_id_set", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "override_id": "SUBPLAN_ID_PET_COMMAND" + }, + "child": { + "type": "subtree", + "subtype": "/datum/bt_node/subtree", + "override_id": "SUBPLAN_ID_PET_COMMAND" + } + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/target_has_reagent", + "vars": { + "invert": false, + "key": "BB_WATERCAN_TARGET", + "reagent_type": "/datum/reagent/water" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_HYDROPLANT_TARGET", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_HYDROPLANT_TARGET", + "required_dist": 1 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/ai_interact", + "vars": { + "target_key": "BB_HYDROPLANT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/clear_key", + "vars": { + "key": "BB_HYDROPLANT_TARGET" + } + } + ] + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/ability_available", + "vars": { + "ability_key": "BB_SOLARBEAM_ABILITY", + "observer_abort": "BT_ABORT_LOWER_PRIORITY" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_BEAMABLE_HYDROPLANT_TARGET", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_BEAMABLE_HYDROPLANT_TARGET", + "required_dist": 2 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability", + "vars": { + "ability_key": "BB_SOLARBEAM_ABILITY", + "target_key": "BB_BEAMABLE_HYDROPLANT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/clear_key", + "vars": { + "key": "BB_BEAMABLE_HYDROPLANT_TARGET" + } + } + ] + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/item_inside_pawn", + "vars": { + "key": "BB_WATERCAN_TARGET" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/target_has_reagent", + "vars": { + "invert": true, + "key": "BB_WATERCAN_TARGET", + "reagent_type": "/datum/reagent/water" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_LOW_PRIORITY_HUNTING_TARGET" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_LOW_PRIORITY_HUNTING_TARGET", + "required_dist": 1 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/ai_interact", + "vars": { + "target_key": "BB_LOW_PRIORITY_HUNTING_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/clear_key", + "vars": { + "key": "BB_LOW_PRIORITY_HUNTING_TARGET" + } + } + ] + } + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/item_inside_pawn", + "vars": { + "invert": true, + "key": "BB_WATERCAN_TARGET" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_WATERCAN_TARGET", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_WATERCAN_TARGET", + "required_dist": 1 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/ai_interact", + "vars": { + "target_key": "BB_WATERCAN_TARGET" + } + } + ] + } + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/item_inside_pawn", + "vars": { + "invert": true, + "key": "BB_WATERCAN_TARGET" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_WATERCAN_TARGET", + "targeting_strategy": "/datum/targeting_strategy/pickup_item", + "target_source": "/datum/target_source/oview_single_type/watering_can" + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/item_inside_pawn", + "vars": { + "key": "BB_WATERCAN_TARGET" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/target_has_reagent", + "vars": { + "invert": true, + "key": "BB_WATERCAN_TARGET", + "reagent_type": "/datum/reagent/water" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_LOW_PRIORITY_HUNTING_TARGET", + "target_source": "/datum/target_source/oview", + "targeting_strategy": "/datum/targeting_strategy/water_dispenser" + } + } + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_HYDROPLANT_TARGET", + "target_source": "/datum/target_source/oview", + "targeting_strategy": "/datum/targeting_strategy/treatable_hydro", + "time_between_perform": "5 SECONDS" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/ability_available", + "vars": { + "ability_key": "BB_SOLARBEAM_ABILITY" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_BEAMABLE_HYDROPLANT_TARGET", + "target_source": "/datum/target_source/oview", + "targeting_strategy": "/datum/targeting_strategy/beamable_hydro", + "time_between_perform": "4 SECONDS" + } + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/jungle/seedling/seedling_ai.dm b/code/modules/mob/living/basic/jungle/seedling/seedling_ai.dm index fe4f3204f26..18705d45e71 100644 --- a/code/modules/mob/living/basic/jungle/seedling/seedling_ai.dm +++ b/code/modules/mob/living/basic/jungle/seedling/seedling_ai.dm @@ -5,167 +5,15 @@ BB_WEEDLEVEL_THRESHOLD = 3, BB_WATERLEVEL_THRESHOLD = 90, ) - ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity/pacifist, - /datum/ai_planning_subtree/pet_planning, - /datum/ai_planning_subtree/find_and_hunt_target/watering_can, - /datum/ai_planning_subtree/find_and_hunt_target/fill_watercan, - /datum/ai_planning_subtree/find_and_hunt_target/treat_hydroplants, - /datum/ai_planning_subtree/find_and_hunt_target/beamable_hydroplants, - ) - -/datum/ai_planning_subtree/find_and_hunt_target/watering_can - target_key = BB_WATERCAN_TARGET - finding_behavior = /datum/ai_behavior/find_hunt_target - hunting_behavior = /datum/ai_behavior/hunt_target/interact_with_target - hunt_targets = list(/obj/item/reagent_containers/cup/watering_can) - hunt_range = 7 - -/datum/ai_planning_subtree/find_and_hunt_target/watering_can/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/mob/living/living_pawn = controller.pawn - if(locate(/obj/item/reagent_containers/cup/watering_can) in living_pawn) //we already have what we came for! - return - return ..() - -/datum/ai_planning_subtree/find_and_hunt_target/treat_hydroplants - target_key = BB_HYDROPLANT_TARGET - finding_behavior = /datum/ai_behavior/find_and_set/treatable_hydro - hunting_behavior = /datum/ai_behavior/hunt_target/interact_with_target/treat_hydroplant - hunt_targets = list(/obj/machinery/hydroponics) - hunt_range = 7 - -/datum/ai_behavior/find_and_set/treatable_hydro - action_cooldown = 5 SECONDS - -/datum/ai_behavior/find_and_set/treatable_hydro/search_tactic(datum/ai_controller/controller, locate_path, search_range = SEARCH_TACTIC_DEFAULT_RANGE) - var/list/possible_trays = list() - var/mob/living/living_pawn = controller.pawn - var/waterlevel_threshold = controller.blackboard[BB_WATERLEVEL_THRESHOLD] - var/weedlevel_threshold = controller.blackboard[BB_WEEDLEVEL_THRESHOLD] - var/watering_can = locate(/obj/item/reagent_containers/cup/watering_can) in living_pawn - - for(var/obj/machinery/hydroponics/hydro in oview(search_range, controller.pawn)) - if(isnull(hydro.myseed)) - continue - if(hydro.waterlevel < waterlevel_threshold && watering_can) - possible_trays += hydro - continue - if(hydro.weedlevel > weedlevel_threshold || hydro.plant_status == HYDROTRAY_PLANT_DEAD) - possible_trays += hydro - continue - - if(possible_trays.len) - return pick(possible_trays) - -/datum/ai_behavior/hunt_target/interact_with_target/treat_hydroplant - hunt_cooldown = 2 SECONDS - always_reset_target = TRUE - -/datum/ai_behavior/hunt_target/interact_with_target/treat_hydroplant/target_caught(mob/living/living_pawn, obj/machinery/hydroponics/hydro_target) - if(QDELETED(hydro_target) || QDELETED(hydro_target.myseed)) - return - - if(hydro_target.plant_status == HYDROTRAY_PLANT_DEAD) - living_pawn.manual_emote("weeps...") //weep over the dead plants - return ..() - - -/datum/ai_planning_subtree/find_and_hunt_target/beamable_hydroplants - target_key = BB_BEAMABLE_HYDROPLANT_TARGET - finding_behavior = /datum/ai_behavior/find_and_set/beamable_hydroplants - hunting_behavior = /datum/ai_behavior/hunt_target/use_ability_on_target/solarbeam - hunt_targets = list(/obj/machinery/hydroponics) - hunt_range = 7 - -/datum/ai_planning_subtree/find_and_hunt_target/beamable_hydroplants/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/datum/action/cooldown/solar_ability = controller.blackboard[BB_SOLARBEAM_ABILITY] - if(QDELETED(solar_ability) || !solar_ability.IsAvailable()) - return - return ..() - -/datum/ai_behavior/hunt_target/use_ability_on_target/solarbeam - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION - required_distance = 2 - action_cooldown = 1 MINUTES - ability_key = BB_SOLARBEAM_ABILITY - -/datum/ai_behavior/hunt_target/use_ability_on_target/solarbeam/setup(datum/ai_controller/controller, target_key, ability_key) - . = ..() - var/obj/target = controller.blackboard[target_key] - if(QDELETED(target)) - return FALSE - set_movement_target(controller, target) - -/datum/ai_behavior/find_and_set/beamable_hydroplants - action_cooldown = 15 SECONDS - -/datum/ai_behavior/find_and_set/beamable_hydroplants/search_tactic(datum/ai_controller/controller, locate_path, search_range = SEARCH_TACTIC_DEFAULT_RANGE) - var/list/possible_trays = list() - - for(var/obj/machinery/hydroponics/hydro in oview(search_range, controller.pawn)) - if(isnull(hydro.myseed)) - continue - if(hydro.plant_health < hydro.myseed.endurance) - possible_trays += hydro - - if(possible_trays.len) - return pick(possible_trays) - -/datum/ai_planning_subtree/find_and_hunt_target/fill_watercan - target_key = BB_LOW_PRIORITY_HUNTING_TARGET - finding_behavior = /datum/ai_behavior/find_hunt_target/suitable_dispenser - hunting_behavior = /datum/ai_behavior/hunt_target/interact_with_target/water_source - hunt_targets = list(/obj/structure/sink, /obj/structure/reagent_dispensers) - hunt_range = 7 - -/datum/ai_planning_subtree/find_and_hunt_target/fill_watercan/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/mob/living/living_pawn = controller.pawn - var/obj/item/reagent_containers/can = locate(/obj/item/reagent_containers/cup/watering_can) in living_pawn - - if(isnull(can)) - return - if(locate(/datum/reagent/water) in can.reagents.reagent_list) - return - - return ..() - -/datum/ai_behavior/find_hunt_target/suitable_dispenser - -/datum/ai_behavior/find_hunt_target/suitable_dispenser/valid_dinner(mob/living/source, obj/structure/water_source, radius) - if(!(locate(/datum/reagent/water) in water_source.reagents.reagent_list)) - return FALSE - - return can_see(source, water_source, radius) - -/datum/ai_behavior/hunt_target/interact_with_target/water_source - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH - always_reset_target = TRUE - hunt_cooldown = 5 SECONDS + behavior_tree_json = "code/modules/mob/living/basic/jungle/seedling/seedling.bt.json" /datum/ai_controller/basic_controller/seedling/meanie blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_PET_TARGETING_STRATEGY = /datum/targeting_strategy/basic/not_friends, ) - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/pet_planning, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/targeted_mob_ability/seedling_rapid, - /datum/ai_planning_subtree/targeted_mob_ability/solarbeam, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) - -/datum/ai_planning_subtree/targeted_mob_ability/seedling_rapid - ability_key = BB_RAPIDSEEDS_ABILITY - finish_planning = FALSE - -/datum/ai_planning_subtree/targeted_mob_ability/solarbeam - ability_key = BB_SOLARBEAM_ABILITY - finish_planning = FALSE + behavior_tree_json = "code/modules/mob/living/basic/jungle/seedling/seedling_meanie.bt.json" ///pet commands /datum/pet_command/use_ability/solarbeam diff --git a/code/modules/mob/living/basic/jungle/seedling/seedling_meanie.bt.json b/code/modules/mob/living/basic/jungle/seedling/seedling_meanie.bt.json new file mode 100644 index 00000000000..d050e7bf8f0 --- /dev/null +++ b/code/modules/mob/living/basic/jungle/seedling/seedling_meanie.bt.json @@ -0,0 +1,106 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/seedling/meanie", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree", + "override_id": "SUBPLAN_ID_PET_COMMAND" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability", + "vars": { + "ability_key": "BB_RAPIDSEEDS_ABILITY", + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability", + "vars": { + "ability_key": "BB_SOLARBEAM_ABILITY", + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/jungle/venus_human_trap.dm b/code/modules/mob/living/basic/jungle/venus_human_trap.dm index 3d2538b3c5c..b3dc776c33b 100644 --- a/code/modules/mob/living/basic/jungle/venus_human_trap.dm +++ b/code/modules/mob/living/basic/jungle/venus_human_trap.dm @@ -256,18 +256,11 @@ vines -= vine /datum/ai_controller/basic_controller/human_trap + behavior_tree_json = "code/modules/mob/living/basic/jungle/human_trap.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/targeted_mob_ability/continue_planning, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) #undef FINAL_BUD_GROWTH_ICON diff --git a/code/modules/mob/living/basic/lavaland/basilisk/basilisk.bt.json b/code/modules/mob/living/basic/lavaland/basilisk/basilisk.bt.json new file mode 100644 index 00000000000..e3d26ddf95b --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/basilisk/basilisk.bt.json @@ -0,0 +1,116 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/basilisk", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/target_has_trait", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "invert": true, + "key": "TRAIT_OVERWATCHED" + }, + "child": { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/is_at_distance", + "vars": { + "min_distance": 2 + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_ranged_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION", + "max_range": 9 + } + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/attack_obstructions", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/maintain_distance", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/lavaland/basilisk/basilisk.dm b/code/modules/mob/living/basic/lavaland/basilisk/basilisk.dm index 3d6380d25be..4c2f73a2949 100644 --- a/code/modules/mob/living/basic/lavaland/basilisk/basilisk.dm +++ b/code/modules/mob/living/basic/lavaland/basilisk/basilisk.dm @@ -76,17 +76,10 @@ ranged_attacks.projectile_type = projectile_type /datum/ai_controller/basic_controller/basilisk + behavior_tree_json = "code/modules/mob/living/basic/lavaland/basilisk/basilisk.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_AGGRO_RANGE = 5, ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/ranged_skirmish, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) diff --git a/code/modules/mob/living/basic/lavaland/bileworm/bileworm.bt.json b/code/modules/mob/living/basic/lavaland/bileworm/bileworm.bt.json new file mode 100644 index 00000000000..224a83c4cc1 --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/bileworm/bileworm.bt.json @@ -0,0 +1,143 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/bileworm", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/ability_available", + "vars": { + "ability_key": "BB_BILEWORM_DEVOUR" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/mob_stat_at_least", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "invert": false, + "key": "BB_BASIC_MOB_EXECUTION_TARGET", + "min_stat": "UNCONSCIOUS" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability/and_clear_target", + "vars": { + "ability_key": "BB_BILEWORM_DEVOUR", + "target_key": "BB_BASIC_MOB_EXECUTION_TARGET", + "maximum_distance": 16 + } + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": false, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/ability_available", + "vars": { + "ability_key": "BB_BILEWORM_RESURFACE" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bileworm_should_resurface", + "vars": { + "target_key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability/and_plan_execute", + "vars": { + "ability_key": "BB_BILEWORM_RESURFACE", + "target_key": "BB_CURRENT_TARGET" + } + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/ability_available", + "vars": { + "ability_key": "BB_BILEWORM_SPEW_BILE" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability/and_plan_execute", + "vars": { + "ability_key": "BB_BILEWORM_SPEW_BILE", + "target_key": "BB_CURRENT_TARGET" + } + } + } + ] + } + ] + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/cooldown", + "vars": { + "cooldown_key": "BB_BASIC_MOB_REINFORCEMENTS_COOLDOWN", + "cooldown_duration": "30 SECONDS", + "lock_on_succeed": true + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/call_reinforcements/mining" + } + } + ] + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/wait" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/lavaland/bileworm/bileworm_actions.dm b/code/modules/mob/living/basic/lavaland/bileworm/bileworm_actions.dm index d3a45a89a76..136811add24 100644 --- a/code/modules/mob/living/basic/lavaland/bileworm/bileworm_actions.dm +++ b/code/modules/mob/living/basic/lavaland/bileworm/bileworm_actions.dm @@ -29,7 +29,7 @@ to_chat(burrower, span_warning("Couldn't burrow anywhere near the target!")) if(burrower.ai_controller?.ai_status == AI_STATUS_ON) //this is a valid reason to give up on a target - burrower.ai_controller.clear_blackboard_key(BB_BASIC_MOB_CURRENT_TARGET) + burrower.ai_controller.clear_blackboard_key(BB_CURRENT_TARGET) return if (istype(burrower, /mob/living/basic/mining/bileworm) && !force) diff --git a/code/modules/mob/living/basic/lavaland/bileworm/bileworm_ai.dm b/code/modules/mob/living/basic/lavaland/bileworm/bileworm_ai.dm index cd2450018cf..3f7f24cdb4a 100644 --- a/code/modules/mob/living/basic/lavaland/bileworm/bileworm_ai.dm +++ b/code/modules/mob/living/basic/lavaland/bileworm/bileworm_ai.dm @@ -1,54 +1,24 @@ /datum/ai_controller/basic_controller/bileworm + behavior_tree_json = "code/modules/mob/living/basic/lavaland/bileworm/bileworm.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/bileworm, BB_TARGET_PRIORITY_STRATEGY = /datum/target_priority_strategy/mining, BB_BILEWORM_FLEE_DISTANCE = 3, - ) - - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/call_reinforcements/mining, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/bileworm_attack, - /datum/ai_planning_subtree/bileworm_execute, + BB_TARGET_MINIMUM_STAT = UNCONSCIOUS, ) /datum/targeting_strategy/basic/bileworm ignore_sight = TRUE -/datum/ai_planning_subtree/bileworm_attack +/// Passes when the worm should burrow and reposition: it's been scared, or its target has gotten within flee distance. +/datum/bt_node/decorator/bileworm_should_resurface + /// Blackboard key holding the atom we want to keep our distance from. + var/target_key = BB_CURRENT_TARGET -/datum/ai_planning_subtree/bileworm_attack/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if (!controller.blackboard_key_exists(BB_BASIC_MOB_CURRENT_TARGET)) - return - - var/datum/action/cooldown/mob_cooldown/resurface = controller.blackboard[BB_BILEWORM_RESURFACE] - var/datum/action/cooldown/mob_cooldown/bile = controller.blackboard[BB_BILEWORM_SPEW_BILE] - - if(resurface?.IsAvailable() && (controller.blackboard[BB_BILEWORM_SCARED] || get_dist(controller.pawn, controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET]) <= controller.blackboard[BB_BILEWORM_FLEE_DISTANCE])) - controller.queue_behavior(/datum/ai_behavior/targeted_mob_ability/and_plan_execute, BB_BILEWORM_RESURFACE, BB_BASIC_MOB_CURRENT_TARGET) - return SUBTREE_RETURN_FINISH_PLANNING - - if(bile?.IsAvailable()) - controller.queue_behavior(/datum/ai_behavior/targeted_mob_ability/and_plan_execute, BB_BILEWORM_SPEW_BILE, BB_BASIC_MOB_CURRENT_TARGET) - return SUBTREE_RETURN_FINISH_PLANNING //focus on the fight - -/datum/ai_planning_subtree/bileworm_execute - -/datum/ai_planning_subtree/bileworm_execute/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - - var/atom/movable/target = controller.blackboard[BB_BASIC_MOB_EXECUTION_TARGET] - if(QDELETED(target) || !isliving(target)) - return - - var/datum/action/cooldown/mob_cooldown/devour = controller.blackboard[BB_BILEWORM_DEVOUR] - - if(!(devour?.IsAvailable())) - return - - var/mob/living/living_target = target - if(living_target.stat < UNCONSCIOUS) - return - - controller.queue_behavior(/datum/ai_behavior/targeted_mob_ability/and_clear_target, BB_BILEWORM_DEVOUR, BB_BASIC_MOB_EXECUTION_TARGET) - return SUBTREE_RETURN_FINISH_PLANNING //focus on devouring this fool +/datum/bt_node/decorator/bileworm_should_resurface/check_condition(datum/ai_controller/controller) + if(controller.blackboard[BB_BILEWORM_SCARED]) + return TRUE + var/atom/target = controller.blackboard[target_key] + if(QDELETED(target)) + return FALSE + return get_dist(controller.pawn, target) <= controller.blackboard[BB_BILEWORM_FLEE_DISTANCE] diff --git a/code/modules/mob/living/basic/lavaland/brimdemon/brimbeam.dm b/code/modules/mob/living/basic/lavaland/brimdemon/brimbeam.dm index 41a1a0f228a..61f0088b2a2 100644 --- a/code/modules/mob/living/basic/lavaland/brimdemon/brimbeam.dm +++ b/code/modules/mob/living/basic/lavaland/brimdemon/brimbeam.dm @@ -7,7 +7,7 @@ background_icon_state = "bg_demon" overlay_icon_state = "bg_demon_border" click_to_activate = TRUE - cooldown_time = 5 SECONDS + cooldown_time = 3 SECONDS melee_cooldown_time = 0 /// How far does our beam go? var/beam_range = 10 diff --git a/code/modules/mob/living/basic/lavaland/brimdemon/brimdemon.bt.json b/code/modules/mob/living/basic/lavaland/brimdemon/brimdemon.bt.json new file mode 100644 index 00000000000..ea42be930a4 --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/brimdemon/brimdemon.bt.json @@ -0,0 +1,122 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/brimdemon", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability/brimbeam", + "vars": { + "ability_key": "BB_TARGETED_ACTION", + "target_key": "BB_CURRENT_TARGET", + "maximum_distance": 9 + } + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_cardinal", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "minimum_distance": 2, + "maximum_distance": 9 + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/cooldown", + "vars": { + "cooldown_key": "BB_BASIC_MOB_REINFORCEMENTS_COOLDOWN", + "cooldown_duration": "30 SECONDS", + "lock_on_succeed": true + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/call_reinforcements/mining" + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION", + "check_faction": true + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/lavaland/brimdemon/brimdemon_ai.dm b/code/modules/mob/living/basic/lavaland/brimdemon/brimdemon_ai.dm index b60d7ab4a90..78af06d2742 100644 --- a/code/modules/mob/living/basic/lavaland/brimdemon/brimdemon_ai.dm +++ b/code/modules/mob/living/basic/lavaland/brimdemon/brimdemon_ai.dm @@ -9,41 +9,11 @@ ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk/no_target - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/call_reinforcements/mining, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_melee_attack_subtree/opportunistic, - /datum/ai_planning_subtree/move_to_cardinal/brimdemon, - /datum/ai_planning_subtree/targeted_mob_ability/brimbeam, - ) + behavior_tree_json = "code/modules/mob/living/basic/lavaland/brimdemon/brimdemon.bt.json" -/datum/ai_planning_subtree/move_to_cardinal/brimdemon - move_behaviour = /datum/ai_behavior/move_to_cardinal/brimdemon - -/datum/ai_behavior/move_to_cardinal/brimdemon - minimum_distance = 2 - -/datum/ai_behavior/move_to_cardinal/brimdemon/finish_action(datum/ai_controller/controller, succeeded, target_key) - . = ..() - if(!succeeded) - return +/// Brimdemon's beam only fires when the target is lined up on a cardinal direction. +/datum/bt_node/ai_behavior/targeted_mob_ability/brimbeam/perform(seconds_per_tick, datum/ai_controller/controller) var/mob/living/target = controller.blackboard[target_key] - var/datum/action/cooldown/ability = controller.blackboard[BB_TARGETED_ACTION] - if(QDELETED(target) || QDELETED(controller.pawn) || !ability?.IsAvailable()) - return - ability.InterceptClickOn(clicker = controller.pawn, target = target) - -/datum/ai_planning_subtree/targeted_mob_ability/brimbeam - use_ability_behaviour = /datum/ai_behavior/targeted_mob_ability/brimbeam - -/datum/ai_behavior/targeted_mob_ability/brimbeam - /// Don't shoot if too far away - var/max_target_distance = 9 - -/datum/ai_behavior/targeted_mob_ability/brimbeam/perform(seconds_per_tick, datum/ai_controller/controller, ability_key, target_key) - var/mob/living/target = controller.blackboard[target_key] - if (QDELETED(target) || !(get_dir(controller.pawn, target) in GLOB.cardinals) || get_dist(controller.pawn, target) > max_target_distance) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + if(QDELETED(target) || !(get_dir(controller.pawn, target) in GLOB.cardinals)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED return ..() diff --git a/code/modules/mob/living/basic/lavaland/goldgrub/babygrub.bt.json b/code/modules/mob/living/basic/lavaland/goldgrub/babygrub.bt.json new file mode 100644 index 00000000000..9f009491b52 --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/goldgrub/babygrub.bt.json @@ -0,0 +1,93 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/babygrub", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity/pacifist" + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/dig_away_from_danger" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/run_away_from_target" + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/grub_eat_target", + "bindings": { + "bjwb8dxm": "BB_ORE_TARGET", + "bj13tsp3": "BB_ORE_TARGET", + "b6t594ql": "BB_ORE_TARGET" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_FOUND_MOM" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_FOUND_MOM", + "required_dist": 1, + "finish_on_arrival": true + } + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/find_ore" + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/find_mom" + } + ] + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/lavaland/goldgrub/goldgrub.bt.json b/code/modules/mob/living/basic/lavaland/goldgrub/goldgrub.bt.json new file mode 100644 index 00000000000..f445db7953f --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/goldgrub/goldgrub.bt.json @@ -0,0 +1,238 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/goldgrub", + "type": "selector", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/pawn_buckled_to_obj", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/resist" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/pawn_grabbed_by_enemy", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/resist" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/pawn_is_restrained", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/resist" + } + } + ] + }, + { + "type": "subtree", + "subtype": "", + "override_id": "SUBPLAN_ID_PET_COMMAND" + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/dig_away_from_danger" + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/burrow_through_ground" + } + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/run_away_from_target" + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/grub_eat_target", + "bindings": { + "b6t594ql": "BB_BOULDER_TARGET", + "bj13tsp3": "BB_BOULDER_TARGET", + "bjwb8dxm": "BB_BOULDER_TARGETBB_BOULDER_TARGET" + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/grub_eat_target", + "bindings": { + "b6t594ql": "BB_ORE_TARGET", + "bj13tsp3": "BB_ORE_TARGET", + "bjwb8dxm": "BB_ORE_TARGET" + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/grub_eat_target", + "bindings": { + "b6t594ql": "BB_VENT_TARGET", + "bj13tsp3": "BB_VENT_TARGET", + "bjwb8dxm": "BB_VENT_TARGET" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_TARGET_MINERAL_WALL" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_TARGET_MINERAL_WALL" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/set_bb_cooldown", + "vars": { + "cooldown_key": "BB_MINING_COOLDOWN", + "cooldown_duration": "4 SECONDS" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/mine_wall", + "vars": { + "target_key": "BB_TARGET_MINERAL_WALL" + } + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_LOW_PRIORITY_HUNTING_TARGET", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_LOW_PRIORITY_HUNTING_TARGET", + "required_dist": 1, + "finish_on_arrival": true + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/grab_target", + "vars": { + "target_key": "BB_LOW_PRIORITY_HUNTING_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/clear_key", + "vars": { + "key": "BB_LOW_PRIORITY_HUNTING_TARGET" + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/find_ore" + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/find_boulder" + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/find_ore_vent" + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/key_off_cooldown", + "vars": { + "cooldown_key": "BB_MINING_COOLDOWN" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/find_mineral_wall", + "vars": { + "target_key": "BB_TARGET_MINERAL_WALL" + } + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/find_grub_egg" + } + ] + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/lavaland/goldgrub/goldgrub_ai.dm b/code/modules/mob/living/basic/lavaland/goldgrub/goldgrub_ai.dm index 0f0cbbed3da..e50003731f0 100644 --- a/code/modules/mob/living/basic/lavaland/goldgrub/goldgrub_ai.dm +++ b/code/modules/mob/living/basic/lavaland/goldgrub/goldgrub_ai.dm @@ -1,5 +1,5 @@ -#define BURROW_RANGE 5 /datum/ai_controller/basic_controller/goldgrub + ai_traits = parent_type::ai_traits | RUN_WHILE_UNWATCHED blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_PET_TARGETING_STRATEGY = /datum/targeting_strategy/basic/not_friends, @@ -8,20 +8,7 @@ ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk/less_walking - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity/pacifist, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/pet_planning, - /datum/ai_planning_subtree/burrow_through_ground, - /datum/ai_planning_subtree/dig_away_from_danger, - /datum/ai_planning_subtree/flee_target, - /datum/ai_planning_subtree/find_and_hunt_target/hunt_ores, - /datum/ai_planning_subtree/find_and_hunt_target/break_boulders, - /datum/ai_planning_subtree/find_and_hunt_target/harvest_vents, - /datum/ai_planning_subtree/find_and_hunt_target/baby_egg, - /datum/ai_planning_subtree/mine_walls, - ) + behavior_tree_json = "code/modules/mob/living/basic/lavaland/goldgrub/goldgrub.bt.json" /datum/ai_controller/basic_controller/babygrub blackboard = list( @@ -33,183 +20,7 @@ ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk/less_walking - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity/pacifist, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/dig_away_from_danger, - /datum/ai_planning_subtree/find_and_hunt_target/hunt_ores, - /datum/ai_planning_subtree/flee_target, - /datum/ai_planning_subtree/look_for_adult, - ) - -/datum/ai_planning_subtree/burrow_through_ground - -/datum/ai_planning_subtree/burrow_through_ground/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(is_jaunting(controller.pawn) && controller.blackboard_key_exists(BB_BASIC_MOB_CURRENT_TARGET)) - controller.queue_behavior(/datum/ai_behavior/burrow_through_ground, BB_BASIC_MOB_CURRENT_TARGET) - return SUBTREE_RETURN_FINISH_PLANNING - -/datum/ai_behavior/burrow_through_ground - action_cooldown = 10 SECONDS - -/datum/ai_behavior/burrow_through_ground/perform(seconds_per_tick, datum/ai_controller/controller, target_key) - var/atom/target = controller.blackboard[target_key] - if(!is_jaunting(controller.pawn) || QDELETED(target)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - var/mob/living/living_pawn = controller.pawn - var/atom/movable/phased = living_pawn.loc - - var/list/turfs_list = RANGE_TURFS(BURROW_RANGE, phased) - var/current_max_distance = 0 - var/turf/selected_turf - - for(var/turf/possible_turf as anything in turfs_list) - if(!ismineralturf(possible_turf) && !isasteroidturf(possible_turf)) - continue - - var/distance_to_target = get_dist(possible_turf, target) - if(distance_to_target > current_max_distance) - current_max_distance = distance_to_target - selected_turf = possible_turf - - if(distance_to_target == BURROW_RANGE) - break - - if(isnull(selected_turf)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - phased.forceMove(selected_turf) - return AI_BEHAVIOR_SUCCEEDED | AI_BEHAVIOR_DELAY - -///consume food! -/datum/ai_planning_subtree/find_and_hunt_target/hunt_ores - target_key = BB_ORE_TARGET - hunting_behavior = /datum/ai_behavior/hunt_target/interact_with_target/hunt_ores - finding_behavior = /datum/ai_behavior/find_hunt_target/hunt_ores - hunt_targets = list(/obj/item/stack/ore) - hunt_chance = 90 - hunt_range = 9 - -/datum/ai_behavior/find_hunt_target/hunt_ores - -/datum/ai_behavior/find_hunt_target/hunt_ores/valid_dinner(mob/living/basic/source, obj/item/stack/ore/target, radius) - var/list/forbidden_ore = source.ai_controller.blackboard[BB_ORE_IGNORE_TYPES] - - if(is_type_in_list(target, forbidden_ore)) - return FALSE - - if(!isturf(target.loc)) - return FALSE - - var/obj/item/pet_target = source.ai_controller.blackboard[BB_CURRENT_PET_TARGET] - if(target == pet_target) //we are currently fetching this ore for master, dont eat it! - return FALSE - - return can_see(source, target, radius) - -/datum/ai_behavior/hunt_target/interact_with_target/hunt_ores - always_reset_target = TRUE - -///break boulders so that we can find more food! -/datum/ai_planning_subtree/find_and_hunt_target/harvest_vents - target_key = BB_VENT_TARGET - hunting_behavior = /datum/ai_behavior/hunt_target/interact_with_target //We call the ore vent's produce_boulder() proc here to produce a single boulder. - finding_behavior = /datum/ai_behavior/find_hunt_target/harvest_vents - hunt_targets = list(/obj/structure/ore_vent) - hunt_chance = 25 - hunt_range = 15 - -/datum/ai_behavior/find_hunt_target/harvest_vents - -/datum/ai_behavior/find_hunt_target/harvest_vents/valid_dinner(mob/living/basic/source, obj/structure/target, radius) - if(target in source) - return FALSE - - var/turf/vent_turf = target.drop_location() - var/counter = 0 - for(var/obj/item/boulder in vent_turf.contents) - counter++ - if(counter > MAX_BOULDERS_PER_VENT) //Too many items currently on the vent - return FALSE - - return can_see(source, target, radius) - -///break boulders so that we can find more food! -/datum/ai_planning_subtree/find_and_hunt_target/break_boulders - target_key = BB_BOULDER_TARGET - hunting_behavior = /datum/ai_behavior/hunt_target/interact_with_target //We process boulders once every tap, so we dont need to do anything special here - finding_behavior = /datum/ai_behavior/find_hunt_target/break_boulders - hunt_targets = list(/obj/item/boulder) - hunt_chance = 100 //If we can, we should always break boulders. - hunt_range = 9 - -/datum/ai_behavior/find_hunt_target/break_boulders - -/datum/ai_behavior/find_hunt_target/break_boulders/valid_dinner(mob/living/basic/source, obj/item/boulder/target, radius) - if(target in source) - return FALSE - - var/obj/item/pet_target = source.ai_controller.blackboard[BB_CURRENT_PET_TARGET] - if(target == pet_target) //we are currently fetching this ore for master, dont eat it! - return FALSE - return can_see(source, target, radius) - -///find our child's egg and pull it! -/datum/ai_planning_subtree/find_and_hunt_target/baby_egg - target_key = BB_LOW_PRIORITY_HUNTING_TARGET - hunting_behavior = /datum/ai_behavior/hunt_target/grub_egg - finding_behavior = /datum/ai_behavior/find_hunt_target - hunt_targets = list(/obj/item/food/egg/green/grub_egg) - hunt_chance = 75 - hunt_range = 9 - -/datum/ai_planning_subtree/find_and_hunt_target/baby_egg - -/datum/ai_planning_subtree/find_and_hunt_target/baby_egg/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/mob/living/living_pawn = controller.pawn - if(living_pawn.pulling) //we are already pulling something - return - return ..() - -/datum/ai_behavior/hunt_target/grub_egg - always_reset_target = TRUE - -/datum/ai_behavior/hunt_target/grub_egg/target_caught(mob/living/hunter, obj/item/target) - hunter.start_pulling(target) - - -///only dig away if storm is coming or if humans are around -/datum/ai_planning_subtree/dig_away_from_danger - -/datum/ai_planning_subtree/dig_away_from_danger/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/currently_underground = is_jaunting(controller.pawn) - var/storm_approaching = controller.blackboard[BB_STORM_APPROACHING] - - //dont do anything until the storm passes - if(currently_underground && storm_approaching) - return SUBTREE_RETURN_FINISH_PLANNING - - var/datum/action/cooldown/dig_ability = controller.blackboard[BB_BURROW_ABILITY] - - if(!dig_ability.IsAvailable()) - return - - var/has_target = controller.blackboard_key_exists(BB_BASIC_MOB_CURRENT_TARGET) - - //a storm is coming or someone is nearby, its time to escape - if(currently_underground) - if(has_target) - return - controller.queue_behavior(/datum/ai_behavior/use_mob_ability/burrow, BB_BURROW_ABILITY) - return SUBTREE_RETURN_FINISH_PLANNING - if(storm_approaching || has_target) - controller.queue_behavior(/datum/ai_behavior/use_mob_ability/burrow, BB_BURROW_ABILITY) - return SUBTREE_RETURN_FINISH_PLANNING - -/datum/ai_behavior/use_mob_ability/burrow - behavior_flags = AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION + behavior_tree_json = "code/modules/mob/living/basic/lavaland/goldgrub/babygrub.bt.json" /datum/pet_command/grub_spit command_name = "Spit" @@ -222,11 +33,182 @@ var/datum/action/cooldown/spit_ability = controller.blackboard[BB_SPIT_ABILITY] if(!spit_ability?.IsAvailable()) return - controller.queue_behavior(/datum/ai_behavior/use_mob_ability, BB_SPIT_ABILITY) + controller.set_blackboard_key(BB_PET_ACTIVE_ABILITY, spit_ability) + controller.set_behavior_tree_override(SUBPLAN_ID_PET_COMMAND, /datum/bt_node/subtree/pet_command/untargeted_ability) controller.clear_blackboard_key(BB_ACTIVE_PET_COMMAND) - return SUBTREE_RETURN_FINISH_PLANNING /datum/pet_command/grub_spit/retrieve_command_text(atom/living_pet, atom/target) return "signals [living_pet] to spit its ores!" + +#define BURROW_RANGE 5 + + +/** + * Burrows underground (or stays underground) when danger is present or a storm is approaching. + * Returns RUNNING while underground+storm (blocks everything). + * Returns FAILURE when no action needed so the selector passes through. + */ +/datum/bt_node/ai_behavior/dig_away_from_danger + +/datum/bt_node/ai_behavior/dig_away_from_danger/perform(seconds_per_tick, datum/ai_controller/controller) + var/currently_underground = is_jaunting(controller.pawn) + var/storm_approaching = controller.blackboard[BB_STORM_APPROACHING] + var/datum/action/cooldown/dig_ability = controller.blackboard[BB_BURROW_ABILITY] + + if(currently_underground && storm_approaching) + return AI_BEHAVIOR_DELAY // Stay underground while storm approaches RUNNING blocks everything + + if(!dig_ability?.IsAvailable()) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + var/has_target = controller.blackboard_key_exists(BB_CURRENT_TARGET) + + if(currently_underground && !has_target) + // No target/danger while underground emerge + INVOKE_ASYNC(dig_ability, TYPE_PROC_REF(/datum/action, Trigger)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + + if(storm_approaching || has_target) + // Go underground to escape + INVOKE_ASYNC(dig_ability, TYPE_PROC_REF(/datum/action, Trigger)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + +/** + * While jaunting (underground) with a target, moves the grub's phased form further away. + * Returns FAILURE when not jaunting so the selector passes through. + */ +/datum/bt_node/ai_behavior/burrow_through_ground + time_between_perform = 10 SECONDS + +/datum/bt_node/ai_behavior/burrow_through_ground/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/living_pawn = controller.pawn + var/atom/target = controller.blackboard[BB_CURRENT_TARGET] + if(!is_jaunting(living_pawn) || QDELETED(target)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + var/atom/movable/phased = living_pawn.loc + var/list/turfs_list = RANGE_TURFS(BURROW_RANGE, phased) + var/current_max_distance = 0 + var/turf/selected_turf + + for(var/turf/possible_turf as anything in turfs_list) + if(!ismineralturf(possible_turf) && !isasteroidturf(possible_turf)) + continue + var/distance_to_target = get_dist(possible_turf, target) + if(distance_to_target > current_max_distance) + current_max_distance = distance_to_target + selected_turf = possible_turf + if(distance_to_target == BURROW_RANGE) + break + + if(isnull(selected_turf)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + phased.forceMove(selected_turf) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + + +/// Finds an ore pile. Sets BB_ORE_TARGET. Skips forbidden types and fetch targets. +/datum/bt_node/ai_behavior/find_ore + time_between_perform = 5 SECONDS + var/range = 9 + +/datum/bt_node/ai_behavior/find_ore/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/grub_pawn = controller.pawn + var/list/forbidden = controller.blackboard[BB_ORE_IGNORE_TYPES] + var/pet_target = controller.blackboard[BB_CURRENT_PET_TARGET] + for(var/obj/item/stack/ore/candidate in oview(range, grub_pawn)) + if(is_type_in_list(candidate, forbidden) || !isturf(candidate.loc)) + continue + if(candidate == pet_target) + continue + if(!can_see(grub_pawn, candidate, range)) + continue + controller.set_blackboard_key(BB_ORE_TARGET, candidate) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + +/// Finds a boulder the goldgrub can break. Sets BB_BOULDER_TARGET. +/datum/bt_node/ai_behavior/find_boulder + time_between_perform = 5 SECONDS + +/datum/bt_node/ai_behavior/find_boulder/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/grub_pawn = controller.pawn + var/pet_target = controller.blackboard[BB_CURRENT_PET_TARGET] + for(var/obj/item/boulder/candidate in oview(9, grub_pawn)) + if(candidate == pet_target) + continue + if(!can_see(grub_pawn, candidate, 9)) + continue + controller.set_blackboard_key(BB_BOULDER_TARGET, candidate) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + +/// Finds an ore vent to harvest. Sets BB_VENT_TARGET. +/datum/bt_node/ai_behavior/find_ore_vent + time_between_perform = 10 SECONDS + +/datum/bt_node/ai_behavior/find_ore_vent/perform(seconds_per_tick, datum/ai_controller/controller) + if(!SPT_PROB(25, seconds_per_tick)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + var/mob/living/grub_pawn = controller.pawn + for(var/obj/structure/ore_vent/candidate in oview(15, grub_pawn)) + if(candidate in grub_pawn) + continue + var/turf/vent_turf = candidate.drop_location() + var/counter = 0 + var/too_many = FALSE + for(var/obj/item/boulder in vent_turf.contents) + counter++ + if(counter > MAX_BOULDERS_PER_VENT) + too_many = TRUE + break + if(too_many || !can_see(grub_pawn, candidate, 15)) + continue + controller.set_blackboard_key(BB_VENT_TARGET, candidate) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + +/// Finds a grub egg to protect/pull. Sets BB_LOW_PRIORITY_HUNTING_TARGET. +/datum/bt_node/ai_behavior/find_grub_egg + time_between_perform = 10 SECONDS + +/datum/bt_node/ai_behavior/find_grub_egg/perform(seconds_per_tick, datum/ai_controller/controller) + if(!SPT_PROB(75, seconds_per_tick)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + var/mob/living/grub_pawn = controller.pawn + if(grub_pawn.pulling) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + for(var/obj/item/food/egg/green/grub_egg/egg in oview(9, grub_pawn)) + if(!can_see(grub_pawn, egg, 9)) + continue + controller.set_blackboard_key(BB_LOW_PRIORITY_HUNTING_TARGET, egg) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + +/// Eats/interacts with a target at the given blackboard key. Must be adjacent. +/datum/bt_node/ai_behavior/grub_eat + var/target_key + time_between_perform = 3 SECONDS + +/datum/bt_node/ai_behavior/grub_eat/perform(seconds_per_tick, datum/ai_controller/controller) + var/atom/target = controller.blackboard[target_key] + if(QDELETED(target)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + if(!controller.pawn.Adjacent(target)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + INVOKE_ASYNC(controller, TYPE_PROC_REF(/datum/ai_controller, ai_interact), target, FALSE) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + +/datum/bt_node/ai_behavior/grub_eat/finish_action(datum/ai_controller/controller, succeeded) + . = ..() + controller.clear_blackboard_key(target_key) + +/datum/bt_node/subtree/grub_eat_target + behavior_tree_json = "code/modules/mob/living/basic/lavaland/goldgrub/grub_eat_target.bt.json" + #undef BURROW_RANGE diff --git a/code/modules/mob/living/basic/lavaland/goldgrub/grub_eat_target.bt.json b/code/modules/mob/living/basic/lavaland/goldgrub/grub_eat_target.bt.json new file mode 100644 index 00000000000..13dc419c109 --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/goldgrub/grub_eat_target.bt.json @@ -0,0 +1,44 @@ +{ + "dm_type": "/datum/bt_node/subtree/grub_eat_target", + "bindings": { + "b6t594ql": { + "label": "target_key", + "default": "null" + }, + "bj13tsp3": { + "label": "target_key", + "default": "" + }, + "bjwb8dxm": { + "label": "target_key", + "default": "" + } + }, + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "$b6t594ql" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "$bj13tsp3", + "required_dist": 1, + "finish_on_arrival": true + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/grub_eat", + "vars": { + "target_key": "$bjwb8dxm" + } + } + ] + } +} diff --git a/code/modules/mob/living/basic/lavaland/goliath/goliath.bt.json b/code/modules/mob/living/basic/lavaland/goliath/goliath.bt.json new file mode 100644 index 00000000000..e6a5703bc92 --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/goliath/goliath.bt.json @@ -0,0 +1,171 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/goliath", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability/goliath_tentacles", + "vars": { + "ability_key": "BB_GOLIATH_TENTACLES", + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/attack_obstructions" + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/cooldown", + "vars": { + "cooldown_key": "BB_BASIC_MOB_REINFORCEMENTS_COOLDOWN", + "cooldown_duration": "30 SECONDS" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/call_reinforcements/mining", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + } + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/move_to_and_eat" + }, + { + "type": "sequence", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_GOLIATH_HOLE_TARGET", + "invert": true + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/goliath_find_diggable_turf", + "vars": { + "target_key": "BB_GOLIATH_HOLE_TARGET" + } + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_GOLIATH_HOLE_TARGET", + "required_dist": 0 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/goliath_dig", + "vars": { + "target_key": "BB_GOLIATH_HOLE_TARGET" + } + } + ] + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION", + "check_faction": true + } + } + ] + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/lavaland/goliath/goliath_ai.dm b/code/modules/mob/living/basic/lavaland/goliath/goliath_ai.dm index 60dfd7a15d0..639d60fe530 100644 --- a/code/modules/mob/living/basic/lavaland/goliath/goliath_ai.dm +++ b/code/modules/mob/living/basic/lavaland/goliath/goliath_ai.dm @@ -9,117 +9,52 @@ ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/call_reinforcements/mining, - /datum/ai_planning_subtree/target_retaliate/check_faction, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/find_food, - /datum/ai_planning_subtree/targeted_mob_ability/goliath_tentacles, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/basic_melee_attack_subtree/goliath, - /datum/ai_planning_subtree/goliath_find_diggable_turf, - /datum/ai_planning_subtree/goliath_dig, - ) + behavior_tree_json = "code/modules/mob/living/basic/lavaland/goliath/goliath.bt.json" -/datum/ai_planning_subtree/basic_melee_attack_subtree/goliath - operational_datums = list(/datum/component/ai_target_timer) - melee_attack_behavior = /datum/ai_behavior/basic_melee_attack/goliath +/// Use tentacles on the current target only after tracking them for MIN_TIME_TO_TENTACLE, and only if not already leg-grappled +/datum/bt_node/ai_behavior/targeted_mob_ability/goliath_tentacles + var/min_target_time = MIN_TIME_TO_TENTACLE -/// Go for the tentacles if they're available -/datum/ai_behavior/basic_melee_attack/goliath - -/datum/ai_behavior/basic_melee_attack/goliath/perform(seconds_per_tick, datum/ai_controller/controller, target_key, targeting_strategy_key, hiding_location_key, health_ratio_key) - var/time_on_target = controller.blackboard[BB_BASIC_MOB_HAS_TARGET_TIME] || 0 - if (time_on_target < MIN_TIME_TO_TENTACLE) - return ..() +/datum/bt_node/ai_behavior/targeted_mob_ability/goliath_tentacles/perform(seconds_per_tick, datum/ai_controller/controller) var/mob/living/target = controller.blackboard[target_key] - // Interrupt attack chain to use tentacles, unless the target is already tentacled - if (ismecha(target) || (isliving(target) && !target.get_item_by_slot(ITEM_SLOT_LEGCUFFED))) - var/datum/action/cooldown/using_action = controller.blackboard[BB_GOLIATH_TENTACLES] - if (using_action?.IsAvailable()) - return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + if(!(isliving(target) || ismecha(target))) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + if(isliving(target) && target.get_item_by_slot(ITEM_SLOT_LEGCUFFED)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + var/time_on_target = controller.blackboard[BB_BASIC_MOB_HAS_TARGET_TIME] || 0 + if(time_on_target < min_target_time) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED return ..() -/datum/ai_planning_subtree/targeted_mob_ability/goliath_tentacles - ability_key = BB_GOLIATH_TENTACLES - operational_datums = list(/datum/component/ai_target_timer) - -/datum/ai_planning_subtree/targeted_mob_ability/goliath_tentacles/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/mob/living/target = controller.blackboard[target_key] - if (!(isliving(target) || ismecha(target)) || (isliving(target) && target.get_item_by_slot(ITEM_SLOT_LEGCUFFED))) - return // Target can be an item or already grabbed, we don't want to tentacle those - var/time_on_target = controller.blackboard[BB_BASIC_MOB_HAS_TARGET_TIME] || 0 - if (time_on_target < MIN_TIME_TO_TENTACLE) - return // We need to spend some time acquiring our target first - return ..() - -/// If we got nothing better to do, find a turf we can search for tasty roots and such -/datum/ai_planning_subtree/goliath_find_diggable_turf - -/datum/ai_planning_subtree/goliath_find_diggable_turf/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - controller.queue_behavior(/datum/ai_behavior/goliath_find_diggable_turf) - -/datum/ai_behavior/goliath_find_diggable_turf - action_cooldown = 2 SECONDS - /// Where do we store the target data +/// Randomly picks a nearby undig asteroid turf to dig and stores it in target_key +/datum/bt_node/ai_behavior/goliath_find_diggable_turf + time_between_perform = 2 SECONDS var/target_key = BB_GOLIATH_HOLE_TARGET - /// How far do we look for turfs? var/scan_range = 3 -/datum/ai_behavior/goliath_find_diggable_turf/perform(seconds_per_tick, datum/ai_controller/controller) - var/turf/target_turf = controller.blackboard[target_key] - if (is_valid_turf(target_turf)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - +/datum/bt_node/ai_behavior/goliath_find_diggable_turf/perform(seconds_per_tick, datum/ai_controller/controller) var/mob/living/pawn = controller.pawn var/list/nearby_turfs = RANGE_TURFS(scan_range, pawn) - var/turf/check_turf = pick(nearby_turfs) // This isn't an efficient search algorithm but we don't need it to be - if (!is_valid_turf(check_turf)) - // Otherwise they won't perform idle wanderin + var/turf/open/misc/asteroid/check_turf = pick(nearby_turfs) + if(!istype(check_turf) || check_turf.dug) return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED controller.set_blackboard_key(target_key, check_turf) return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED -/// Return true if this is a turf we can dig -/datum/ai_behavior/goliath_find_diggable_turf/proc/is_valid_turf(turf/check_turf) - if (!isasteroidturf(check_turf)) - return FALSE - var/turf/open/misc/asteroid/floor = check_turf - return !floor.dug - -/datum/ai_planning_subtree/goliath_dig - /// Where did we store the target data +/// Melee-attacks the stored hole target turf and clears the key when done +/datum/bt_node/ai_behavior/goliath_dig + time_between_perform = 3 MINUTES var/target_key = BB_GOLIATH_HOLE_TARGET -/datum/ai_planning_subtree/goliath_dig/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if (!controller.blackboard_key_exists(target_key)) - return - controller.queue_behavior(/datum/ai_behavior/goliath_dig, target_key) - return SUBTREE_RETURN_FINISH_PLANNING - -/// If we got nothing better to do, dig a little hole -/datum/ai_behavior/goliath_dig - action_cooldown = 3 MINUTES - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION - -/datum/ai_behavior/goliath_dig/setup(datum/ai_controller/controller, target_key) - . = ..() - var/turf/target_turf = controller.blackboard[target_key] - if (QDELETED(target_turf)) - return - set_movement_target(controller, target_turf) - -/datum/ai_behavior/goliath_dig/perform(seconds_per_tick, datum/ai_controller/controller, target_key) +/datum/bt_node/ai_behavior/goliath_dig/perform(seconds_per_tick, datum/ai_controller/controller) var/turf/target_turf = controller.blackboard[target_key] var/mob/living/basic/basic_mob = controller.pawn - if(!target_turf.IsReachableBy(basic_mob)) - return AI_BEHAVIOR_DELAY + if(isnull(target_turf) || !target_turf.IsReachableBy(basic_mob)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED basic_mob.melee_attack(target_turf) return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED -/datum/ai_behavior/goliath_dig/finish_action(datum/ai_controller/controller, succeeded, target_key) +/datum/bt_node/ai_behavior/goliath_dig/finish_action(datum/ai_controller/controller, succeeded) . = ..() controller.clear_blackboard_key(target_key) diff --git a/code/modules/mob/living/basic/lavaland/gutlunchers/gutlunch_baby.bt.json b/code/modules/mob/living/basic/lavaland/gutlunchers/gutlunch_baby.bt.json new file mode 100644 index 00000000000..6351fb95695 --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/gutlunchers/gutlunch_baby.bt.json @@ -0,0 +1,81 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/gutlunch/gutlunch_baby", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity/pacifist" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/run_away_from_target" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_FOUND_MOM" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_FOUND_MOM", + "required_dist": 1, + "finish_on_arrival": true + } + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION", + "check_faction": false + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/find_parent", + "vars": { + "mom_types_key": "BB_FIND_MOM_TYPES", + "found_mom_key": "BB_FOUND_MOM" + } + } + ] + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/lavaland/gutlunchers/gutlunch_milk.bt.json b/code/modules/mob/living/basic/lavaland/gutlunchers/gutlunch_milk.bt.json new file mode 100644 index 00000000000..8b412de5dcc --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/gutlunchers/gutlunch_milk.bt.json @@ -0,0 +1,96 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/gutlunch/gutlunch_milk", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity/pacifist" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/run_away_from_target" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_TROUGH_TARGET" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_TROUGH_TARGET", + "required_dist": 1 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/hunt_target/interact_with_target/food_trough", + "vars": { + "target_key": "BB_TROUGH_TARGET" + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION", + "check_faction": false + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_true", + "vars": { + "key": "BB_CHECK_HUNGRY" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_TROUGH_TARGET", + "target_source": "/datum/target_source/oview_single_type/gutlunch_trough", + "targeting_strategy": "/datum/targeting_strategy/trough_with_ore", + "vision_range": 9 + } + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/lavaland/gutlunchers/gutlunch_warrior.bt.json b/code/modules/mob/living/basic/lavaland/gutlunchers/gutlunch_warrior.bt.json new file mode 100644 index 00000000000..5b32a3d8dc7 --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/gutlunchers/gutlunch_warrior.bt.json @@ -0,0 +1,53 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/gutlunch/gutlunch_warrior", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "subtree", + "subtype": "", + "override_id": "SUBPLAN_ID_PET_COMMAND" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/make_babies" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "5 SECONDS", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/make_babies" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_retaliate_combat", + "bindings": { + "b2jvnm5d": "TRUE" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/befriend_ashwalkers" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/find_partner" + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/lavaland/gutlunchers/gutlunchers_ai.dm b/code/modules/mob/living/basic/lavaland/gutlunchers/gutlunchers_ai.dm index 800f4c351aa..96aed06f146 100644 --- a/code/modules/mob/living/basic/lavaland/gutlunchers/gutlunchers_ai.dm +++ b/code/modules/mob/living/basic/lavaland/gutlunchers/gutlunchers_ai.dm @@ -1,7 +1,7 @@ #define MAXIMUM_GUTLUNCH_POP 20 /datum/ai_controller/basic_controller/gutlunch ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk + behavior_tree_json = ABSTRACT_AI_CLASS /datum/ai_controller/basic_controller/gutlunch/gutlunch_warrior blackboard = list( @@ -10,99 +10,22 @@ BB_BABIES_PARTNER_TYPES = list(/mob/living/basic/mining/gutlunch/milk), BB_BABIES_CHILD_TYPES = list(/mob/living/basic/mining/gutlunch/grub), BB_MAX_CHILDREN = 5, + BB_FUCKS = TRUE, ) - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/target_retaliate/check_faction, - /datum/ai_planning_subtree/pet_planning, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/befriend_ashwalkers, - /datum/ai_planning_subtree/make_babies/gutlunch, - ) - -/datum/ai_planning_subtree/make_babies/gutlunch/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(GLOB.gutlunch_count >= MAXIMUM_GUTLUNCH_POP) - return - return ..() - -///find ashwalkers and add them to the list of masters -/datum/ai_planning_subtree/befriend_ashwalkers - -/datum/ai_planning_subtree/befriend_ashwalkers/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - controller.queue_behavior(/datum/ai_behavior/befriend_ashwalkers) - -/datum/ai_behavior/befriend_ashwalkers - action_cooldown = 5 SECONDS - behavior_flags = AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION - -/datum/ai_behavior/befriend_ashwalkers/perform(seconds_per_tick, datum/ai_controller/controller, target_key) - var/mob/living/living_pawn = controller.pawn - - for(var/mob/living/potential_friend in oview(9, living_pawn)) - if(!isashwalker(potential_friend)) - continue - if(living_pawn.has_ally(REF(potential_friend))) - continue - living_pawn.befriend(potential_friend) - to_chat(potential_friend, span_nicegreen("[living_pawn] looks at you with endearing eyes!")) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - + behavior_tree_json = "code/modules/mob/living/basic/lavaland/gutlunchers/gutlunch_warrior.bt.json" /datum/ai_controller/basic_controller/gutlunch/gutlunch_baby blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_FIND_MOM_TYPES = list(/mob/living/basic/mining/gutlunch/milk), ) - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity/pacifist, - /datum/ai_planning_subtree/target_retaliate, - /datum/ai_planning_subtree/flee_target, - /datum/ai_planning_subtree/look_for_adult, - ) + behavior_tree_json = "code/modules/mob/living/basic/lavaland/gutlunchers/gutlunch_baby.bt.json" /datum/ai_controller/basic_controller/gutlunch/gutlunch_milk blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, ) - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity/pacifist, - /datum/ai_planning_subtree/target_retaliate, - /datum/ai_planning_subtree/flee_target, - /datum/ai_planning_subtree/find_and_hunt_target/food_trough - ) - -///consume food! -/datum/ai_planning_subtree/find_and_hunt_target/food_trough - target_key = BB_TROUGH_TARGET - hunting_behavior = /datum/ai_behavior/hunt_target/interact_with_target/food_trough - finding_behavior = /datum/ai_behavior/find_hunt_target/food_trough - hunt_targets = list(/obj/structure/ore_container/food_trough/gutlunch_trough) - hunt_chance = 75 - hunt_range = 9 - - -/datum/ai_planning_subtree/find_and_hunt_target/food_trough/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(!controller.blackboard[BB_CHECK_HUNGRY]) - return - return ..() - -/datum/ai_behavior/find_hunt_target/food_trough - -/datum/ai_behavior/find_hunt_target/food_trough/valid_dinner(mob/living/basic/source, obj/target, radius) - if(isnull(target)) - return FALSE - - if(isnull(locate(/obj/item/stack/ore) in target)) - return FALSE - - return can_see(source, target, radius) - -/datum/ai_behavior/hunt_target/interact_with_target/food_trough - always_reset_target = TRUE - behavior_combat_mode = FALSE + behavior_tree_json = "code/modules/mob/living/basic/lavaland/gutlunchers/gutlunch_milk.bt.json" /datum/pet_command/mine_walls command_name = "Mine" @@ -121,10 +44,7 @@ return ..() /datum/pet_command/mine_walls/execute_action(datum/ai_controller/controller) - if(controller.blackboard_key_exists(BB_CURRENT_PET_TARGET)) - controller.queue_behavior(/datum/ai_behavior/mine_wall, BB_CURRENT_PET_TARGET) - return SUBTREE_RETURN_FINISH_PLANNING - controller.queue_behavior(/datum/ai_behavior/find_mineral_wall, BB_CURRENT_PET_TARGET) + controller.set_behavior_tree_override(SUBPLAN_ID_PET_COMMAND, /datum/bt_node/subtree/pet_command/mine_walls) /datum/pet_command/mine_walls/retrieve_command_text(atom/living_pet, atom/target) return "signals [living_pet] to start mining!" @@ -138,4 +58,53 @@ return FALSE return ..() +/// Interacts with the food trough to eat ore, then clears the hungry flag. +/datum/bt_node/ai_behavior/hunt_target/interact_with_target/food_trough + always_reset_target = TRUE + behavior_combat_mode = FALSE + +/datum/bt_node/ai_behavior/hunt_target/interact_with_target/food_trough/finish_action(datum/ai_controller/controller, succeeded) + . = ..() + if(succeeded) + controller.clear_blackboard_key(BB_CHECK_HUNGRY) + +///Find nearby ashwalkers. we love lizards. +/datum/bt_node/ai_behavior/befriend_ashwalkers + time_between_perform = 5 SECONDS + +/datum/bt_node/ai_behavior/befriend_ashwalkers/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/living_pawn = controller.pawn + for(var/mob/living/potential_friend in oview(9, living_pawn)) + if(!isashwalker(potential_friend) || living_pawn.has_ally(REF(potential_friend))) + continue + living_pawn.befriend(potential_friend) + to_chat(potential_friend, span_nicegreen("[living_pawn] looks at you with endearing eyes!")) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + +/// Searches for and moves to a parent mob (of types in BB_FIND_MOM_TYPES), sets BB_FOUND_MOM. +/datum/bt_node/ai_behavior/find_parent + var/mom_types_key + var/found_mom_key + +/datum/bt_node/ai_behavior/find_parent/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living_pawn = controller.pawn + var/list/mom_types = controller.blackboard[mom_types_key] + if(!length(mom_types)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + for(var/mob/mother in oview(7, living_pawn)) + if(!is_type_in_list(mother, mom_types)) + continue + controller.set_blackboard_key(found_mom_key, mother) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + +// + +/// Mine walls pet command subtree: find mineral wall -> move to it -> mine it -> clear command. +/datum/bt_node/subtree/pet_command/mine_walls + behavior_tree_json = "code/datums/ai/basic_mobs/pet_commands/pet_command_mine_walls.bt.json" + + #undef MAXIMUM_GUTLUNCH_POP diff --git a/code/modules/mob/living/basic/lavaland/hivelord/hivelord.bt.json b/code/modules/mob/living/basic/lavaland/hivelord/hivelord.bt.json new file mode 100644 index 00000000000..6a99b899d66 --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/hivelord/hivelord.bt.json @@ -0,0 +1,5 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/hivelord", + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_ability_ranged_combat" +} diff --git a/code/modules/mob/living/basic/lavaland/hivelord/hivelord.dm b/code/modules/mob/living/basic/lavaland/hivelord/hivelord.dm index 08599831a51..8a8d234ebec 100644 --- a/code/modules/mob/living/basic/lavaland/hivelord/hivelord.dm +++ b/code/modules/mob/living/basic/lavaland/hivelord/hivelord.dm @@ -60,7 +60,7 @@ /mob/living/basic/mining/hivelord/proc/complete_spawn(turf/spawn_turf) var/mob/living/brood = new death_spawn_type(spawn_turf) SET_FACTION_AND_ALLIES_FROM(brood, src) - brood.ai_controller?.set_blackboard_key(ai_controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET]) + brood.ai_controller?.set_blackboard_key(ai_controller.blackboard[BB_CURRENT_TARGET]) brood.dir = get_dir(src, spawn_turf) /mob/living/basic/mining/hivelord/RangedAttack(atom/atom_target, modifiers) diff --git a/code/modules/mob/living/basic/lavaland/hivelord/hivelord_ai.dm b/code/modules/mob/living/basic/lavaland/hivelord/hivelord_ai.dm index aecabc82373..afb54229538 100644 --- a/code/modules/mob/living/basic/lavaland/hivelord/hivelord_ai.dm +++ b/code/modules/mob/living/basic/lavaland/hivelord/hivelord_ai.dm @@ -1,15 +1,9 @@ /// Basically just keep away and shit out worms /datum/ai_controller/basic_controller/hivelord + behavior_tree_json = "code/modules/mob/living/basic/lavaland/hivelord/hivelord.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_AGGRO_RANGE = 5, // Only get mad at people nearby ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/maintain_distance, - /datum/ai_planning_subtree/targeted_mob_ability, - ) diff --git a/code/modules/mob/living/basic/lavaland/hivelord/spawn_hivelord_brood.dm b/code/modules/mob/living/basic/lavaland/hivelord/spawn_hivelord_brood.dm index 146d2fba82f..080a40b0e9c 100644 --- a/code/modules/mob/living/basic/lavaland/hivelord/spawn_hivelord_brood.dm +++ b/code/modules/mob/living/basic/lavaland/hivelord/spawn_hivelord_brood.dm @@ -11,7 +11,7 @@ melee_cooldown_time = 0 shared_cooldown = NONE /// If a mob is not clicked directly, inherit targeting data from this blackboard key and setting it upon this target key - var/ai_target_key = BB_BASIC_MOB_CURRENT_TARGET + var/ai_target_key = BB_CURRENT_TARGET /// What are we actually spawning? var/spawn_type = /mob/living/basic/hivelord_brood /// Do we automatically fire with no cooldown when damaged? diff --git a/code/modules/mob/living/basic/lavaland/legion/legion.bt.json b/code/modules/mob/living/basic/lavaland/legion/legion.bt.json new file mode 100644 index 00000000000..451fa812723 --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/legion/legion.bt.json @@ -0,0 +1,102 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/legion", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability", + "vars": { + "ability_key": "BB_TARGETED_ACTION", + "target_key": "BB_CURRENT_TARGET" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/maintain_distance", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/cooldown", + "vars": { + "cooldown_key": "BB_BASIC_MOB_REINFORCEMENTS_COOLDOWN", + "cooldown_duration": "30 SECONDS", + "lock_on_succeed": true + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/call_reinforcements/mining" + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "loop_delay": "1 SECONDS", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/random_speech/legion" + } + ] + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/lavaland/legion/legion_ai.dm b/code/modules/mob/living/basic/lavaland/legion/legion_ai.dm index 8d5082d4c08..438a1977a52 100644 --- a/code/modules/mob/living/basic/lavaland/legion/legion_ai.dm +++ b/code/modules/mob/living/basic/lavaland/legion/legion_ai.dm @@ -1,5 +1,6 @@ /// Keep away and launch skulls at every opportunity, prioritising injured allies /datum/ai_controller/basic_controller/legion + behavior_tree_json = "code/modules/mob/living/basic/lavaland/legion/legion.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/legion, BB_TARGET_PRIORITY_STRATEGY = /datum/target_priority_strategy/mining, @@ -8,35 +9,21 @@ BB_RANGED_SKIRMISH_MIN_DISTANCE = 4, BB_RANGED_SKIRMISH_MAX_DISTANCE = 6, ) - ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/call_reinforcements/mining, - /datum/ai_planning_subtree/random_speech/legion, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/maintain_distance, - /datum/ai_planning_subtree/targeted_mob_ability, - ) /// Chase and attack whatever we are targeting, if it's friendly we will heal them /datum/ai_controller/basic_controller/legion_brood + behavior_tree_json = "code/modules/mob/living/basic/lavaland/legion/legion_brood.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/legion, BB_TARGET_PRIORITY_STRATEGY = /datum/target_priority_strategy/mining/low_node_priority, BB_TARGET_MINIMUM_STAT = HARD_CRIT, ) - ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) /// Target nearby friendlies if they are hurt (and are not themselves Legions) /datum/targeting_strategy/basic/legion + custom_faction_check = TRUE /datum/targeting_strategy/basic/legion/faction_check(datum/ai_controller/controller, mob/living/living_mob, mob/living/the_target) if (!living_mob.faction_check_atom(the_target, exact_match = check_factions_exactly)) @@ -48,39 +35,3 @@ return TRUE return the_target.stat == DEAD || the_target.health >= the_target.maxHealth -/// Don't run away from friendlies -/datum/ai_planning_subtree/flee_target/legion - -/datum/ai_planning_subtree/flee_target/legion/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/mob/living/target = controller.blackboard[target_key] - if (QDELETED(target) || target.faction_check_atom(controller.pawn)) - return // Only flee if we have a hostile target - return ..() - -/// Make spooky sounds, if we have a corpse inside then impersonate them -/datum/ai_planning_subtree/random_speech/legion - speech_chance = 1 - speak = list("Come...", "Legion...", "Why...?") - emote_hear = list("groans.", "wails.", "whimpers.") - emote_see = list("twitches.", "shudders.") - /// Stuff to specifically say into a radio - var/list/radio_speech = list("Come...", "Why...?") - -/datum/ai_planning_subtree/random_speech/legion/speak(datum/ai_controller/controller) - var/mob/living/carbon/human/victim = controller.blackboard[BB_LEGION_CORPSE] - if (QDELETED(victim) || prob(30)) - return ..() - - if(HAS_MIND_TRAIT(victim, TRAIT_MIMING)) // mimes cant talk - return - - var/list/remembered_speech = controller.blackboard[BB_LEGION_RECENT_LINES] || list() - - if (length(remembered_speech) && prob(50)) // Don't spam the radio - controller.queue_behavior(/datum/ai_behavior/perform_speech, pick(remembered_speech)) - return - - var/obj/item/radio/mob_radio = locate() in victim.contents - if (QDELETED(mob_radio)) - return ..() // No radio, just talk funny - controller.queue_behavior(/datum/ai_behavior/perform_speech_radio, pick(radio_speech + remembered_speech), mob_radio, list(RADIO_CHANNEL_SUPPLY, RADIO_CHANNEL_COMMON)) diff --git a/code/modules/mob/living/basic/lavaland/legion/legion_brood.bt.json b/code/modules/mob/living/basic/lavaland/legion/legion_brood.bt.json new file mode 100644 index 00000000000..c75a7dcbbe1 --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/legion/legion_brood.bt.json @@ -0,0 +1,81 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/legion_brood", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/attack_obstructions", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] +} diff --git a/code/modules/mob/living/basic/lavaland/legion/legion_brood.dm b/code/modules/mob/living/basic/lavaland/legion/legion_brood.dm index dd2bbe18de2..ad58998fde4 100644 --- a/code/modules/mob/living/basic/lavaland/legion/legion_brood.dm +++ b/code/modules/mob/living/basic/lavaland/legion/legion_brood.dm @@ -112,7 +112,7 @@ return // Inherit our creator's target and reinforcement requests - ai_controller.set_blackboard_key(BB_BASIC_MOB_CURRENT_TARGET, creator.ai_controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET]) + ai_controller.set_blackboard_key(BB_CURRENT_TARGET, creator.ai_controller.blackboard[BB_CURRENT_TARGET]) ai_controller.set_blackboard_key(BB_MINING_MOB_REINFORCEMENTS_REQUESTS, creator.ai_controller.blackboard[BB_MINING_MOB_REINFORCEMENTS_REQUESTS]) /// Reference handling diff --git a/code/modules/mob/living/basic/lavaland/legion/legion_monkey.bt.json b/code/modules/mob/living/basic/lavaland/legion/legion_monkey.bt.json new file mode 100644 index 00000000000..6e8e16f2a27 --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/legion/legion_monkey.bt.json @@ -0,0 +1,119 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/legion_monkey", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability", + "vars": { + "ability_key": "BB_TARGETED_ACTION", + "target_key": "BB_CURRENT_TARGET" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/maintain_distance", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/cooldown", + "vars": { + "cooldown_key": "BB_BASIC_MOB_REINFORCEMENTS_COOLDOWN", + "cooldown_duration": "30 SECONDS", + "lock_on_succeed": true + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/call_reinforcements/mining" + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/consider_venting" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk", + "bindings": { + "bf07i8ep": "10" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_ENTRY_VENT_TARGET", + "target_source": "/datum/target_source/oview_single_type/vent_pump", + "targeting_strategy": "/datum/targeting_strategy/anything", + "vision_range": 7 + } + }, + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "loop_delay": "1 SECONDS", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/random_speech/legion" + } + ] + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/lavaland/legion/legion_monkey.dm b/code/modules/mob/living/basic/lavaland/legion/legion_monkey.dm index 1fc569e51f8..44b5b200928 100644 --- a/code/modules/mob/living/basic/lavaland/legion/legion_monkey.dm +++ b/code/modules/mob/living/basic/lavaland/legion/legion_monkey.dm @@ -42,6 +42,7 @@ /// Opportunistically hops in and out of vents, if it can find one and is not biting someone. /datum/ai_controller/basic_controller/legion_monkey + behavior_tree_json = "code/modules/mob/living/basic/lavaland/legion/legion_monkey.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_TARGET_MINIMUM_STAT = HARD_CRIT, @@ -51,14 +52,3 @@ ai_traits = DEFAULT_AI_FLAGS | STOP_MOVING_WHEN_PULLED ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk/less_walking - - // We understand that vents are nice little hidey holes through epigenetic inheritance, so we'll use them. - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/random_speech/legion, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/opportunistic_ventcrawler, - ) diff --git a/code/modules/mob/living/basic/lavaland/legion/spawn_legions.dm b/code/modules/mob/living/basic/lavaland/legion/spawn_legions.dm index 2f464fb0843..889d0347be4 100644 --- a/code/modules/mob/living/basic/lavaland/legion/spawn_legions.dm +++ b/code/modules/mob/living/basic/lavaland/legion/spawn_legions.dm @@ -11,7 +11,7 @@ melee_cooldown_time = 0 shared_cooldown = NONE /// If a mob is not clicked directly, inherit targeting data from this blackboard key and setting it upon this target key - var/ai_target_key = BB_BASIC_MOB_CURRENT_TARGET + var/ai_target_key = BB_CURRENT_TARGET /// What are we actually spawning? var/spawn_type = /mob/living/basic/mining/legion_brood /// How far can we fire? diff --git a/code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity.bt.json b/code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity.bt.json new file mode 100644 index 00000000000..bc32d519c2b --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity.bt.json @@ -0,0 +1,131 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/lobstrosity", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "subtree", + "subtype": "", + "override_id": "SUBPLAN_ID_PET_COMMAND" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/run_away_from_target" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability", + "vars": { + "ability_key": "BB_TARGETED_ACTION", + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/attack_obstructions", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/cooldown", + "vars": { + "cooldown_key": "BB_BASIC_MOB_REINFORCEMENTS_COOLDOWN", + "cooldown_duration": "30 SECONDS", + "lock_on_succeed": true + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/call_reinforcements/mining" + } + } + ] + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop" + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity.dm b/code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity.dm index ecf69e02a9a..fe66b6cb247 100644 --- a/code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity.dm +++ b/code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity.dm @@ -270,16 +270,14 @@ command_feedback = "growl" pointed_reaction = "and growls" pet_ability_key = BB_TARGETED_ACTION - ability_behavior = /datum/ai_behavior/pet_use_ability/then_attack/long_ranged /datum/pet_command/use_ability/lob_charge/set_command_target(mob/living/parent, atom/target) if (!target) return FALSE var/datum/targeting_strategy/targeter = GET_TARGETING_STRATEGY(parent.ai_controller.blackboard[targeting_strategy_key]) - if(!targeter?.can_attack(parent, target)) + if(!targeter?.is_valid_target(parent, target)) parent.balloon_alert_to_viewers("shakes head!") return FALSE return ..() /datum/pet_command/use_ability/lob_charge/shrimp - ability_behavior = /datum/ai_behavior/pet_use_ability/then_attack/short_ranged diff --git a/code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity_ai.dm b/code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity_ai.dm index bd9b2addff6..b922843b0ee 100644 --- a/code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity_ai.dm +++ b/code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity_ai.dm @@ -19,25 +19,15 @@ BB_ONLY_FISH_WHILE_HUNGRY = TRUE, BB_TARGET_PRIORITY_TRAIT = TRAIT_SCARY_FISHERMAN, BB_OWNER_SELF_HARM_RESPONSES = SHRIMP_HARM_RESPONSES, + BB_BASIC_MOB_SPEAK_LINES = list( + BB_EMOTE_HEAR = list("chitters."), + BB_EMOTE_SOUND = list('sound/mobs/non-humanoids/insect/chitter.ogg'), + BB_SPEAK_CHANCE = 5, + ), ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/call_reinforcements/mining, - /datum/ai_planning_subtree/random_speech/insect, - /datum/ai_planning_subtree/hoard_fingers, - /datum/ai_planning_subtree/pet_planning, - /datum/ai_planning_subtree/find_target_prioritize_traits, - /datum/ai_planning_subtree/targeted_mob_ability/lobster, - /datum/ai_planning_subtree/flee_target/lobster, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/basic_melee_attack_subtree/lobster, - /datum/ai_planning_subtree/find_food, - /datum/ai_planning_subtree/fish/fish_from_turfs, - /datum/ai_planning_subtree/find_fingers, - ) + behavior_tree_json = "code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity.bt.json" /datum/ai_controller/basic_controller/lobstrosity/TryPossessPawn(atom/new_pawn) . = ..() @@ -58,282 +48,19 @@ BB_BASIC_MOB_FLEE_DISTANCE = 4, BB_TARGET_PRIORITY_TRAIT = TRAIT_SCARY_FISHERMAN, BB_OWNER_SELF_HARM_RESPONSES = SHRIMP_HARM_RESPONSES, - ) - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/random_speech/insect, - /datum/ai_planning_subtree/hoard_fingers, - /datum/ai_planning_subtree/pet_planning, - /datum/ai_planning_subtree/find_target_prioritize_traits, - /datum/ai_planning_subtree/targeted_mob_ability/lobster, - /datum/ai_planning_subtree/flee_target/lobster, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/basic_melee_attack_subtree/lobster, - /datum/ai_planning_subtree/find_food, - /datum/ai_planning_subtree/fish/fish_from_turfs, - /datum/ai_planning_subtree/find_fingers, + BB_BASIC_MOB_SPEAK_LINES = list( + BB_EMOTE_HEAR = list("chitters."), + BB_EMOTE_SOUND = list('sound/mobs/non-humanoids/insect/chitter.ogg'), + BB_SPEAK_CHANCE = 5, + ), ) ///A subtype of juvenile lobster AI that has the target_retaliate behaviour instead of simple_find_target /datum/ai_controller/basic_controller/lobstrosity/juvenile/calm - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity/pacifist, - /datum/ai_planning_subtree/random_speech/insect, - /datum/ai_planning_subtree/hoard_fingers, - /datum/ai_planning_subtree/pet_planning, - /datum/ai_planning_subtree/target_retaliate, - /datum/ai_planning_subtree/targeted_mob_ability/lobster/juvenile, - /datum/ai_planning_subtree/flee_target/lobster, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/basic_melee_attack_subtree/lobster, - /datum/ai_planning_subtree/find_food, - /datum/ai_planning_subtree/fish/fish_from_turfs, - /datum/ai_planning_subtree/find_fingers, - ) + behavior_tree_json = "code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity_calm.bt.json" ///A subtype of juvenile lobster AI that has the capricious_retaliate behaviour instead of simple_find_target /datum/ai_controller/basic_controller/lobstrosity/juvenile/capricious - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/random_speech/insect, - /datum/ai_planning_subtree/hoard_fingers, - /datum/ai_planning_subtree/pet_planning, - /datum/ai_planning_subtree/capricious_retaliate, - /datum/ai_planning_subtree/targeted_mob_ability/lobster/juvenile, - /datum/ai_planning_subtree/flee_target/lobster, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/basic_melee_attack_subtree/lobster, - /datum/ai_planning_subtree/find_food, - /datum/ai_planning_subtree/fish/fish_from_turfs, - /datum/ai_planning_subtree/find_fingers, - ) + behavior_tree_json = "code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity_capricious.bt.json" -/datum/ai_planning_subtree/basic_melee_attack_subtree/lobster - melee_attack_behavior = /datum/ai_behavior/basic_melee_attack/lobster - -/datum/ai_planning_subtree/basic_melee_attack_subtree/lobster/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/atom/movable/target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] - if(!isliving(target)) - return ..() - if (!controller.blackboard[BB_BASIC_MOB_STOP_FLEEING]) - return - if (!isnull(controller.blackboard[BB_LOBSTROSITY_TARGET_LIMB])) - return - if(controller.blackboard[BB_LOBSTROSITY_NAIVE_HUNTER] && HAS_TRAIT(target, TRAIT_SCARY_FISHERMAN)) - return //juvenile lobstrosities fear me. - var/mob/living/living_pawn = controller.pawn - if (DOING_INTERACTION_WITH_TARGET(living_pawn, target)) - return - return ..() - -/datum/ai_behavior/basic_melee_attack/lobster - -/datum/ai_behavior/basic_melee_attack/lobster/perform(seconds_per_tick, datum/ai_controller/controller, target_key, targeting_strategy_key, hiding_location_key) - var/mob/living/target = controller.blackboard[target_key] - if (isnull(target) || !istype(target)) - return ..() - var/is_vulnerable = FALSE - if(controller.blackboard[BB_LOBSTROSITY_NAIVE_HUNTER]) - if(HAS_TRAIT(target, TRAIT_SCARY_FISHERMAN)) - //Trigger lobstrosity PTSD. Don't clear the target so we can run away. - controller.set_blackboard_key(BB_BASIC_MOB_STOP_FLEEING, FALSE) - return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED - if(target.body_position == LYING_DOWN) - is_vulnerable = TRUE - if(!is_vulnerable) - for (var/trait in controller.blackboard[BB_LOBSTROSITY_EXPLOIT_TRAITS]) - if (!HAS_TRAIT(target, trait)) - continue - is_vulnerable = TRUE - break - if (!is_vulnerable) - controller.set_blackboard_key(BB_BASIC_MOB_STOP_FLEEING, FALSE) - if (!controller.blackboard[BB_BASIC_MOB_STOP_FLEEING]) - // We don't want to clear our target - return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED - return ..() - -/datum/ai_planning_subtree/flee_target/lobster - flee_behaviour = /datum/ai_behavior/run_away_from_target/lobster - -/datum/ai_planning_subtree/flee_target/lobster/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/atom/movable/target = controller.blackboard[target_key] - if(!QDELETED(target) && controller.blackboard[BB_LOBSTROSITY_NAIVE_HUNTER] && HAS_TRAIT(target, TRAIT_SCARY_FISHERMAN)) - return ..() - var/datum/action/cooldown/using_action = controller.blackboard[BB_TARGETED_ACTION] - if (using_action?.IsAvailable()) - return FALSE - return ..() - -/datum/ai_behavior/run_away_from_target/lobster - clear_failed_targets = FALSE - -/datum/ai_behavior/run_away_from_target/lobster/perform(seconds_per_tick, datum/ai_controller/controller, target_key, hiding_location_key) - var/atom/target = controller.blackboard[target_key] - if(QDELETED(target)) - return ..() - - var/is_naive = controller.blackboard[BB_LOBSTROSITY_NAIVE_HUNTER] - var/is_scary = HAS_TRAIT(target, TRAIT_SCARY_FISHERMAN) - - if(!is_naive || !is_scary) //the lobstrosity isn't naive/young and the target isn't a scary fisherman. - if(isliving(target)) - var/mob/living/living_target = target - if(is_naive && living_target.body_position == LYING_DOWN) - controller.set_blackboard_key(BB_BASIC_MOB_STOP_FLEEING, TRUE) - return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED - for (var/trait in controller.blackboard[BB_LOBSTROSITY_EXPLOIT_TRAITS]) - if (!HAS_TRAIT(target, trait)) - continue - controller.set_blackboard_key(BB_BASIC_MOB_STOP_FLEEING, TRUE) - return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED - - var/mob/living/us = controller.pawn - if (us.pulling == target) - us.stop_pulling() // If we're running away from someone, best not to bring them with us - - return ..() - -/// Don't use charge ability on an adjacent target, and make sure you're visible before you start -/datum/ai_planning_subtree/targeted_mob_ability/lobster - use_ability_behaviour = /datum/ai_behavior/targeted_mob_ability/min_range - -/datum/ai_planning_subtree/targeted_mob_ability/lobster/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/atom/target = controller.blackboard[target_key] - if(QDELETED(target) || in_range(controller.pawn, target)) - return - if(controller.blackboard[BB_LOBSTROSITY_NAIVE_HUNTER] && HAS_TRAIT(target, TRAIT_SCARY_FISHERMAN)) - return - return ..() - - -/datum/ai_planning_subtree/targeted_mob_ability/lobster/juvenile - use_ability_behaviour = /datum/ai_behavior/targeted_mob_ability/min_range/short - -/// Look for loose arms lying around -/datum/ai_planning_subtree/find_fingers - /// Where do we store target limb data? - var/target_key = BB_LOBSTROSITY_TARGET_LIMB - /// What are we actually looking for? - var/desired_type = /obj/item/bodypart/arm - -/datum/ai_planning_subtree/find_fingers/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - . = ..() - controller.queue_behavior(/datum/ai_behavior/find_and_set, target_key, desired_type) - -/// If you see an arm, grab it and run -/datum/ai_planning_subtree/hoard_fingers - /// Where do we store target limb data? - var/target_key = BB_LOBSTROSITY_TARGET_LIMB - -/datum/ai_planning_subtree/hoard_fingers/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - . = ..() - var/atom/current_target = controller.blackboard[target_key] - if (QDELETED(current_target)) - return - - var/mob/living/living_pawn = controller.pawn - if (living_pawn.pulling != current_target) - controller.queue_behavior(/datum/ai_behavior/grab_fingers, target_key) - else - controller.queue_behavior(/datum/ai_behavior/hoard_fingers, target_key) - return SUBTREE_RETURN_FINISH_PLANNING - -/// If our target is an arm then move over and drag it -/datum/ai_behavior/grab_fingers - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION - -/datum/ai_behavior/grab_fingers/setup(datum/ai_controller/controller, target_key) - . = ..() - var/atom/current_target = controller.blackboard[target_key] - if (QDELETED(current_target)) - return FALSE - set_movement_target(controller, current_target) - -/datum/ai_behavior/grab_fingers/perform(seconds_per_tick, datum/ai_controller/controller, target_key) - var/atom/current_target = controller.blackboard[target_key] - if (QDELETED(current_target)) - return AI_BEHAVIOR_DELAY - var/mob/living/living_pawn = controller.pawn - living_pawn.start_pulling(current_target) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -/// How far we'll try to go before eating an arm -#define FLEE_TO_RANGE 9 -/// How many times we'll attempt to move before giving up -#define MAX_LOBSTROSITY_PATIENCE 15 - -/// If we are dragging an arm then run away until we are out of range and feast -/datum/ai_behavior/hoard_fingers - required_distance = 0 - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION - /// We store a counter at this key we increment on every movement until we are overwhelmed with hunger - var/patience_key = BB_LOBSTROSITY_FINGER_LUST - -/datum/ai_behavior/hoard_fingers/setup(datum/ai_controller/controller, target_key) - . = ..() - var/atom/current_target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] - if (QDELETED(current_target)) - set_movement_target(controller, get_turf(controller.pawn)) - return - var/perform_flags = target_step_away(controller, current_target, target_key) - if (perform_flags & AI_BEHAVIOR_SUCCEEDED) - finish_action(controller, TRUE, target_key) - else if(perform_flags & AI_BEHAVIOR_FAILED) - finish_action(controller, FALSE, target_key) - -/// Find the next step to take away from the current target -/datum/ai_behavior/hoard_fingers/proc/target_step_away(datum/ai_controller/controller, atom/current_target, target_key) - var/turf/next_step = get_step_away(controller.pawn, current_target) - if (!isnull(next_step) && !next_step.is_blocked_turf(exclude_mobs = TRUE)) - set_movement_target(controller, next_step) - return NONE - var/list/all_dirs = GLOB.alldirs.Copy() - all_dirs -= get_dir(controller.pawn, next_step) - all_dirs -= get_dir(controller.pawn, current_target) - shuffle_inplace(all_dirs) - for (var/dir in all_dirs) - next_step = get_step(controller.pawn, dir) - if (!isnull(next_step) && !next_step.is_blocked_turf(exclude_mobs = TRUE)) - set_movement_target(controller, next_step) - return NONE - return AI_BEHAVIOR_FAILED - -/datum/ai_behavior/hoard_fingers/perform(seconds_per_tick, datum/ai_controller/controller, target_key) - var/current_patience = controller.blackboard[patience_key] + 1 - if (current_patience >= MAX_LOBSTROSITY_PATIENCE) - if(eat_fingers(controller, target_key)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - controller.set_blackboard_key(patience_key, current_patience) - var/mob/living/living_pawn = controller.pawn - if (isnull(living_pawn.pulling)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - var/atom/current_target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] - if (QDELETED(current_target) || !can_see(controller.pawn, current_target, FLEE_TO_RANGE)) - if(eat_fingers(controller, target_key)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - if(target_step_away(controller, current_target, target_key)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - -/// Finally consume those delicious digits -/datum/ai_behavior/hoard_fingers/proc/eat_fingers(datum/ai_controller/controller, target_key) - var/mob/living/basic/living_pawn = controller.pawn - var/atom/fingers = controller.blackboard[target_key] - if (QDELETED(fingers) || living_pawn.pulling != fingers) - return AI_BEHAVIOR_FAILED - controller.ai_interact(target = fingers) - return AI_BEHAVIOR_SUCCEEDED - -/datum/ai_behavior/hoard_fingers/finish_action(datum/ai_controller/controller, succeeded, target_key) - . = ..() - controller.set_blackboard_key(patience_key, 0) - controller.clear_blackboard_key(target_key) - controller.clear_blackboard_key(BB_BASIC_MOB_CURRENT_TARGET) - -#undef FLEE_TO_RANGE -#undef MAX_LOBSTROSITY_PATIENCE #undef SHRIMP_HARM_RESPONSES diff --git a/code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity_calm.bt.json b/code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity_calm.bt.json new file mode 100644 index 00000000000..58447748038 --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity_calm.bt.json @@ -0,0 +1,19 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/lobstrosity/juvenile/calm", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity/pacifist" + }, + { + "type": "subtree", + "subtype": "", + "override_id": "SUBPLAN_ID_PET_COMMAND" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_retaliate_combat" + } + ] +} diff --git a/code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity_capricious.bt.json b/code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity_capricious.bt.json new file mode 100644 index 00000000000..7f5bcc6f817 --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity_capricious.bt.json @@ -0,0 +1,19 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/lobstrosity/juvenile/capricious", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "subtree", + "subtype": "", + "override_id": "SUBPLAN_ID_PET_COMMAND" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_capricious_combat" + } + ] +} diff --git a/code/modules/mob/living/basic/lavaland/mook/bard.bt.json b/code/modules/mob/living/basic/lavaland/mook/bard.bt.json new file mode 100644 index 00000000000..9ea04b0d887 --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/mook/bard.bt.json @@ -0,0 +1,9 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/mook/bard", + "type": "subtree", + "subtype": "/datum/bt_node/subtree/generic_mook_behavior", + "bindings": { + "b1kiiayf": "/datum/bt_node/subtree/bard_play_music", + "b0ymbjo1": "/datum/bt_node/subtree/bard_find_targets" + } +} diff --git a/code/modules/mob/living/basic/lavaland/mook/bard_find_targets.bt.json b/code/modules/mob/living/basic/lavaland/mook/bard_find_targets.bt.json new file mode 100644 index 00000000000..d1fa50af808 --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/mook/bard_find_targets.bt.json @@ -0,0 +1,56 @@ +{ + "dm_type": "/datum/bt_node/subtree/bard_find_targets", + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "invert": true, + "key": "BB_MOOK_TRIBAL_CHIEF" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_MOOK_TRIBAL_CHIEF", + "target_source": "/datum/target_source/oview_single_type/tribal_chief", + "targeting_strategy": "/datum/targeting_strategy/anything" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/befriend_target", + "vars": { + "target_key": "BB_MOOK_TRIBAL_CHIEF", + "long_range_friendship": true, + "forget_target": false + } + } + ] + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_MOOK_MUSIC_AUDIENCE", + "target_source": "/datum/target_source/near_village_humans", + "targeting_strategy": "/datum/targeting_strategy/conscious_human", + "time_between_perform": "1 SECONDS" + } + } + ] +} diff --git a/code/modules/mob/living/basic/lavaland/mook/bard_play_music.bt.json b/code/modules/mob/living/basic/lavaland/mook/bard_play_music.bt.json new file mode 100644 index 00000000000..b29f041e5e8 --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/mook/bard_play_music.bt.json @@ -0,0 +1,55 @@ +{ + "dm_type": "/datum/bt_node/subtree/bard_play_music", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/generic_play_instrument", + "bindings": { + "bulu13xf": "75" + } + }, + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "key": "BB_MOOK_MUSIC_AUDIENCE" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_MOOK_MUSIC_AUDIENCE", + "required_dist": 2, + "finish_on_arrival": false + } + } + }, + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_FAIL_ON_FAILURE", + "loop_delay": "1 SECONDS", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/idle_random_walk/walk_near_target", + "vars": { + "target_key": "BB_HOME_VILLAGE", + "walk_chance": 75 + } + } + ] + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/lavaland/mook/chief_find_targets.bt.json b/code/modules/mob/living/basic/lavaland/mook/chief_find_targets.bt.json new file mode 100644 index 00000000000..07346360c17 --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/mook/chief_find_targets.bt.json @@ -0,0 +1,68 @@ +{ + "dm_type": "/datum/bt_node/subtree/chief_find_targets", + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_SIMPLE_CARRY_ITEM" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/find_deposit_position" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "invert": true, + "key": "BB_SIMPLE_CARRY_ITEM" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/find_ore", + "vars": { + "time_between_perform": "1 SECONDS", + "range": 1 + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "invert": true, + "key": "BB_MATERIAL_STAND_TARGET" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_MATERIAL_STAND_TARGET", + "target_source": "/datum/target_source/oview_single_type/ore_stand", + "targeting_strategy": "/datum/targeting_strategy/anything" + } + } + } + ] +} diff --git a/code/modules/mob/living/basic/lavaland/mook/chief_issue_commands.bt.json b/code/modules/mob/living/basic/lavaland/mook/chief_issue_commands.bt.json new file mode 100644 index 00000000000..2f786c350ea --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/mook/chief_issue_commands.bt.json @@ -0,0 +1,53 @@ +{ + "dm_type": "/datum/bt_node/subtree/chief_issue_commands", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/cooldown", + "vars": { + "cooldown_key": "BB_COMMAND_COOLDOWN", + "cooldown_duration": "10 SECONDS", + "lock_on_succeed": true + }, + "child": { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/issue_pet_command", + "vars": { + "command_list_key": "BB_MOOK_COMMANDS", + "command_type": "/datum/pet_command/attack", + "target_key": "BB_CURRENT_TARGET", + "commandable_mob_type": "/mob/living/basic/mining/mook", + "command_distance": 7 + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_ORE_TARGET" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/issue_pet_command", + "vars": { + "command_list_key": "BB_MOOK_COMMANDS", + "command_type": "/datum/pet_command/fetch", + "target_key": "BB_ORE_TARGET", + "commandable_mob_type": "/mob/living/basic/mining/mook", + "command_distance": 7 + } + } + } + ] + } +} diff --git a/code/modules/mob/living/basic/lavaland/mook/chief_manage_village.bt.json b/code/modules/mob/living/basic/lavaland/mook/chief_manage_village.bt.json new file mode 100644 index 00000000000..f77afce86f4 --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/mook/chief_manage_village.bt.json @@ -0,0 +1,150 @@ +{ + "dm_type": "/datum/bt_node/subtree/chief_manage_village", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "1 SECONDS", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_SIMPLE_CARRY_ITEM" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "key": "BB_DEPOSIT_POSITION" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_DEPOSIT_POSITION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/clear_key", + "vars": { + "key": "BB_DEPOSIT_POSITION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/ai_interact", + "vars": { + "target_key": "BB_MATERIAL_STAND_TARGET" + } + } + ] + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "invert": false, + "key": "BB_MOOK_BONFIRE_TARGET" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_MOOK_BONFIRE_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/hunt_target/interact_with_target", + "vars": { + "target_key": "BB_MOOK_BONFIRE_TARGET", + "always_reset_target": true, + "behavior_combat_mode": false + } + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "invert": true, + "key": "BB_SIMPLE_CARRY_ITEM" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "invert": false, + "key": "BB_ORE_TARGET" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_ORE_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/pick_up_item_virtual", + "vars": { + "target_key": "BB_ORE_TARGET", + "storage_key": "BB_SIMPLE_CARRY_ITEM" + } + } + ] + } + } + }, + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_FAIL_ON_FAILURE", + "loop_delay": "1 SECONDS", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/idle_random_walk/walk_near_target", + "vars": { + "target_key": "BB_HOME_VILLAGE", + "walk_chance": 75 + } + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_MOOK_BONFIRE_TARGET", + "target_source": "/datum/target_source/oview_typed/from_bb_key/bonfire_targets", + "targeting_strategy": "/datum/targeting_strategy/unlit_bonfire" + } + } + ] +} diff --git a/code/modules/mob/living/basic/lavaland/mook/generic_mook_behavior.bt.json b/code/modules/mob/living/basic/lavaland/mook/generic_mook_behavior.bt.json new file mode 100644 index 00000000000..ceb511fd5b2 --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/mook/generic_mook_behavior.bt.json @@ -0,0 +1,178 @@ +{ + "dm_type": "/datum/bt_node/subtree/generic_mook_behavior", + "bindings": { + "b1kiiayf": { + "label": "Idle Behavior", + "default": "/datum/bt_node/subtree" + }, + "b0ymbjo1": { + "label": "Targetting Behavior", + "default": "/datum/bt_node/subtree" + }, + "bk7ixti3": { + "label": "High Priority Behavior", + "default": "/datum/bt_node/subtree" + } + }, + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "invert": true, + "key": "BB_HOME_VILLAGE" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/find_village" + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree", + "override_id": "SUBPLAN_ID_PET_COMMAND" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/pawn_same_z_as_key", + "vars": { + "key": "BB_HOME_VILLAGE" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/pawn_farther_than_from_key", + "vars": { + "anchor_key": "BB_HOME_VILLAGE", + "distance_key": "BB_MAXIMUM_DISTANCE_TO_VILLAGE" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/mook_has_flee_reason", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/clear_key", + "vars": { + "key": "BB_INJURED_MOOK" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/use_mob_ability", + "vars": { + "ability_key": "BB_MOOK_JUMP_ABILITY" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/clear_key", + "vars": { + "key": "BB_TARGET_MINERAL_WALL" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/clear_key", + "vars": { + "key": "BB_MOOK_MUSIC_AUDIENCE" + } + } + ] + } + } + } + }, + { + "type": "subtree", + "subtype": "$bk7ixti3" + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability", + "vars": { + "ability_key": "BB_MOOK_LEAP_ABILITY", + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "$b1kiiayf" + } + ] + }, + { + "type": "subtree", + "subtype": "$b0ymbjo1" + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/lavaland/mook/go_mining.bt.json b/code/modules/mob/living/basic/lavaland/mook/go_mining.bt.json new file mode 100644 index 00000000000..df73c3e1a16 --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/mook/go_mining.bt.json @@ -0,0 +1,213 @@ +{ + "dm_type": "/datum/bt_node/subtree/go_mining", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "1 SECONDS", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_SIMPLE_CARRY_ITEM" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "key": "BB_DEPOSIT_POSITION" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_DEPOSIT_POSITION", + "required_dist": 0 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/clear_key", + "vars": { + "key": "BB_DEPOSIT_POSITION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/ai_interact", + "vars": { + "target_key": "BB_MATERIAL_STAND_TARGET" + } + } + ] + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "invert": true, + "key": "BB_SIMPLE_CARRY_ITEM" + }, + "child": { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "invert": false, + "key": "BB_ORE_TARGET" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_ORE_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/pick_up_item_virtual", + "vars": { + "target_key": "BB_ORE_TARGET", + "storage_key": "BB_SIMPLE_CARRY_ITEM" + } + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "invert": false, + "key": "BB_TARGET_MINERAL_WALL" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_TARGET_MINERAL_WALL" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/mine_wall", + "vars": { + "target_key": "BB_TARGET_MINERAL_WALL", + "time_between_perform": "1 SECONDS" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/set_bb_cooldown", + "vars": { + "cooldown_key": "BB_MOOK_MINING_COOLDOWN", + "cooldown_duration": "10 SECONDS" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/find_ore", + "vars": { + "time_between_perform": "0.5 SECONDS" + } + } + ] + } + }, + { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/calculate_wander_destination" + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_WANDER_DESTINATION", + "required_dist": 0 + } + } + ] + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + } + } + ] + }, + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_SIMPLE_CARRY_ITEM" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/find_deposit_position" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "invert": true, + "key": "BB_SIMPLE_CARRY_ITEM" + }, + "child": { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/find_ore" + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/key_off_cooldown", + "vars": { + "cooldown_key": "BB_MOOK_MINING_COOLDOWN" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/find_mineral_wall/mook", + "vars": { + "target_key": "BB_TARGET_MINERAL_WALL" + } + } + } + ] + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/lavaland/mook/heal_injured.bt.json b/code/modules/mob/living/basic/lavaland/mook/heal_injured.bt.json new file mode 100644 index 00000000000..dd6d274c634 --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/mook/heal_injured.bt.json @@ -0,0 +1,42 @@ +{ + "dm_type": "/datum/bt_node/subtree/heal_injured", + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_INJURED_MOOK" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_INJURED_MOOK" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/hunt_target/interact_with_target", + "vars": { + "always_reset_target": true, + "target_key": "BB_INJURED_MOOK" + } + } + ] + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/idle_random_walk/walk_near_target", + "vars": { + "target_key": "BB_HOME_VILLAGE", + "walk_chance": 75 + } + } + ] +} diff --git a/code/modules/mob/living/basic/lavaland/mook/mook.bt.json b/code/modules/mob/living/basic/lavaland/mook/mook.bt.json new file mode 100644 index 00000000000..a129f8b83d5 --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/mook/mook.bt.json @@ -0,0 +1,9 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/mook", + "type": "subtree", + "subtype": "/datum/bt_node/subtree/generic_mook_behavior", + "bindings": { + "b0ymbjo1": "/datum/bt_node/subtree/worker_find_targets", + "b1kiiayf": "/datum/bt_node/subtree/go_mining" + } +} diff --git a/code/modules/mob/living/basic/lavaland/mook/mook.dm b/code/modules/mob/living/basic/lavaland/mook/mook.dm index 966c1bfba18..2ecc54895ef 100644 --- a/code/modules/mob/living/basic/lavaland/mook/mook.dm +++ b/code/modules/mob/living/basic/lavaland/mook/mook.dm @@ -44,6 +44,8 @@ /datum/pet_command/attack, /datum/pet_command/fetch, ) + /// Things we want to find to heal + var/static/list/heal_targets = list(/mob/living/basic/mining/mook/worker) /mob/living/basic/mining/mook/Initialize(mapload) . = ..() @@ -64,6 +66,8 @@ grant_healer_abilities() AddComponent(/datum/component/obeys_commands, pet_commands) + ai_controller?.set_blackboard_key(BB_MOOK_HEAL_TARGETS, typecacheof(heal_targets)) + /// Returns a list of actions and blackboard keys to pass into `grant_actions_by_list`. /mob/living/basic/mining/mook/proc/get_innate_abilities() @@ -84,6 +88,7 @@ /mob/living/basic/mining/mook/Entered(atom/movable/mover) if(istype(mover, /obj/item/stack/ore)) held_ore = mover + ai_controller?.set_blackboard_key(BB_SIMPLE_CARRY_ITEM, mover) update_appearance(UPDATE_OVERLAYS) return ..() @@ -187,7 +192,7 @@ if(istype(intruder, /mob/living/basic/mining/mook)) return for(var/mob/living/basic/mining/mook/villager in oview(src, 9)) - villager.ai_controller?.set_blackboard_key_assoc(BB_BASIC_MOB_RETALIATE_LIST, intruder, world.time) + villager.ai_controller?.set_blackboard_key_assoc_lazylist(BB_BASIC_MOB_RETALIATE_LIST, intruder, world.time) /mob/living/basic/mining/mook/worker @@ -276,10 +281,12 @@ var/static/mutable_appearance/chief_active = mutable_appearance('icons/mob/simple/jungle/mook.dmi', "mook_chief_leap") ///overlay in our warmup state var/static/mutable_appearance/chief_warmup = mutable_appearance('icons/mob/simple/jungle/mook.dmi', "mook_chief_warmup") + var/static/list/bonfire_targets = list(/obj/structure/bonfire) /mob/living/basic/mining/mook/worker/tribal_chief/Initialize(mapload) . = ..() update_appearance() + ai_controller?.set_blackboard_key(BB_BONFIRE_TARGETS, typecacheof(bonfire_targets)) /mob/living/basic/mining/mook/worker/tribal_chief/update_overlays() . = ..() diff --git a/code/modules/mob/living/basic/lavaland/mook/mook_ai.dm b/code/modules/mob/living/basic/lavaland/mook/mook_ai.dm index 92f6156a705..d3d20f1802b 100644 --- a/code/modules/mob/living/basic/lavaland/mook/mook_ai.dm +++ b/code/modules/mob/living/basic/lavaland/mook/mook_ai.dm @@ -5,6 +5,7 @@ GLOBAL_LIST_INIT(mook_commands, list( )) /datum/ai_controller/basic_controller/mook + behavior_tree_json = "code/modules/mob/living/basic/lavaland/mook/mook.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/mook, BB_BLACKLIST_MINERAL_TURFS = list(/turf/closed/mineral/gibtonite, /turf/closed/mineral/strong), @@ -13,21 +14,11 @@ GLOBAL_LIST_INIT(mook_commands, list( ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/target_retaliate, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/look_for_village, - /datum/ai_planning_subtree/targeted_mob_ability/leap, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/find_and_hunt_target/material_stand, - /datum/ai_planning_subtree/use_mob_ability/mook_jump, - /datum/ai_planning_subtree/find_and_hunt_target/hunt_ores/mook, - /datum/ai_planning_subtree/mine_walls/mook, - /datum/ai_planning_subtree/wander_away_from_village, - ) - can_idle = FALSE // these guys are intended to operate even if nobody's around + // these guys are intended to operate even if nobody's around + ai_traits = DEFAULT_AI_FLAGS | RUN_WHILE_UNWATCHED + +/datum/targeting_strategy/basic/mook + custom_faction_check = TRUE ///check for faction if not a ash walker, otherwise just attack /datum/targeting_strategy/basic/mook/faction_check(datum/ai_controller/controller, mob/living/living_mob, mob/living/the_target) @@ -36,389 +27,35 @@ GLOBAL_LIST_INIT(mook_commands, list( return ..() -/datum/ai_planning_subtree/targeted_mob_ability/leap - ability_key = BB_MOOK_LEAP_ABILITY - -/datum/ai_planning_subtree/use_mob_ability/mook_jump - ability_key = BB_MOOK_JUMP_ABILITY - -///jump towards the village when we have found ore or there is a storm coming -/datum/ai_planning_subtree/use_mob_ability/mook_jump/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/storm_approaching = controller.blackboard[BB_STORM_APPROACHING] - var/mob/living/living_pawn = controller.pawn - var/obj/effect/home = controller.blackboard[BB_HOME_VILLAGE] - if(QDELETED(home)) - return - if(get_dist(living_pawn, home) < controller.blackboard[BB_MAXIMUM_DISTANCE_TO_VILLAGE]) - return - if(home.z != living_pawn.z) - return - if(!storm_approaching && !(locate(/obj/item/stack/ore) in living_pawn)) - return - - controller.clear_blackboard_key(BB_TARGET_MINERAL_WALL) - return ..() - -///hunt ores that we will haul off back to the village -/datum/ai_planning_subtree/find_and_hunt_target/hunt_ores/mook - -/datum/ai_planning_subtree/find_and_hunt_target/hunt_ores/mook/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/mob/living/living_pawn = controller.pawn - if(locate(/obj/item/stack/ore) in living_pawn) - return - return ..() - -///deposit ores into the stand! -/datum/ai_planning_subtree/find_and_hunt_target/material_stand - target_key = BB_MATERIAL_STAND_TARGET - hunting_behavior = /datum/ai_behavior/hunt_target/interact_with_target/material_stand - finding_behavior = /datum/ai_behavior/find_hunt_target - hunt_targets = list(/obj/structure/ore_container/material_stand) - hunt_range = 9 - -/datum/ai_planning_subtree/find_and_hunt_target/material_stand/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/mob/living/living_pawn = controller.pawn - if(!locate(/obj/item/stack/ore) in living_pawn) - return - return ..() - -/datum/ai_behavior/hunt_target/interact_with_target/material_stand - required_distance = 0 - always_reset_target = TRUE - behavior_combat_mode = FALSE - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT - -///try to face the counter when depositing ores -/datum/ai_behavior/hunt_target/interact_with_target/material_stand/setup(datum/ai_controller/controller, hunting_target_key, hunting_cooldown_key) - . = ..() - var/atom/hunt_target = controller.blackboard[hunting_target_key] - if (QDELETED(hunt_target)) - return FALSE - var/list/possible_turfs = list() - var/list/directions = list(SOUTH, SOUTHEAST) - - for(var/direction in directions) - var/turf/bottom_turf = get_step(hunt_target, direction) - if(!bottom_turf.is_blocked_turf()) - possible_turfs += bottom_turf - - if(!length(possible_turfs)) - return FALSE - set_movement_target(controller, pick(possible_turfs)) - -///look for our village -/datum/ai_planning_subtree/look_for_village - -/datum/ai_planning_subtree/look_for_village/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(controller.blackboard_key_exists(BB_HOME_VILLAGE)) - return - - controller.queue_behavior(/datum/ai_behavior/find_village, BB_HOME_VILLAGE) - -/datum/ai_behavior/find_village - -/datum/ai_behavior/find_village/perform(seconds_per_tick, datum/ai_controller/controller, village_key) - - var/obj/effect/landmark/home_marker = locate(/obj/effect/landmark/mook_village) in GLOB.landmarks_list - if(isnull(home_marker)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - controller.set_blackboard_key(village_key, home_marker) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -///explore the lands away from the village to look for ore -/datum/ai_planning_subtree/wander_away_from_village - -/datum/ai_planning_subtree/wander_away_from_village/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/mob/living/living_pawn = controller.pawn - var/storm_approaching = controller.blackboard[BB_STORM_APPROACHING] - ///if we have ores to deposit or a storm is approaching, dont wander away - if(storm_approaching || (locate(/obj/item/stack/ore) in living_pawn)) - return - - if(controller.blackboard_key_exists(BB_HOME_VILLAGE)) - controller.queue_behavior(/datum/ai_behavior/wander, BB_HOME_VILLAGE) - -/datum/ai_behavior/wander - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION - required_distance = 0 - /// distance we will wander away from the village - var/wander_distance = 9 - -/datum/ai_behavior/wander/setup(datum/ai_controller/controller, village_key) - . = ..() - var/mob/living/living_pawn = controller.pawn - var/obj/effect/target = controller.blackboard[village_key] - if(QDELETED(target)) - return FALSE - - if(target.z != living_pawn.z) - return FALSE - - var/list/angle_directions = list() - for(var/direction in GLOB.alldirs) - angle_directions += dir2angle(direction) - - var/angle_to_home = get_angle(living_pawn, target) - angle_directions -= angle_to_home - angle_directions -= (angle_to_home + 45) - angle_directions -= (angle_to_home - 45) - shuffle_inplace(angle_directions) - - var/turf/wander_destination = get_turf(living_pawn) - for(var/angle in angle_directions) - var/turf/test_turf = get_furthest_turf(living_pawn, angle, target) - if(isnull(test_turf)) - continue - var/distance_from_target = get_dist(target, test_turf) - if(distance_from_target <= get_dist(target, wander_destination)) - continue - wander_destination = test_turf - if(distance_from_target == wander_distance) - break - - set_movement_target(controller, wander_destination) - -/datum/ai_behavior/wander/proc/get_furthest_turf(atom/source, angle, atom/target) - var/turf/return_turf - for(var/i in 1 to wander_distance) - var/turf/test_destination = get_ranged_target_turf_direct(source, target, range = i, offset = angle) - if(test_destination.is_blocked_turf(source_atom = source)) - break - return_turf = test_destination - return return_turf - -/datum/ai_behavior/wander/perform(seconds_per_tick, datum/ai_controller/controller, target_key, hiding_location_key) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -/datum/ai_planning_subtree/mine_walls/mook - find_wall_behavior = /datum/ai_behavior/find_mineral_wall/mook - -/datum/ai_planning_subtree/mine_walls/mook/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/mob/living/living_pawn = controller.pawn - var/storm_approaching = controller.blackboard[BB_STORM_APPROACHING] - if(storm_approaching || locate(/obj/item/stack/ore) in living_pawn) - return - return ..() - -/datum/ai_behavior/find_mineral_wall/mook - -/datum/ai_behavior/find_mineral_wall/mook/check_if_mineable(datum/ai_controller/controller, turf/target_wall) - var/list/forbidden_turfs = controller.blackboard[BB_BLACKLIST_MINERAL_TURFS] - if(is_type_in_list(target_wall, forbidden_turfs)) - return FALSE - return ..() - ///bard mook plays nice music for the village /datum/ai_controller/basic_controller/mook/bard + behavior_tree_json = "code/modules/mob/living/basic/lavaland/mook/bard.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/mook, BB_MAXIMUM_DISTANCE_TO_VILLAGE = 10, BB_STORM_APPROACHING = FALSE, BB_SONG_LINES = MOOK_SONG, + BB_INSTRUMENT_UP_ASS = TRUE, ) - idle_behavior = /datum/idle_behavior/walk_near_target/mook_village - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/target_retaliate, - /datum/ai_planning_subtree/look_for_village, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/play_music_for_visitor, - /datum/ai_planning_subtree/use_mob_ability/mook_jump, - /datum/ai_planning_subtree/generic_play_instrument, - ) - - -///find an audience to follow and play music for! -/datum/ai_planning_subtree/play_music_for_visitor - -/datum/ai_planning_subtree/play_music_for_visitor/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(!controller.blackboard_key_exists(BB_MOOK_MUSIC_AUDIENCE)) - controller.queue_behavior(/datum/ai_behavior/find_and_set/music_audience, BB_MOOK_MUSIC_AUDIENCE, /mob/living/carbon/human) - return - var/atom/home = controller.blackboard[BB_HOME_VILLAGE] - if(isnull(home)) - return - - var/atom/human_target = controller.blackboard[BB_MOOK_MUSIC_AUDIENCE] - if(get_dist(human_target, home) > controller.blackboard[BB_MAXIMUM_DISTANCE_TO_VILLAGE] || controller.blackboard[BB_STORM_APPROACHING]) - controller.clear_blackboard_key(BB_MOOK_MUSIC_AUDIENCE) - return - - controller.queue_behavior(/datum/ai_behavior/travel_towards, BB_MOOK_MUSIC_AUDIENCE) - -/datum/ai_behavior/find_and_set/music_audience - -/datum/ai_behavior/find_and_set/music_audience/search_tactic(datum/ai_controller/controller, locate_path, search_range = SEARCH_TACTIC_DEFAULT_RANGE) - var/atom/home = controller.blackboard[BB_HOME_VILLAGE] - for(var/mob/living/carbon/human/target in oview(search_range, controller.pawn)) - if(target.stat > UNCONSCIOUS || !target.mind) - continue - if(isnull(home) || get_dist(target, home) > controller.blackboard[BB_MAXIMUM_DISTANCE_TO_VILLAGE]) - continue - return target - -/datum/idle_behavior/walk_near_target/mook_village - target_key = BB_HOME_VILLAGE ///healer mooks guard the village from intruders and heal the miner mooks when they come home /datum/ai_controller/basic_controller/mook/support + behavior_tree_json = "code/modules/mob/living/basic/lavaland/mook/support.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/mook, BB_MAXIMUM_DISTANCE_TO_VILLAGE = 10, BB_STORM_APPROACHING = FALSE, BB_PET_TARGETING_STRATEGY = /datum/targeting_strategy/basic/not_friends, ) - idle_behavior = /datum/idle_behavior/walk_near_target/mook_village - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/target_retaliate, - /datum/ai_planning_subtree/look_for_village, - /datum/ai_planning_subtree/acknowledge_chief, - /datum/ai_planning_subtree/pet_planning, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/use_mob_ability/mook_jump, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/find_and_hunt_target/injured_mooks, - ) - -///tree to find and register our leader -/datum/ai_planning_subtree/acknowledge_chief/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(controller.blackboard_key_exists(BB_MOOK_TRIBAL_CHIEF)) - return - controller.queue_behavior(/datum/ai_behavior/find_and_set/find_chief, BB_MOOK_TRIBAL_CHIEF, /mob/living/basic/mining/mook/worker/tribal_chief) - -/datum/ai_behavior/find_and_set/find_chief/search_tactic(datum/ai_controller/controller, locate_path, search_range = SEARCH_TACTIC_DEFAULT_RANGE) - var/mob/living/chief = locate(locate_path) in oview(search_range, controller.pawn) - if(isnull(chief)) - return null - var/mob/living/living_pawn = controller.pawn - living_pawn.befriend(chief) - return chief - -///find injured miner mooks after they come home from a long day of work -/datum/ai_planning_subtree/find_and_hunt_target/injured_mooks - target_key = BB_INJURED_MOOK - hunting_behavior = /datum/ai_behavior/hunt_target/interact_with_target/injured_mooks - finding_behavior = /datum/ai_behavior/find_hunt_target/injured_mooks - hunt_targets = list(/mob/living/basic/mining/mook/worker) - hunt_range = 9 - -///we only heal when the mooks are home during a storm -/datum/ai_planning_subtree/find_and_hunt_target/injured_mooks/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(controller.blackboard[BB_STORM_APPROACHING]) - return ..() - - -/datum/ai_behavior/find_hunt_target/injured_mooks - -/datum/ai_behavior/find_hunt_target/injured_mooks/valid_dinner(mob/living/source, mob/living/injured_mook) - return (injured_mook.health < injured_mook.maxHealth) - -/datum/ai_behavior/hunt_target/interact_with_target/injured_mooks - always_reset_target = TRUE - hunt_cooldown = 10 SECONDS - ///the chief would rather command his mooks to attack people than attack them himself /datum/ai_controller/basic_controller/mook/tribal_chief + behavior_tree_json = "code/modules/mob/living/basic/lavaland/mook/tribal_chief.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/mook, BB_STORM_APPROACHING = FALSE, ) - idle_behavior = /datum/idle_behavior/walk_near_target/mook_village - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/target_retaliate, - /datum/ai_planning_subtree/look_for_village, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/targeted_mob_ability/leap, - /datum/ai_planning_subtree/issue_commands, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/find_and_hunt_target/material_stand, - /datum/ai_planning_subtree/use_mob_ability/mook_jump, - /datum/ai_planning_subtree/find_and_hunt_target/bonfire, - /datum/ai_planning_subtree/find_and_hunt_target/hunt_ores/tribal_chief, - ) -/datum/ai_planning_subtree/issue_commands - ///how far we look for a mook to command - var/command_distance = 5 - -/datum/ai_planning_subtree/issue_commands/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(!locate(/mob/living/basic/mining/mook) in oview(command_distance, controller.pawn)) - return - if(controller.blackboard_key_exists(BB_BASIC_MOB_CURRENT_TARGET)) - controller.queue_behavior(/datum/ai_behavior/issue_commands, BB_BASIC_MOB_CURRENT_TARGET, /datum/pet_command/attack) - return - - var/atom/ore_target = controller.blackboard[BB_ORE_TARGET] - var/mob/living/living_pawn = controller.pawn - if(isnull(ore_target)) - return - if(get_dist(ore_target, living_pawn) <= 1) - return - - controller.queue_behavior(/datum/ai_behavior/issue_commands, BB_ORE_TARGET, /datum/pet_command/fetch) - -/datum/ai_behavior/issue_commands - action_cooldown = 5 SECONDS - -/datum/ai_behavior/issue_commands/perform(seconds_per_tick, datum/ai_controller/controller, target_key, command_path) - var/mob/living/basic/living_pawn = controller.pawn - var/atom/target = controller.blackboard[target_key] - - if(isnull(target)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - var/datum/pet_command/to_command = locate(command_path) in GLOB.mook_commands - if(isnull(to_command)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - var/issue_command = pick(to_command.speech_commands) - living_pawn.say(issue_command, forced = "controller") - living_pawn._pointed(target) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - - -///find an ore, only pick it up when a mook brings it close to us -/datum/ai_planning_subtree/find_and_hunt_target/hunt_ores/tribal_chief - -/datum/ai_planning_subtree/find_and_hunt_target/hunt_ores/tribal_chief/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/mob/living/living_pawn = controller.pawn - if(locate(/obj/item/stack/ore) in living_pawn) - return - - var/atom/target_ore = controller.blackboard[BB_ORE_TARGET] - - if(isnull(target_ore)) - return ..() - - if(!isturf(target_ore.loc)) //picked up by someone else - controller.clear_blackboard_key(BB_ORE_TARGET) - return - - if(get_dist(target_ore, living_pawn) > 1) - return - - return ..() - -/datum/ai_planning_subtree/find_and_hunt_target/bonfire - target_key = BB_MOOK_BONFIRE_TARGET - finding_behavior = /datum/ai_behavior/find_hunt_target/bonfire - hunting_behavior = /datum/ai_behavior/hunt_target/interact_with_target/bonfire - hunt_targets = list(/obj/structure/bonfire) - hunt_range = 9 - - -/datum/ai_behavior/find_hunt_target/bonfire - -/datum/ai_behavior/find_hunt_target/bonfire/valid_dinner(mob/living/source, obj/structure/bonfire/fire, radius) - if(fire.burning) - return FALSE - - return can_see(source, fire, radius) - -/datum/ai_behavior/hunt_target/interact_with_target/bonfire - always_reset_target = TRUE +/datum/ai_controller/basic_controller/mook/tribal_chief/New(atom/new_pawn) + . = ..() + set_blackboard_key(BB_MOOK_COMMANDS, GLOB.mook_commands) diff --git a/code/modules/mob/living/basic/lavaland/mook/mook_bt.dm b/code/modules/mob/living/basic/lavaland/mook/mook_bt.dm new file mode 100644 index 00000000000..d1ba42a14fe --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/mook/mook_bt.dm @@ -0,0 +1,156 @@ + +///hey buddy I went to mook village and everyone there knew you +/datum/bt_node/ai_behavior/find_village + /// Blackboard key to store the found landmark. + var/target_key = BB_HOME_VILLAGE + +/datum/bt_node/ai_behavior/find_village/perform(seconds_per_tick, datum/ai_controller/controller) + var/obj/effect/landmark/home = locate(/obj/effect/landmark/mook_village) in GLOB.landmarks_list + if(isnull(home)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + controller.set_blackboard_key(target_key, home) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + +/// Mook-specific mineral wall finder that skips turfs in BB_BLACKLIST_MINERAL_TURFS. +/datum/bt_node/ai_behavior/find_mineral_wall/mook + +/datum/bt_node/ai_behavior/find_mineral_wall/mook/check_if_mineable(datum/ai_controller/controller, turf/target_wall) + var/list/forbidden = controller.blackboard[BB_BLACKLIST_MINERAL_TURFS] + if(is_type_in_list(target_wall, forbidden)) + return FALSE + return ..() + +///Wander in a random direction to find ore +/datum/bt_node/ai_behavior/calculate_wander_destination + /// Blackboard key holding the anchor atom to wander away from. + var/anchor_key = BB_HOME_VILLAGE/// Blackboard key to write the chosen turf into. + var/destination_key = BB_WANDER_DESTINATION + /// How far we try to wander from the anchor. + var/wander_distance = 9 + +/datum/bt_node/ai_behavior/calculate_wander_destination/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/living_pawn = controller.pawn + var/atom/anchor = controller.blackboard[anchor_key] + if(QDELETED(anchor)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + if(anchor.z != living_pawn.z) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + var/list/angle_directions = list() + for(var/direction in GLOB.alldirs) + angle_directions += dir2angle(direction) + + var/angle_to_home = get_angle(living_pawn, anchor) + angle_directions -= angle_to_home + angle_directions -= (angle_to_home + 45) + angle_directions -= (angle_to_home - 45) + shuffle_inplace(angle_directions) + + var/turf/best = get_turf(living_pawn) + for(var/angle in angle_directions) + var/turf/candidate = _get_furthest_turf(living_pawn, angle, anchor) + if(isnull(candidate)) + continue + var/dist = get_dist(anchor, candidate) + if(dist <= get_dist(anchor, best)) + continue + best = candidate + if(dist >= wander_distance) + break + + controller.set_blackboard_key(destination_key, best) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + +/datum/bt_node/ai_behavior/calculate_wander_destination/proc/_get_furthest_turf(atom/source, angle, atom/anchor) + var/turf/result + for(var/i in 1 to wander_distance) + var/turf/candidate = get_ranged_target_turf_direct(source, anchor, range = i, offset = angle) + if(candidate.is_blocked_turf(source_atom = source)) + break + result = candidate + return result + + +///gotta get south of the material stand +/datum/bt_node/ai_behavior/find_deposit_position + /// Blackboard key holding the /obj/structure/ore_container/material_stand target. + var/stand_key = BB_MATERIAL_STAND_TARGET + /// Blackboard key to write the chosen deposit turf into. + var/destination_key = BB_DEPOSIT_POSITION + +/datum/bt_node/ai_behavior/find_deposit_position/perform(seconds_per_tick, datum/ai_controller/controller) + var/atom/stand = controller.blackboard[stand_key] + if(QDELETED(stand)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + var/mob/living/pawn = controller.pawn + var/list/candidates = list() + for(var/direction in list(SOUTH, SOUTHWEST, SOUTHEAST)) + var/turf/candidate = get_step(stand, direction) + if(!candidate.is_blocked_turf()) + candidates += candidate + var/turf/destination = get_closest_atom(/turf/, candidates, pawn) + if(isnull(destination)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + controller.set_blackboard_key(destination_key, destination) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + +///OH SHIT STORM COMING (or maybe we found ore :3) +/datum/bt_node/decorator/mook_has_flee_reason + +/datum/bt_node/decorator/mook_has_flee_reason/register_observe_signals(atom/pawn) + RegisterSignals(pawn, list(\ + COMSIG_AI_BLACKBOARD_KEY_SET(BB_STORM_APPROACHING),\ + COMSIG_AI_BLACKBOARD_KEY_SET(BB_SIMPLE_CARRY_ITEM),\ + COMSIG_AI_BLACKBOARD_KEY_CLEARED(BB_SIMPLE_CARRY_ITEM),\ + ), PROC_REF(on_signal_changed)) + return TRUE + +/datum/bt_node/decorator/mook_has_flee_reason/unregister_observe_signals(atom/pawn) + UnregisterSignal(pawn, list(\ + COMSIG_AI_BLACKBOARD_KEY_SET(BB_STORM_APPROACHING),\ + COMSIG_AI_BLACKBOARD_KEY_SET(BB_SIMPLE_CARRY_ITEM),\ + COMSIG_AI_BLACKBOARD_KEY_CLEARED(BB_SIMPLE_CARRY_ITEM),\ + )) + +/datum/bt_node/decorator/mook_has_flee_reason/check_condition(datum/ai_controller/controller) + return controller.blackboard[BB_STORM_APPROACHING] || !isnull(controller.blackboard[BB_SIMPLE_CARRY_ITEM]) + + + +/datum/bt_node/subtree/generic_mook_behavior + behavior_tree_json = "code/modules/mob/living/basic/lavaland/mook/generic_mook_behavior.bt.json" + + +///Worker trees +/datum/bt_node/subtree/worker_find_targets + behavior_tree_json = "code/modules/mob/living/basic/lavaland/mook/worker_find_targets.bt.json" + +/datum/bt_node/subtree/go_mining + behavior_tree_json = "code/modules/mob/living/basic/lavaland/mook/go_mining.bt.json" + + +///Bard trees +/datum/bt_node/subtree/bard_play_music + behavior_tree_json = "code/modules/mob/living/basic/lavaland/mook/bard_play_music.bt.json" + +/datum/bt_node/subtree/bard_find_targets + behavior_tree_json = "code/modules/mob/living/basic/lavaland/mook/bard_find_targets.bt.json" + + +///Support trees +/datum/bt_node/subtree/heal_injured + behavior_tree_json = "code/modules/mob/living/basic/lavaland/mook/heal_injured.bt.json" + +/datum/bt_node/subtree/support_find_targets + behavior_tree_json = "code/modules/mob/living/basic/lavaland/mook/support_find_targets.bt.json" + +///Chief trees +/datum/bt_node/subtree/chief_issue_commands + behavior_tree_json = "code/modules/mob/living/basic/lavaland/mook/chief_issue_commands.bt.json" + +/datum/bt_node/subtree/chief_manage_village + behavior_tree_json = "code/modules/mob/living/basic/lavaland/mook/chief_manage_village.bt.json" + + +/datum/bt_node/subtree/chief_find_targets + behavior_tree_json = "code/modules/mob/living/basic/lavaland/mook/chief_find_targets.bt.json" diff --git a/code/modules/mob/living/basic/lavaland/mook/support.bt.json b/code/modules/mob/living/basic/lavaland/mook/support.bt.json new file mode 100644 index 00000000000..ad0fbb631c1 --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/mook/support.bt.json @@ -0,0 +1,9 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/mook/support", + "type": "subtree", + "subtype": "/datum/bt_node/subtree/generic_mook_behavior", + "bindings": { + "b1kiiayf": "/datum/bt_node/subtree/heal_injured", + "b0ymbjo1": "/datum/bt_node/subtree/support_find_targets" + } +} diff --git a/code/modules/mob/living/basic/lavaland/mook/support_find_targets.bt.json b/code/modules/mob/living/basic/lavaland/mook/support_find_targets.bt.json new file mode 100644 index 00000000000..c87cf49caa0 --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/mook/support_find_targets.bt.json @@ -0,0 +1,63 @@ +{ + "dm_type": "/datum/bt_node/subtree/support_find_targets", + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "invert": true, + "key": "BB_MOOK_TRIBAL_CHIEF" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_MOOK_TRIBAL_CHIEF", + "target_source": "/datum/target_source/oview_single_type/tribal_chief", + "targeting_strategy": "/datum/targeting_strategy/anything" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/befriend_target", + "vars": { + "target_key": "BB_MOOK_TRIBAL_CHIEF", + "long_range_friendship": true, + "forget_target": false + } + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_true", + "vars": { + "invert": true, + "key": "BB_STORM_APPROACHING" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_INJURED_MOOK", + "target_source": "/datum/target_source/oview_typed/from_bb_key/mook_heal_targets", + "targeting_strategy": "/datum/targeting_strategy/injured_mob" + } + } + } + ] +} diff --git a/code/modules/mob/living/basic/lavaland/mook/tribal_chief.bt.json b/code/modules/mob/living/basic/lavaland/mook/tribal_chief.bt.json new file mode 100644 index 00000000000..3f526950dd0 --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/mook/tribal_chief.bt.json @@ -0,0 +1,10 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/mook/tribal_chief", + "type": "subtree", + "subtype": "/datum/bt_node/subtree/generic_mook_behavior", + "bindings": { + "b0ymbjo1": "/datum/bt_node/subtree/chief_find_targets", + "b1kiiayf": "/datum/bt_node/subtree/chief_manage_village", + "bk7ixti3": "/datum/bt_node/subtree/chief_issue_commands" + } +} diff --git a/code/modules/mob/living/basic/lavaland/mook/worker_find_targets.bt.json b/code/modules/mob/living/basic/lavaland/mook/worker_find_targets.bt.json new file mode 100644 index 00000000000..26616bcbe11 --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/mook/worker_find_targets.bt.json @@ -0,0 +1,72 @@ +{ + "dm_type": "/datum/bt_node/subtree/worker_find_targets", + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "invert": true, + "key": "BB_MATERIAL_STAND_TARGET" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_MATERIAL_STAND_TARGET", + "target_source": "/datum/target_source/oview_single_type/ore_stand", + "targeting_strategy": "/datum/targeting_strategy/anything" + } + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "invert": true, + "key": "BB_MOOK_TRIBAL_CHIEF" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_MOOK_TRIBAL_CHIEF", + "target_source": "/datum/target_source/oview_single_type/tribal_chief", + "targeting_strategy": "/datum/targeting_strategy/anything" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/befriend_target", + "vars": { + "target_key": "BB_MOOK_TRIBAL_CHIEF", + "long_range_friendship": true, + "forget_target": false + } + } + ] + } + } + ] +} diff --git a/code/modules/mob/living/basic/lavaland/node_drone/node_drone.bt.json b/code/modules/mob/living/basic/lavaland/node_drone/node_drone.bt.json new file mode 100644 index 00000000000..935c577753e --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/node_drone/node_drone.bt.json @@ -0,0 +1,73 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/node_drone", + "type": "selector", + "children": [ + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "loop_delay": "IDLE_BEHAVIOR_RATE", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CURRENT_HUNTING_TARGET", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/pawn_buckled_to_obj", + "vars": { + "observer_abort": "BT_ABORT_SELF", + "invert": true, + "target_key": "BB_CURRENT_HUNTING_TARGET" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_HUNTING_TARGET", + "required_dist": 1, + "finish_on_arrival": true + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/hunt_target/latch_onto", + "vars": { + "target_key": "BB_CURRENT_HUNTING_TARGET", + "hunt_cooldown": "5 SECONDS" + } + } + ] + } + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_CURRENT_HUNTING_TARGET", + "target_source": "/datum/target_source/oview_single_type/ore_vent", + "targeting_strategy": "/datum/targeting_strategy/ore_vent_unclaimed", + "vision_range": 7 + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/lavaland/node_drone/node_drone.dm b/code/modules/mob/living/basic/lavaland/node_drone/node_drone.dm index 0f1f0962559..ad2f7b5ea75 100644 --- a/code/modules/mob/living/basic/lavaland/node_drone/node_drone.dm +++ b/code/modules/mob/living/basic/lavaland/node_drone/node_drone.dm @@ -51,6 +51,7 @@ /mob/living/basic/node_drone/Initialize(mapload) . = ..() ADD_TRAIT(src, TRAIT_MINING_AOE_IMMUNE, INNATE_TRAIT) + AddElement(/datum/element/ai_retaliate) /mob/living/basic/node_drone/death(gibbed) . = ..() @@ -146,43 +147,27 @@ return /// The node drone AI controller -// Generally, this is a very simple AI that will try to find a vent and latch onto it, unless attacked by a lavaland mob, who it will try to flee from. /datum/ai_controller/basic_controller/node_drone + behavior_tree_json = "code/modules/mob/living/basic/lavaland/node_drone/node_drone.bt.json" blackboard = list( - BB_BASIC_MOB_FLEEING = FALSE, // Will flee when the vent lies undefended. - BB_CURRENT_HUNTING_TARGET = null, // Hunts for vents. - BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, // Use this to find vents to run away from + BB_CURRENT_HUNTING_TARGET = null, + BB_CURRENT_TARGET_HIDING_LOCATION = null, + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_BASIC_MOB_FLEE_DISTANCE = 3, ) ai_traits = PASSIVE_AI_FLAGS ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = null - planning_subtrees = list( - // Priority is see if lavaland mobs are attacking us to flee from them. - /datum/ai_planning_subtree/find_nearest_thing_which_attacked_me_to_flee, - // Fly you fool - /datum/ai_planning_subtree/flee_target/node_drone, - // Otherwise, look for and execute hunts for vents to latch onto. - /datum/ai_planning_subtree/find_and_hunt_target/look_for_vent, - ) -// Node subtree to hunt down ore vents. -/datum/ai_planning_subtree/find_and_hunt_target/look_for_vent - hunting_behavior = /datum/ai_behavior/hunt_target/latch_onto/node_drone - hunt_targets = list(/obj/structure/ore_vent) - hunt_range = 7 // Hunt vents to the end of the earth. +/// Validates an ore vent as a valid hunt target: must exist and have no drone already latched. +/datum/targeting_strategy/ore_vent_unclaimed -// node drone behavior for buckling down on a vent. -/datum/ai_behavior/hunt_target/latch_onto/node_drone - hunt_cooldown = 5 SECONDS - -// Evasion behavior. -/datum/ai_planning_subtree/flee_target/node_drone - flee_behaviour = /datum/ai_behavior/run_away_from_target/drone - -/datum/ai_behavior/run_away_from_target/drone - action_cooldown = 1 SECONDS - run_distance = 3 +/datum/targeting_strategy/ore_vent_unclaimed/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + var/obj/structure/ore_vent/vent = target + return istype(vent) && isnull(vent.node) #undef FLY_IN_STATE diff --git a/code/modules/mob/living/basic/lavaland/raptor/baby_raptor.bt.json b/code/modules/mob/living/basic/lavaland/raptor/baby_raptor.bt.json new file mode 100644 index 00000000000..0147a5c5a02 --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/raptor/baby_raptor.bt.json @@ -0,0 +1,108 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/baby_raptor", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity/pacifist" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/run_away_from_target" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/move_to_and_eat" + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/random_chance", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "chance": 0.3 + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/express_happiness" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/random_chance", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "chance": 0.15 + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_FOUND_MOM" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_FOUND_MOM" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/look_to_parent" + } + ] + } + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/raptor_find_food" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop" + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "invert": true, + "key": "BB_FOUND_MOM" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/find_mom" + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/lavaland/raptor/care_for_young.bt.json b/code/modules/mob/living/basic/lavaland/raptor/care_for_young.bt.json new file mode 100644 index 00000000000..6dd9e84fa56 --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/raptor/care_for_young.bt.json @@ -0,0 +1,50 @@ +{ + "dm_type": "/datum/bt_node/subtree/care_for_young", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_RAPTOR_BABY" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/random_chance", + "vars": { + "chance": 0.6 + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_RAPTOR_BABY" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/ai_interact", + "vars": { + "target_key": "BB_RAPTOR_BABY", + "combat_mode": false + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/perform_emote", + "vars": { + "emote": "\"grooms its baby!\"" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/clear_key", + "vars": { + "key": "BB_RAPTOR_BABY" + } + } + ] + } + } +} diff --git a/code/modules/mob/living/basic/lavaland/raptor/raptor_ai.bt.json b/code/modules/mob/living/basic/lavaland/raptor/raptor_ai.bt.json new file mode 100644 index 00000000000..52a56e06a2e --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/raptor/raptor_ai.bt.json @@ -0,0 +1,221 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/raptor", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree", + "override_id": "SUBPLAN_ID_PET_COMMAND" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/raptor_flee" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/raptor_heal_injured" + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/raptor_food_trough" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/move_to_and_eat" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/make_babies" + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/random_chance", + "vars": { + "observer_abort": "BT_ABORT_NONE", + "chance": 0.4 + }, + "child": { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/play_with_owner" + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/care_for_young" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/raptor_find_food" + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_true", + "vars": { + "observer_abort": "BT_ABORT_NONE", + "key": "BB_RAPTOR_PLAYFUL" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target", + "vars": { + "target_key": "BB_OWNER_TARGET", + "targeting_strategy": "/datum/targeting_strategy/ally_mob", + "target_source": "/datum/target_source/oview_single_type/living_mob" + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_true", + "vars": { + "observer_abort": "BT_ABORT_NONE", + "key": "BB_BASIC_MOB_HEALER" + }, + "child": { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target", + "vars": { + "target_key": "BB_INJURED_RAPTOR", + "targeting_strategy": "/datum/targeting_strategy/injured_mob/not_self/injured_raptor" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/retrieve_injured_rider", + "vars": { + "target_key": "BB_INJURED_RAPTOR" + } + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_true", + "vars": { + "observer_abort": "BT_ABORT_NONE", + "key": "BB_RAPTOR_MOTHERLY" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target", + "vars": { + "target_key": "BB_RAPTOR_BABY", + "targeting_strategy": "/datum/targeting_strategy/healthy_raptor_baby", + "target_source": "/datum/target_source/oview_raptor_babies" + } + } + }, + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/find_partner" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop" + }, + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "loop_delay": "1 SECONDS", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/express_happiness" + } + ] + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/lavaland/raptor/raptor_ai_behavior.dm b/code/modules/mob/living/basic/lavaland/raptor/raptor_ai_behavior.dm deleted file mode 100644 index 777a76de1a6..00000000000 --- a/code/modules/mob/living/basic/lavaland/raptor/raptor_ai_behavior.dm +++ /dev/null @@ -1,38 +0,0 @@ -/datum/ai_behavior/hunt_target/interact_with_target/heal_raptor - always_reset_target = TRUE - -/datum/ai_behavior/find_hunt_target/injured_raptor - action_cooldown = 7.5 SECONDS - -/datum/ai_behavior/find_hunt_target/injured_raptor/valid_dinner(mob/living/source, mob/living/target, radius) - return (source != target && target.health < target.maxHealth) - -/datum/ai_behavior/find_hunt_target/raptor_baby/valid_dinner(mob/living/source, mob/living/target, radius) - if (!can_see(source, target, radius) || target.stat == DEAD || !istype(target, /mob/living/basic/raptor)) - return FALSE - var/mob/living/basic/raptor/raptor = target - return raptor.growth_stage == RAPTOR_BABY - -/datum/ai_behavior/hunt_target/interact_with_target/reset_target_combat_mode_off/care_for_young - -/datum/ai_behavior/hunt_target/interact_with_target/reset_target_combat_mode_off/care_for_young/target_caught(mob/living/hunter, atom/hunted) - hunter.manual_emote("grooms [hunted]!") - return ..() - -/datum/ai_behavior/find_hunt_target/raptor_trough - action_cooldown = 7.5 SECONDS - -/datum/ai_behavior/find_hunt_target/raptor_trough/valid_dinner(mob/living/source, atom/movable/trough, radius) - return !!(locate(/obj/item/stack/ore) in trough.contents) - -/datum/ai_behavior/find_injured_rider/perform(seconds_per_tick, datum/ai_controller/controller, hunting_target_key, types_to_hunt, hunt_range) - var/mob/living/living_mob = controller.pawn - if (!length(living_mob.buckled_mobs) || !isliving(living_mob.buckled_mobs[1])) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - var/mob/living/rider = living_mob.buckled_mobs[1] - if (rider.stat == CONSCIOUS || rider.stat == DEAD || rider.health >= rider.maxHealth) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - controller.set_blackboard_key(hunting_target_key, rider) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED diff --git a/code/modules/mob/living/basic/lavaland/raptor/raptor_ai_controller.dm b/code/modules/mob/living/basic/lavaland/raptor/raptor_ai_controller.dm index bae8bd0d2cd..91cd704332b 100644 --- a/code/modules/mob/living/basic/lavaland/raptor/raptor_ai_controller.dm +++ b/code/modules/mob/living/basic/lavaland/raptor/raptor_ai_controller.dm @@ -1,4 +1,5 @@ /datum/ai_controller/basic_controller/raptor + behavior_tree_json = "code/modules/mob/living/basic/lavaland/raptor/raptor_ai.bt.json" blackboard = list( BB_INTERACTIONS_WITH_OWNER = list( "pecks", @@ -11,31 +12,13 @@ BB_BABIES_PARTNER_TYPES = list(/mob/living/basic/raptor), BB_MAX_CHILDREN = 5, BB_RAPTOR_FLEE_THRESHOLD = 0.25, + BB_FUCKS = TRUE ) - ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/find_nearest_thing_which_attacked_me_to_flee/raptor, - /datum/ai_planning_subtree/flee_target/from_flee_key, - /datum/ai_planning_subtree/find_and_hunt_target/heal_rider, - /datum/ai_planning_subtree/find_and_hunt_target/heal_raptors, - /datum/ai_planning_subtree/random_speech/blackboard, - /datum/ai_planning_subtree/pet_planning, - /datum/ai_planning_subtree/target_retaliate/check_faction, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/find_and_hunt_target/raptor_trough, - /datum/ai_planning_subtree/find_food, - /datum/ai_planning_subtree/find_and_hunt_target/care_for_young, - /datum/ai_planning_subtree/make_babies, - /datum/ai_planning_subtree/express_happiness, - /datum/ai_planning_subtree/find_and_hunt_target/play_with_owner/raptor, - ) /// Angry raptors with no faction check on retaliation /datum/ai_controller/basic_controller/raptor/aggressive + ai_movement = /datum/ai_movement/basic_avoidance blackboard = list( BB_INTERACTIONS_WITH_OWNER = list( "pecks", @@ -49,24 +32,7 @@ BB_BABIES_PARTNER_TYPES = list(/mob/living/basic/raptor), BB_MAX_CHILDREN = 5, BB_RAPTOR_FLEE_THRESHOLD = 0.1, - ) - - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/find_nearest_thing_which_attacked_me_to_flee/raptor, - /datum/ai_planning_subtree/flee_target/from_flee_key, - /datum/ai_planning_subtree/find_and_hunt_target/heal_raptors, - /datum/ai_planning_subtree/random_speech/blackboard, - /datum/ai_planning_subtree/pet_planning, - /datum/ai_planning_subtree/target_retaliate, - /datum/ai_planning_subtree/simple_find_target/hunt, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/find_and_hunt_target/raptor_trough, - /datum/ai_planning_subtree/find_food, - /datum/ai_planning_subtree/find_and_hunt_target/care_for_young, - /datum/ai_planning_subtree/make_babies, - /datum/ai_planning_subtree/express_happiness, - /datum/ai_planning_subtree/find_and_hunt_target/play_with_owner/raptor, + BB_FUCKS = TRUE ) /datum/ai_controller/basic_controller/raptor/TryPossessPawn(atom/new_pawn) @@ -84,24 +50,11 @@ SIGNAL_HANDLER REMOVE_TRAIT(pawn, TRAIT_MOB_DIFFICULT_TO_MOUNT, REF(src)) -/datum/ai_controller/basic_controller/raptor/on_mob_eat() - . = ..() - clear_blackboard_key(BB_RAPTOR_TROUGH_TARGET) - /datum/ai_controller/basic_controller/baby_raptor + behavior_tree_json = "code/modules/mob/living/basic/lavaland/raptor/baby_raptor.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_FIND_MOM_TYPES = list(/mob/living/basic/raptor), + BB_IGNORE_MOM_TYPES = list(/mob/living/basic/raptor/baby) ) - ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity/pacifist, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/flee_target, - /datum/ai_planning_subtree/random_speech/blackboard, - /datum/ai_planning_subtree/find_and_hunt_target/raptor_trough, - /datum/ai_planning_subtree/express_happiness, - /datum/ai_planning_subtree/look_for_adult/raptor, - ) diff --git a/code/modules/mob/living/basic/lavaland/raptor/raptor_ai_subtrees.dm b/code/modules/mob/living/basic/lavaland/raptor/raptor_ai_subtrees.dm index 66f08924023..6d05cdeb4a3 100644 --- a/code/modules/mob/living/basic/lavaland/raptor/raptor_ai_subtrees.dm +++ b/code/modules/mob/living/basic/lavaland/raptor/raptor_ai_subtrees.dm @@ -1,73 +1,18 @@ -/datum/ai_planning_subtree/find_and_hunt_target/heal_raptors - target_key = BB_INJURED_RAPTOR - hunting_behavior = /datum/ai_behavior/hunt_target/interact_with_target/heal_raptor - finding_behavior = /datum/ai_behavior/find_hunt_target/injured_raptor - hunt_targets = list(/mob/living/basic/raptor) - hunt_chance = 70 - hunt_range = 9 +/datum/bt_node/subtree/raptor_flee + behavior_tree_json = "code/modules/mob/living/basic/lavaland/raptor/raptor_flee.bt.json" -/datum/ai_planning_subtree/find_and_hunt_target/heal_raptors/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(!controller.blackboard[BB_BASIC_MOB_HEALER]) - return - return ..() +/datum/bt_node/subtree/raptor_heal_injured + behavior_tree_json = "code/modules/mob/living/basic/lavaland/raptor/raptor_heal_injured.bt.json" -/datum/ai_planning_subtree/find_and_hunt_target/heal_rider - target_key = BB_INJURED_RAPTOR - hunting_behavior = /datum/ai_behavior/hunt_target/interact_with_target/heal_raptor - finding_behavior = /datum/ai_behavior/find_injured_rider - hunt_targets = list(/mob/living/carbon/human) +/datum/bt_node/subtree/raptor_food_trough + behavior_tree_json = "code/modules/mob/living/basic/lavaland/raptor/raptor_trough.bt.json" -/datum/ai_planning_subtree/find_and_hunt_target/heal_rider/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(!controller.blackboard[BB_BASIC_MOB_HEALER]) - return - return ..() +/datum/bt_node/subtree/raptor_find_food + behavior_tree_json = "code/modules/mob/living/basic/lavaland/raptor/raptor_find_food.bt.json" -/datum/ai_planning_subtree/find_nearest_thing_which_attacked_me_to_flee/raptor - target_key = BB_BASIC_MOB_FLEE_TARGET +/datum/bt_node/subtree/care_for_young + behavior_tree_json = "code/modules/mob/living/basic/lavaland/raptor/care_for_young.bt.json" -/datum/ai_planning_subtree/find_nearest_thing_which_attacked_me_to_flee/raptor/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(controller.blackboard[BB_RAPTOR_COWARD]) - return ..() - var/mob/living/basic/raptor/raptor = controller.pawn - if (raptor.health <= raptor.maxHealth * controller.blackboard[BB_RAPTOR_FLEE_THRESHOLD]) - return ..() - - if (!length(raptor.buckled_mobs)) - return - - var/mob/living/buckled_to = raptor.buckled_mobs[1] - // Flee if our owner is badly injured and out - if (buckled_to.stat != CONSCIOUS && buckled_to.stat != DEAD) - return ..() - -/datum/ai_planning_subtree/find_and_hunt_target/care_for_young - target_key = BB_RAPTOR_BABY - hunting_behavior = /datum/ai_behavior/hunt_target/interact_with_target/reset_target_combat_mode_off/care_for_young - finding_behavior = /datum/ai_behavior/find_hunt_target/raptor_baby - hunt_targets = list(/mob/living/basic/raptor) - hunt_chance = 75 - hunt_range = 9 - -/datum/ai_planning_subtree/find_and_hunt_target/care_for_young/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(!controller.blackboard[BB_RAPTOR_MOTHERLY]) - return - return ..() - -/datum/ai_planning_subtree/find_and_hunt_target/raptor_trough - target_key = BB_RAPTOR_TROUGH_TARGET - hunting_behavior = /datum/ai_behavior/hunt_target/interact_with_target/reset_target_combat_mode_off - finding_behavior = /datum/ai_behavior/find_hunt_target/raptor_trough - hunt_targets = list(/obj/structure/ore_container/food_trough/raptor_trough) - hunt_chance = 80 - hunt_range = 9 - -/datum/ai_planning_subtree/find_and_hunt_target/raptor_trough/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(world.time < controller.blackboard[BB_NEXT_FOOD_EAT]) - return - return ..() - -/datum/ai_planning_subtree/find_and_hunt_target/play_with_owner/raptor/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(!controller.blackboard[BB_RAPTOR_PLAYFUL]) - return - return ..() +/datum/bt_node/subtree/play_with_owner + behavior_tree_json = "code/modules/mob/living/basic/lavaland/raptor/raptor_play_with_owner.bt.json" diff --git a/code/modules/mob/living/basic/lavaland/raptor/raptor_find_food.bt.json b/code/modules/mob/living/basic/lavaland/raptor/raptor_find_food.bt.json new file mode 100644 index 00000000000..1912562fe82 --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/raptor/raptor_find_food.bt.json @@ -0,0 +1,31 @@ +{ + "dm_type": "/datum/bt_node/subtree/raptor_find_food", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/key_off_cooldown", + "vars": { + "observer_abort": "BT_ABORT_SELF", + "cooldown_key": "BB_NEXT_EAT_FOOD" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": false, + "finish_on_primary": false, + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/find_food" + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target", + "vars": { + "target_key": "BB_RAPTOR_TROUGH_TARGET", + "targeting_strategy": "/datum/targeting_strategy/raptor_trough", + "target_source": "/datum/target_source/oview_single_type/raptor_trough" + } + } + ] + } +} diff --git a/code/modules/mob/living/basic/lavaland/raptor/raptor_flee.bt.json b/code/modules/mob/living/basic/lavaland/raptor/raptor_flee.bt.json new file mode 100644 index 00000000000..182ead09793 --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/raptor/raptor_flee.bt.json @@ -0,0 +1,41 @@ +{ + "dm_type": "/datum/bt_node/subtree/raptor_flee", + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_true", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_RAPTOR_COWARDLY" + }, + "child": { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/run_away_from_target" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/pawn_health_below", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "health_blackboard_key": "BB_RAPTOR_FLEE_THRESHOLD" + }, + "child": { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/run_away_from_target" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/check_rider_stat", + "vars": { + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/run_away_from_target" + } + } + ] +} diff --git a/code/modules/mob/living/basic/lavaland/raptor/raptor_heal_injured.bt.json b/code/modules/mob/living/basic/lavaland/raptor/raptor_heal_injured.bt.json new file mode 100644 index 00000000000..84e80f387c4 --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/raptor/raptor_heal_injured.bt.json @@ -0,0 +1,36 @@ +{ + "dm_type": "/datum/bt_node/subtree/raptor_heal_injured", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_INJURED_RAPTOR" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_INJURED_RAPTOR" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/ai_interact", + "vars": { + "target_key": "BB_INJURED_RAPTOR", + "combat_mode": false + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/clear_key", + "vars": { + "key": "BB_INJURED_RAPTOR" + } + } + ] + } +} diff --git a/code/modules/mob/living/basic/lavaland/raptor/raptor_play_with_owner.bt.json b/code/modules/mob/living/basic/lavaland/raptor/raptor_play_with_owner.bt.json new file mode 100644 index 00000000000..3293e410e7c --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/raptor/raptor_play_with_owner.bt.json @@ -0,0 +1,28 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/raptor/aggressive", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_OWNER_TARGET" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_OWNER_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/hunt_target/play_with_owner", + "vars": { + "cooldown_key": "BB_OWNER_TARGET" + } + } + ] + } +} diff --git a/code/modules/mob/living/basic/lavaland/raptor/raptor_trough.bt.json b/code/modules/mob/living/basic/lavaland/raptor/raptor_trough.bt.json new file mode 100644 index 00000000000..4c988ea6a27 --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/raptor/raptor_trough.bt.json @@ -0,0 +1,36 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/baby_raptor", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_RAPTOR_TROUGH_TARGET" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_RAPTOR_TROUGH_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/ai_interact", + "vars": { + "target_key": "BB_RAPTOR_TROUGH_TARGET", + "combat_mode": false + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/clear_key", + "vars": { + "key": "BB_RAPTOR_TROUGH_TARGET" + } + } + ] + } +} diff --git a/code/modules/mob/living/basic/lavaland/tendril/tendril.bt.json b/code/modules/mob/living/basic/lavaland/tendril/tendril.bt.json new file mode 100644 index 00000000000..63f0c076958 --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/tendril/tendril.bt.json @@ -0,0 +1,124 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/tendril", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": false, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/is_at_distance", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "maximum_distance": 7 + }, + "child": { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/use_mob_ability", + "vars": { + "ability_key": "BB_TENDRIL_SPIKES" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/use_mob_ability", + "vars": { + "ability_key": "BB_TENDRIL_LASH" + } + } + ] + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability", + "vars": { + "ability_key": "BB_TENDRIL_CHASER", + "target_key": "BB_CURRENT_TARGET" + } + } + ] + } + ] + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/cooldown", + "vars": { + "cooldown_key": "BB_BASIC_MOB_REINFORCEMENTS_COOLDOWN", + "cooldown_duration": "30 SECONDS", + "lock_on_succeed": true + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/call_reinforcements/mining" + } + } + ] + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/wait" + } + ] + }, + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION", + "check_faction": true, + "vision_range": 5 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION", + "target_loss_distance": 9, + "vision_range": 5 + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/lavaland/tendril/tendril.dm b/code/modules/mob/living/basic/lavaland/tendril/tendril.dm index d7758e6048c..1c2766e706f 100644 --- a/code/modules/mob/living/basic/lavaland/tendril/tendril.dm +++ b/code/modules/mob/living/basic/lavaland/tendril/tendril.dm @@ -132,7 +132,7 @@ GLOBAL_LIST_INIT(tendrils, list()) return var/beat_rate = HEARTBEAT_NORMAL - if (ai_controller?.blackboard[BB_BASIC_MOB_CURRENT_TARGET]) + if (ai_controller?.blackboard[BB_CURRENT_TARGET]) beat_rate = round(HEARTBEAT_FRANTIC + health / maxHealth * (HEARTBEAT_FAST - HEARTBEAT_FRANTIC), 0.05 SECONDS) if (beat_rate != soundloop.mid_length) diff --git a/code/modules/mob/living/basic/lavaland/tendril/tendril_ai.dm b/code/modules/mob/living/basic/lavaland/tendril/tendril_ai.dm index 5da4fcedf28..db605e350da 100644 --- a/code/modules/mob/living/basic/lavaland/tendril/tendril_ai.dm +++ b/code/modules/mob/living/basic/lavaland/tendril/tendril_ai.dm @@ -1,40 +1,7 @@ /datum/ai_controller/basic_controller/tendril + behavior_tree_json = "code/modules/mob/living/basic/lavaland/tendril/tendril.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_TARGET_MINIMUM_STAT = HARD_CRIT, BB_AGGRO_RANGE = 9, // Keeps an eye on you even if you flee - BB_AGGRO_GRAB_RANGE = 5, // Only aggros if you get real close and personal ) - - planning_subtrees = list( - /datum/ai_planning_subtree/call_reinforcements/mining, - /datum/ai_planning_subtree/target_retaliate/check_faction, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/targeted_mob_ability/tendril_chaser, - /datum/ai_planning_subtree/use_mob_ability/tendril_spikes, - /datum/ai_planning_subtree/use_mob_ability/tendril_lash, - ) - -/datum/ai_planning_subtree/targeted_mob_ability/tendril_chaser - ability_key = BB_TENDRIL_CHASER - operational_datums = list(/datum/component/ai_target_timer) - finish_planning = FALSE - -/datum/ai_planning_subtree/use_mob_ability/tendril_lash - ability_key = BB_TENDRIL_LASH - -/datum/ai_planning_subtree/use_mob_ability/tendril_lash/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/atom/target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] - if (isnull(target) || get_dist(controller.pawn, target) > /obj/projectile/tentacle_lash::range) - return FALSE - return ..() - -/datum/ai_planning_subtree/use_mob_ability/tendril_spikes - ability_key = BB_TENDRIL_SPIKES - -/datum/ai_planning_subtree/use_mob_ability/tendril_spikes/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/atom/target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] - var/datum/action/cooldown/mob_cooldown/tendril_cross_spikes/ability = controller.blackboard[ability_key] - if (isnull(target) || !istype(ability) || get_dist(controller.pawn, target) > ability.spike_range) - return FALSE - return ..() diff --git a/code/modules/mob/living/basic/lavaland/watcher/watcher.bt.json b/code/modules/mob/living/basic/lavaland/watcher/watcher.bt.json new file mode 100644 index 00000000000..b408d011c4d --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/watcher/watcher.bt.json @@ -0,0 +1,133 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/watcher", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/target_has_trait", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "invert": true, + "key": "TRAIT_OVERWATCHED" + }, + "child": { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_at_least", + "vars": { + "key": "BB_BASIC_MOB_HAS_TARGET_TIME", + "minimum": "5 SECONDS" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/use_mob_ability" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_ranged_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION", + "max_range": 9 + } + } + ] + } + ] + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/maintain_distance", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/cooldown", + "vars": { + "cooldown_key": "BB_BASIC_MOB_REINFORCEMENTS_COOLDOWN", + "cooldown_duration": "30 SECONDS", + "lock_on_succeed": true + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/call_reinforcements/mining" + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION", + "check_faction": true + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/lavaland/watcher/watcher_ai.dm b/code/modules/mob/living/basic/lavaland/watcher/watcher_ai.dm index cd45eb9c6ec..637f0f78bb9 100644 --- a/code/modules/mob/living/basic/lavaland/watcher/watcher_ai.dm +++ b/code/modules/mob/living/basic/lavaland/watcher/watcher_ai.dm @@ -1,4 +1,5 @@ /datum/ai_controller/basic_controller/watcher + behavior_tree_json = "code/modules/mob/living/basic/lavaland/watcher/watcher.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_TARGET_PRIORITY_STRATEGY = /datum/target_priority_strategy/mining, @@ -7,34 +8,3 @@ ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/call_reinforcements/mining, - /datum/ai_planning_subtree/target_retaliate/check_faction, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/maintain_distance, - /datum/ai_planning_subtree/use_mob_ability/gaze, - /datum/ai_planning_subtree/ranged_skirmish/watcher, - ) - -/datum/ai_planning_subtree/use_mob_ability/gaze - finish_planning = TRUE - -/datum/ai_planning_subtree/use_mob_ability/gaze/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/mob/living/target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] - if (!isliving(target)) - return // Don't do this if there's nothing hostile around or if our target is a mech - var/time_on_target = controller.blackboard[BB_BASIC_MOB_HAS_TARGET_TIME] || 0 - if (time_on_target < 5 SECONDS) - return // We need to spend some time acquiring our target first - return ..() - -/datum/ai_planning_subtree/ranged_skirmish/watcher - min_range = 0 - -/datum/ai_planning_subtree/ranged_skirmish/watcher/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/mob/living/target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] - if (QDELETED(target) || HAS_TRAIT(target, TRAIT_OVERWATCHED)) - return // Don't bully people who are playing red light green light - return ..() diff --git a/code/modules/mob/living/basic/minebots/minebot.bt.json b/code/modules/mob/living/basic/minebots/minebot.bt.json new file mode 100644 index 00000000000..1e27d18584a --- /dev/null +++ b/code/modules/mob/living/basic/minebots/minebot.bt.json @@ -0,0 +1,107 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/minebot", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree", + "override_id": "SUBPLAN_ID_PET_COMMAND" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/minebot_combat" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/minebot_mining" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_SUCCEED_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_FAIL_ON_FAILURE", + "loop_delay": "1 SECONDS", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_MINER_FRIEND", + "target_source": "/datum/target_source/oview_single_type/human_mob", + "targeting_strategy": "/datum/targeting_strategy/rock_stoner" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/befriend_target", + "vars": { + "target_key": "BB_MINER_FRIEND", + "long_range_friendship": true + } + } + ] + }, + { + "type": "sequence", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/cooldown", + "vars": { + "cooldown_key": "BB_MINEBOT_CRIT_ALERT_COOLDOWN", + "cooldown_duration": "10 SECONDS" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_NEARBY_DEAD_MINER", + "target_source": "/datum/target_source/oview_single_type/human_mob", + "targeting_strategy": "/datum/targeting_strategy/unconscious_human" + } + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/send_sos_message", + "vars": { + "target_key": "BB_NEARBY_DEAD_MINER" + } + } + ] + } + ] + } + ] + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/minebots/minebot.dm b/code/modules/mob/living/basic/minebots/minebot.dm index 525f1450141..1b4a1579187 100644 --- a/code/modules/mob/living/basic/minebots/minebot.dm +++ b/code/modules/mob/living/basic/minebots/minebot.dm @@ -110,7 +110,7 @@ for(var/obj/item/borg/upgrade/modkit/modkit as anything in stored_gun.modkits) . += span_notice("There is \a [modkit] installed, using [modkit.cost]% capacity.") - if(ai_controller && ai_controller.ai_status == AI_STATUS_IDLE) + if(ai_controller && ai_controller.ai_status == AI_STATUS_OFF && ai_controller.get_expected_ai_status() == AI_STATUS_ON) . += "The [src] appears to be in sleep mode. You can restore normal functions by tapping it." @@ -137,8 +137,8 @@ /mob/living/basic/mining_drone/attack_hand(mob/living/carbon/human/user, list/modifiers) if(!user.combat_mode) - if(ai_controller && ai_controller.ai_status == AI_STATUS_IDLE) - ai_controller.set_ai_status(AI_STATUS_ON) + if(ai_controller && ai_controller.ai_status == AI_STATUS_OFF) + ai_controller.reset_ai_status() //wakes a performance-slept bot, no-op if it is off for a real reason if(LAZYACCESS(modifiers, LEFT_CLICK)) //Lets Right Click be specifically for re-enabling their AI (and avoiding the UI popup), while Left Click simply does both. ui_interact(user) return diff --git a/code/modules/mob/living/basic/minebots/minebot_ai.dm b/code/modules/mob/living/basic/minebots/minebot_ai.dm index 53895358d2a..fdb8e308079 100644 --- a/code/modules/mob/living/basic/minebots/minebot_ai.dm +++ b/code/modules/mob/living/basic/minebots/minebot_ai.dm @@ -1,4 +1,5 @@ /datum/ai_controller/basic_controller/minebot + behavior_tree_json = "code/modules/mob/living/basic/minebots/minebot.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_PET_TARGETING_STRATEGY = /datum/targeting_strategy/basic/not_friends, @@ -19,110 +20,60 @@ ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/launch_missiles, - /datum/ai_planning_subtree/pet_planning, - /datum/ai_planning_subtree/befriend_miners, - /datum/ai_planning_subtree/defend_node, - /datum/ai_planning_subtree/minebot_maintain_distance, - /datum/ai_planning_subtree/basic_ranged_attack_subtree/minebot, - /datum/ai_planning_subtree/find_and_hunt_target/hunt_ores/minebot, - /datum/ai_planning_subtree/minebot_mining, - /datum/ai_planning_subtree/locate_dead_humans, - ) -/datum/ai_planning_subtree/launch_missiles +/datum/bt_node/subtree/minebot_combat + behavior_tree_json = "code/modules/mob/living/basic/minebots/minebot_combat.bt.json" -/datum/ai_planning_subtree/launch_missiles/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/datum/action/cooldown/missile_ability = controller.blackboard[BB_MINEBOT_MISSILE_ABILITY] - if(!missile_ability?.IsAvailable()) - return - if(!controller.blackboard_key_exists(BB_MINEBOT_MISSILE_TARGET)) - controller.queue_behavior(/datum/ai_behavior/find_and_set/clear_bombing_zone, BB_MINEBOT_MISSILE_TARGET, /obj/effect/temp_visual/minebot_target, 7) - return - controller.queue_behavior(/datum/ai_behavior/targeted_mob_ability/and_clear_target, BB_MINEBOT_MISSILE_ABILITY, BB_MINEBOT_MISSILE_TARGET) - return SUBTREE_RETURN_FINISH_PLANNING +/datum/bt_node/subtree/minebot_mining + behavior_tree_json = "code/modules/mob/living/basic/minebots/minebot_mining.bt.json" -/datum/ai_behavior/find_and_set/clear_bombing_zone +/// Mineral wall finder that skips turfs in the blacklist and the previously unreachable wall. +/datum/bt_node/ai_behavior/find_mineral_wall/minebot -/datum/ai_behavior/find_and_set/clear_bombing_zone/search_tactic(datum/ai_controller/controller, locate_path, search_range = SEARCH_TACTIC_DEFAULT_RANGE) - for(var/obj/effect/temp_visual/minebot_target/target in oview(search_range, controller.pawn)) - if(isclosedturf(get_turf(target))) - continue - return target - return null - -/datum/ai_planning_subtree/befriend_miners/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(!controller.blackboard_key_exists(BB_MINER_FRIEND)) - controller.queue_behavior(/datum/ai_behavior/find_and_set/miner_to_befriend, BB_MINER_FRIEND) - return - controller.queue_behavior(/datum/ai_behavior/befriend_target, BB_MINER_FRIEND) - -/datum/ai_behavior/find_and_set/miner_to_befriend - -/datum/ai_behavior/find_and_set/miner_to_befriend/search_tactic(datum/ai_controller/controller, locate_path, search_range = SEARCH_TACTIC_DEFAULT_RANGE) - for(var/mob/living/carbon/human/target in oview(search_range, controller.pawn)) - if(HAS_TRAIT(target, TRAIT_ROCK_STONER)) - return target - return null - -/datum/ai_planning_subtree/defend_node/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/mob/living/target = controller.blackboard[BB_DRONE_DEFEND] - if(QDELETED(target)) - controller.queue_behavior(/datum/ai_behavior/find_and_set, BB_DRONE_DEFEND, /mob/living/basic/node_drone) - return - var/mob/living/living_pawn = controller.pawn - if(!living_pawn.has_ally(target)) - controller.queue_behavior(/datum/ai_behavior/befriend_target, BB_DRONE_DEFEND) - return - if(target.health < (target.maxHealth * 0.75) && controller.blackboard[BB_MINEBOT_REPAIR_DRONE]) - controller.queue_behavior(/datum/ai_behavior/repair_drone, BB_DRONE_DEFEND) - return SUBTREE_RETURN_FINISH_PLANNING - -/datum/ai_behavior/repair_drone - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION | AI_BEHAVIOR_REQUIRE_REACH - -/datum/ai_behavior/repair_drone/setup(datum/ai_controller/controller, target_key) - . = ..() - var/turf/target = controller.blackboard[target_key] - if(isnull(target)) +/datum/bt_node/ai_behavior/find_mineral_wall/minebot/check_if_mineable(datum/ai_controller/controller, turf/target_wall) + var/list/forbidden = controller.blackboard[BB_BLACKLIST_MINERAL_TURFS] + var/turf/previous_unreachable = controller.blackboard[BB_PREVIOUS_UNREACHABLE_WALL] + if(is_type_in_list(target_wall, forbidden) || target_wall == previous_unreachable) return FALSE - set_movement_target(controller, target) + controller.clear_blackboard_key(BB_PREVIOUS_UNREACHABLE_WALL) + return ..() -/datum/ai_behavior/repair_drone/perform(seconds_per_tick, datum/ai_controller/controller, target_key) - var/atom/target = controller.blackboard[target_key] +/// Mines a mineral turf at range using RangedAttack rather than ai_interact. +/datum/bt_node/ai_behavior/minebot_mine_turf + time_between_perform = 3 SECONDS + var/target_key = BB_TARGET_MINERAL_TURF + +/datum/bt_node/ai_behavior/minebot_mine_turf/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/basic/living_pawn = controller.pawn + var/turf/target = controller.blackboard[target_key] if(QDELETED(target)) return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - var/mob/living/living_pawn = controller.pawn - living_pawn.say("REPAIRING [target]!") - controller.ai_interact(target = target) + if(check_obstacles_in_path(controller, target)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + if(!living_pawn.combat_mode) + living_pawn.set_combat_mode(TRUE) + living_pawn.RangedAttack(target) return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED -/datum/ai_behavior/repair_drone/finish_action(datum/ai_controller/controller, success, target_key) +/datum/bt_node/ai_behavior/minebot_mine_turf/proc/check_obstacles_in_path(datum/ai_controller/controller, turf/target) + var/mob/living/source = controller.pawn + var/list/turfs_in_path = get_line(source, target) - target + for(var/turf/turf in turfs_in_path) + if(turf.is_blocked_turf(ignore_atoms = list(source))) + controller.set_blackboard_key(BB_PREVIOUS_UNREACHABLE_WALL, target) + return TRUE + return FALSE + +/datum/bt_node/ai_behavior/minebot_mine_turf/finish_action(datum/ai_controller/controller, succeeded) . = ..() - if(!success) - controller.clear_blackboard_key(target_key) + controller.clear_blackboard_key(target_key) -///find dead humans and report their location on the radio -/datum/ai_planning_subtree/locate_dead_humans/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(controller.blackboard_key_exists(BB_NEARBY_DEAD_MINER)) - controller.queue_behavior(/datum/ai_behavior/send_sos_message, BB_NEARBY_DEAD_MINER) - return SUBTREE_RETURN_FINISH_PLANNING - controller.queue_behavior(/datum/ai_behavior/find_and_set/unconscious_human, BB_NEARBY_DEAD_MINER, /mob/living/carbon/human) +/// Sends a radio SOS message for a dead or unconscious miner. Clears the target key on finish. +/datum/bt_node/ai_behavior/send_sos_message + time_between_perform = 2 MINUTES + var/target_key = BB_NEARBY_DEAD_MINER -/datum/ai_behavior/find_and_set/unconscious_human/search_tactic(datum/ai_controller/controller, locate_path, search_range = SEARCH_TACTIC_DEFAULT_RANGE) - for(var/mob/living/carbon/human/target in oview(search_range, controller.pawn)) - if(target.stat >= UNCONSCIOUS && target.mind) - return target - return null - -/datum/ai_behavior/send_sos_message - behavior_flags = AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION - action_cooldown = 2 MINUTES - -/datum/ai_behavior/send_sos_message/perform(seconds_per_tick, datum/ai_controller/controller, target_key) +/datum/bt_node/ai_behavior/send_sos_message/perform(seconds_per_tick, datum/ai_controller/controller) var/mob/living/carbon/target = controller.blackboard[target_key] var/mob/living/living_pawn = controller.pawn if(QDELETED(target) || is_station_level(target.z)) @@ -135,140 +86,85 @@ radio_implant.radio.talk_into(living_pawn, message, RADIO_CHANNEL_SUPPLY) return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED -/datum/ai_behavior/send_sos_message/finish_action(datum/ai_controller/controller, success, target_key) +/datum/bt_node/ai_behavior/send_sos_message/finish_action(datum/ai_controller/controller, succeeded) . = ..() controller.clear_blackboard_key(target_key) -///operational datums is null because we dont use a ranged component, we use a gun in our contents -/datum/ai_planning_subtree/basic_ranged_attack_subtree/minebot - operational_datums = null - ranged_attack_behavior = /datum/ai_behavior/basic_ranged_attack/minebot +/// Moves adjacent to an allied node drone and repairs it if its health is below the threshold. +/// Fails if the drone is healthy, not allied, or repair is disabled. +/datum/bt_node/ai_behavior/repair_drone + var/target_key = BB_DRONE_DEFEND + var/repair_threshold = 0.75 -/datum/ai_planning_subtree/basic_ranged_attack_subtree/minebot/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/atom/target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] - if(QDELETED(target)) - return - var/mob/living/living_pawn = controller.pawn - if(!living_pawn.combat_mode) //we are not on attack mode - return - controller.queue_behavior(ranged_attack_behavior, BB_BASIC_MOB_CURRENT_TARGET, BB_TARGETING_STRATEGY, BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION) - return SUBTREE_RETURN_FINISH_PLANNING - -/datum/ai_planning_subtree/minebot_maintain_distance/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/atom/target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] - if(QDELETED(target)) - return - var/mob/living/living_pawn = controller.pawn - if(get_dist(living_pawn, target) <= controller.blackboard[BB_MINIMUM_SHOOTING_DISTANCE]) - controller.queue_behavior(/datum/ai_behavior/run_away_from_target/run_and_shoot/minebot, BB_BASIC_MOB_CURRENT_TARGET) - return SUBTREE_RETURN_FINISH_PLANNING - -/datum/ai_behavior/run_away_from_target/run_and_shoot/minebot - -/datum/ai_behavior/run_away_from_target/run_and_shoot/minebot/perform(seconds_per_tick, datum/ai_controller/controller, target_key, hiding_location_key) - if(!controller.blackboard[BB_MINEBOT_PLANT_MINES]) - return ..() - var/datum/action/cooldown/mine_ability = controller.blackboard[BB_MINEBOT_LANDMINE_ABILITY] - mine_ability?.Trigger() - return ..() - -/datum/ai_behavior/basic_ranged_attack/minebot - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION - avoid_friendly_fire = TRUE - ///if our target is closer than this distance, finish action - var/minimum_distance = 3 - -/datum/ai_behavior/basic_ranged_attack/minebot/perform(seconds_per_tick, datum/ai_controller/controller, target_key, targeting_strategy_key, hiding_location_key) - . = ..() - minimum_distance = controller.blackboard[BB_MINIMUM_SHOOTING_DISTANCE] ? controller.blackboard[BB_MINIMUM_SHOOTING_DISTANCE] : initial(minimum_distance) - var/atom/target = controller.blackboard[target_key] +/datum/bt_node/ai_behavior/repair_drone/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/target = controller.blackboard[target_key] if(QDELETED(target)) return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + if(!controller.blackboard[BB_MINEBOT_REPAIR_DRONE]) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED var/mob/living/living_pawn = controller.pawn - if(get_dist(living_pawn, target) <= minimum_distance) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -///mine walls if we are on automated mining mode -/datum/ai_planning_subtree/minebot_mining/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(!controller.blackboard[BB_AUTOMATED_MINING]) - return - if(controller.blackboard_key_exists(BB_TARGET_MINERAL_TURF)) - controller.queue_behavior(/datum/ai_behavior/minebot_mine_turf, BB_TARGET_MINERAL_TURF) - return SUBTREE_RETURN_FINISH_PLANNING - controller.queue_behavior(/datum/ai_behavior/find_mineral_wall/minebot, BB_TARGET_MINERAL_TURF) - -/datum/ai_behavior/find_mineral_wall/minebot - -/datum/ai_behavior/find_mineral_wall/minebot/check_if_mineable(datum/ai_controller/controller, turf/target_wall) - var/list/forbidden_turfs = controller.blackboard[BB_BLACKLIST_MINERAL_TURFS] - var/turf/previous_unreachable_wall = controller.blackboard[BB_PREVIOUS_UNREACHABLE_WALL] - if(is_type_in_list(target_wall, forbidden_turfs) || target_wall == previous_unreachable_wall) - return FALSE - controller.clear_blackboard_key(BB_PREVIOUS_UNREACHABLE_WALL) - return ..() - -/datum/ai_behavior/minebot_mine_turf - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION - required_distance = 2 - action_cooldown = 3 SECONDS - -/datum/ai_behavior/minebot_mine_turf/setup(datum/ai_controller/controller, target_key) - . = ..() - var/turf/target = controller.blackboard[target_key] - if(isnull(target)) - return FALSE - set_movement_target(controller, target) - -/datum/ai_behavior/minebot_mine_turf/perform(seconds_per_tick, datum/ai_controller/controller, target_key) - var/mob/living/basic/living_pawn = controller.pawn - var/turf/target = controller.blackboard[target_key] - - if(QDELETED(target)) + if(!living_pawn.has_ally(target)) return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - if(check_obstacles_in_path(controller, target)) + if(target.health >= target.maxHealth * repair_threshold) return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - if(!living_pawn.combat_mode) - living_pawn.set_combat_mode(TRUE) - - living_pawn.RangedAttack(target) + INVOKE_ASYNC(living_pawn, TYPE_PROC_REF(/atom/movable, say), "REPAIRING [target]!") + INVOKE_ASYNC(controller, TYPE_PROC_REF(/datum/ai_controller, ai_interact), target) return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED -/datum/ai_behavior/minebot_mine_turf/proc/check_obstacles_in_path(datum/ai_controller/controller, turf/target) - var/mob/living/source = controller.pawn - var/list/turfs_in_path = get_line(source, target) - target - for(var/turf/turf in turfs_in_path) - if(turf.is_blocked_turf(ignore_atoms = list(source))) - controller.set_blackboard_key(BB_PREVIOUS_UNREACHABLE_WALL, target) - return TRUE - return FALSE +/// Turns off combat mode then interacts with a nearby ore to collect it. Clears the target key on finish. +/datum/bt_node/ai_behavior/collect_ore/minebot + var/target_key = BB_ORE_TARGET -/datum/ai_behavior/minebot_mine_turf/finish_action(datum/ai_controller/controller, success, target_key) +/datum/bt_node/ai_behavior/collect_ore/minebot/perform(seconds_per_tick, datum/ai_controller/controller) + var/obj/item/stack/ore/target = controller.blackboard[target_key] + if(QDELETED(target)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + var/mob/living/living_pawn = controller.pawn + if(living_pawn.combat_mode) + living_pawn.set_combat_mode(FALSE) + INVOKE_ASYNC(controller, TYPE_PROC_REF(/datum/ai_controller, ai_interact), target) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + +/datum/bt_node/ai_behavior/collect_ore/minebot/finish_action(datum/ai_controller/controller, succeeded) . = ..() controller.clear_blackboard_key(target_key) -///store ores in our body -/datum/ai_planning_subtree/find_and_hunt_target/hunt_ores/minebot - hunting_behavior = /datum/ai_behavior/hunt_target/interact_with_target/consume_ores/minebot - hunt_chance = 100 +/// befriend_target variant that fails immediately if the target is already an ally used to gate the drone-defend block. +/datum/bt_node/ai_behavior/befriend_target/check_ally -/datum/ai_planning_subtree/find_and_hunt_target/hunt_ores/minebot/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/automated_mining = controller.blackboard[BB_AUTOMATED_MINING] +/datum/bt_node/ai_behavior/befriend_target/check_ally/perform(seconds_per_tick, datum/ai_controller/controller) var/mob/living/living_pawn = controller.pawn - - if(!automated_mining && living_pawn.combat_mode) //are we not on automated mining or collect mode? - return - + var/mob/living/living_target = controller.blackboard[target_key] + if(QDELETED(living_target) || living_pawn.has_ally(living_target)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED return ..() -/datum/ai_behavior/hunt_target/interact_with_target/consume_ores/minebot - hunt_cooldown = 2 SECONDS +/// BT-native ranged attack for the minebot. Avoids friendly fire. +/datum/bt_node/ai_behavior/basic_ranged_attack/minebot + avoid_friendly_fire = TRUE -/datum/ai_behavior/hunt_target/interact_with_target/consume_ores/minebot/target_caught(mob/living/hunter, obj/item/stack/ore/hunted) - if(hunter.combat_mode) - hunter.set_combat_mode(FALSE) - return ..() +/// Accepts humans with TRAIT_ROCK_STONER (miners). +/datum/targeting_strategy/rock_stoner + +/datum/targeting_strategy/rock_stoner/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + if(!istype(target, /mob/living/carbon/human)) + return FALSE + return HAS_TRAIT(target, TRAIT_ROCK_STONER) + +/// Accepts unconscious or dead humans that have a mind (i.e., real players/NPCs that need SOS). +/datum/targeting_strategy/unconscious_human + +/datum/targeting_strategy/unconscious_human/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + if(!istype(target, /mob/living/carbon/human)) + return FALSE + var/mob/living/carbon/human/human_target = target + return human_target.stat >= UNCONSCIOUS && human_target.mind ///pet commands /datum/pet_command/free/minebot @@ -305,9 +201,8 @@ var/datum/action/cooldown/ability = controller.blackboard[ability_key] if(!ability?.IsAvailable()) return - controller.queue_behavior(/datum/ai_behavior/use_mob_ability, ability_key) + INVOKE_ASYNC(ability, TYPE_PROC_REF(/datum/action, Trigger)) controller.clear_blackboard_key(BB_ACTIVE_PET_COMMAND) - return SUBTREE_RETURN_FINISH_PLANNING /datum/pet_command/minebot_ability/light command_name = "Toggle lights" @@ -329,8 +224,12 @@ /datum/pet_command/minebot_ability/dump/retrieve_command_text(atom/living_pet, atom/target) return "signals [living_pet] to dump its ore!" +/datum/pet_command/minebot_ability/dump/execute_action(datum/ai_controller/controller) + controller.set_blackboard_key(BB_AUTOMATED_MINING, FALSE) //else bro will just pick it up + return ..() + /datum/pet_command/attack/minebot - attack_behaviour = /datum/ai_behavior/basic_ranged_attack/minebot + attack_subtree = /datum/bt_node/subtree/pet_command/attack/minebot /datum/pet_command/attack/minebot/execute_action(datum/ai_controller/controller) controller.set_blackboard_key(BB_AUTOMATED_MINING, FALSE) @@ -350,8 +249,8 @@ /datum/pet_command/protect_owner/minebot/set_command_target(mob/living/parent, atom/target) if(!parent.ai_controller.blackboard[BB_MINEBOT_AUTO_DEFEND]) return FALSE - if(!parent.ai_controller.blackboard_key_exists(BB_BASIC_MOB_CURRENT_TARGET) && !QDELETED(target)) //we are already dealing with something, - parent.ai_controller.set_blackboard_key(BB_BASIC_MOB_CURRENT_TARGET, target) + if(!parent.ai_controller.blackboard_key_exists(BB_CURRENT_TARGET) && !QDELETED(target)) //we are already dealing with something, + parent.ai_controller.set_blackboard_key(BB_CURRENT_TARGET, target) return TRUE /datum/pet_command/protect_owner/minebot/execute_action(datum/ai_controller/controller) @@ -359,5 +258,3 @@ var/mob/living/living_pawn = controller.pawn living_pawn.set_combat_mode(TRUE) controller.clear_blackboard_key(BB_ACTIVE_PET_COMMAND) - - diff --git a/code/modules/mob/living/basic/minebots/minebot_combat.bt.json b/code/modules/mob/living/basic/minebots/minebot_combat.bt.json new file mode 100644 index 00000000000..7a8e60c9fd1 --- /dev/null +++ b/code/modules/mob/living/basic/minebots/minebot_combat.bt.json @@ -0,0 +1,112 @@ +{ + "dm_type": "/datum/bt_node/subtree/minebot_combat", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CURRENT_TARGET", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "key": "BB_MINEBOT_MISSILE_ABILITY" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability/and_clear_target", + "vars": { + "ability_key": "BB_MINEBOT_MISSILE_ABILITY", + "target_key": "BB_MINEBOT_MISSILE_TARGET" + } + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_ranged_attack/minebot", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/maintain_distance", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "min_dist_key": "BB_MINIMUM_SHOOTING_DISTANCE", + "max_dist_key": "BB_MINIMUM_SHOOTING_DISTANCE" + } + }, + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_SUCCEED_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_FAIL_ON_FAILURE", + "loop_delay": "1 SECONDS", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_MINEBOT_MISSILE_TARGET", + "invert": true + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_MINEBOT_MISSILE_TARGET", + "target_source": "/datum/target_source/oview_single_type/minebot_target", + "targeting_strategy": "/datum/targeting_strategy/anything", + "vision_range": 7 + } + } + } + ] + }, + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_SUCCEED_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_FAIL_ON_FAILURE", + "loop_delay": "1 SECONDS", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "invert": false, + "key": "BB_MINEBOT_LANDMINE_ABILITY" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/use_mob_ability", + "vars": { + "ability_key": "BB_MINEBOT_LANDMINE_ABILITY" + } + } + } + ] + } + ] + } +} diff --git a/code/modules/mob/living/basic/minebots/minebot_mining.bt.json b/code/modules/mob/living/basic/minebots/minebot_mining.bt.json new file mode 100644 index 00000000000..e8ad56080db --- /dev/null +++ b/code/modules/mob/living/basic/minebots/minebot_mining.bt.json @@ -0,0 +1,193 @@ +{ + "dm_type": "/datum/bt_node/subtree/minebot_mining", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_true", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_MINEBOT_REPAIR_DRONE" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_DRONE_DEFEND" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/target_health_below_fraction", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_DRONE_DEFEND", + "fraction": 0.75 + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_DRONE_DEFEND", + "required_dist": 1, + "finish_on_arrival": true + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/repair_drone", + "vars": { + "target_key": "BB_DRONE_DEFEND" + } + } + ] + } + } + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/befriend_target/check_ally", + "vars": { + "target_key": "BB_DRONE_DEFEND", + "long_range_friendship": true, + "forget_target": false + } + } + ] + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_ORE_TARGET" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_ORE_TARGET", + "required_dist": 1, + "finish_on_arrival": true + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/collect_ore/minebot", + "vars": { + "target_key": "BB_ORE_TARGET" + } + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_true", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "key": "BB_AUTOMATED_MINING" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "key": "BB_TARGET_MINERAL_TURF" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_TARGET_MINERAL_TURF", + "required_dist": 2, + "finish_on_arrival": true + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/minebot_mine_turf", + "vars": { + "target_key": "BB_TARGET_MINERAL_TURF" + } + } + ] + } + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "time_between_perform": "5 SECONDS", + "target_key": "BB_DRONE_DEFEND", + "targeting_strategy": "/datum/targeting_strategy/anything", + "target_source": "/datum/target_source/oview_single_type/node_drone", + "vision_range": 9, + "revalidation_mode": "TARGET_REVALIDATE" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_true", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "key": "BB_AUTOMATED_MINING" + }, + "child": { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_ORE_TARGET", + "targeting_strategy": "/datum/targeting_strategy/pickup_item", + "target_source": "/datum/target_source/oview_single_type/ore", + "vision_range": 2 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/find_mineral_wall/minebot", + "vars": { + "target_key": "BB_TARGET_MINERAL_TURF" + } + } + ] + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/pets/cat/bread_cat_ai.dm b/code/modules/mob/living/basic/pets/cat/bread_cat_ai.dm deleted file mode 100644 index 655a6431fc2..00000000000 --- a/code/modules/mob/living/basic/pets/cat/bread_cat_ai.dm +++ /dev/null @@ -1,62 +0,0 @@ -/datum/ai_controller/basic_controller/cat/bread - planning_subtrees = list( - /datum/ai_planning_subtree/find_and_hunt_target/turn_off_stove, - /datum/ai_planning_subtree/find_and_hunt_target/hunt_mice, - /datum/ai_planning_subtree/find_and_hunt_target/find_cat_food, - /datum/ai_planning_subtree/haul_food_to_young, - /datum/ai_planning_subtree/random_speech/cats, - ) - -/datum/ai_planning_subtree/find_and_hunt_target/turn_off_stove - target_key = BB_STOVE_TARGET - hunting_behavior = /datum/ai_behavior/hunt_target/interact_with_target/stove_target - finding_behavior = /datum/ai_behavior/find_hunt_target/stove - hunt_targets = list(/obj/machinery/oven/range) - hunt_range = 9 - -/datum/ai_behavior/find_hunt_target/stove - -/datum/ai_behavior/find_hunt_target/stove/valid_dinner(mob/living/source, obj/machinery/oven/range/stove, radius) - if(!length(stove.used_tray?.contents) || stove.open) - return FALSE - //something in there is still baking... - for(var/atom/baking in stove.used_tray) - if(HAS_TRAIT(baking, TRAIT_BAKEABLE)) - return FALSE - return TRUE - -/datum/ai_behavior/hunt_target/interact_with_target/stove_target - always_reset_target = TRUE - -/datum/ai_behavior/hunt_target/interact_with_target/stove_target/target_caught(mob/living/hunter, obj/machinery/oven/range/stove) - if(stove.open) - return - return ..() - -/datum/ai_controller/basic_controller/cat/cake - planning_subtrees = list( - /datum/ai_planning_subtree/find_and_hunt_target/turn_off_stove, - /datum/ai_planning_subtree/find_and_hunt_target/decorate_donuts, - /datum/ai_planning_subtree/find_and_hunt_target/hunt_mice, - /datum/ai_planning_subtree/find_and_hunt_target/find_cat_food, - /datum/ai_planning_subtree/haul_food_to_young, - /datum/ai_planning_subtree/random_speech/cats, - ) - -/datum/ai_planning_subtree/find_and_hunt_target/decorate_donuts - target_key = BB_DONUT_TARGET - hunting_behavior = /datum/ai_behavior/hunt_target/decorate_donuts - finding_behavior = /datum/ai_behavior/find_hunt_target/decorate_donuts - hunt_targets = list(/obj/item/food/donut) - hunt_range = 9 - -/datum/ai_behavior/find_hunt_target/decorate_donuts/valid_dinner(mob/living/source, obj/item/food/donut/target, radius) - if(!target.is_decorated) - return FALSE - return can_see(source, target, radius) - -/datum/ai_behavior/hunt_target/decorate_donuts - always_reset_target = TRUE - -/datum/ai_behavior/hunt_target/decorate_donuts/target_caught(mob/living/hunter, atom/target) - hunter.spin(spintime = 4, speed = 1) diff --git a/code/modules/mob/living/basic/pets/cat/cat.bt.json b/code/modules/mob/living/basic/pets/cat/cat.bt.json new file mode 100644 index 00000000000..b8bb0004a3a --- /dev/null +++ b/code/modules/mob/living/basic/pets/cat/cat.bt.json @@ -0,0 +1,275 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/cat", + "type": "selector", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/pawn_buckled_to_obj", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/resist" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/pawn_grabbed_by_enemy", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/resist" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/pawn_is_restrained", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/resist" + } + } + ] + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree", + "override_id": "SUBPLAN_ID_PET_COMMAND" + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_BASIC_MOB_FLEE_TARGET", + "observer_abort": "BT_ABORT_LOWER_PRIORITY" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/run_away_from_target", + "bindings": { + "byk9gqj4": "BB_BASIC_MOB_FLEE_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/clear_key", + "vars": { + "key": "BB_BASIC_MOB_FLEE_TARGET" + } + } + ] + } + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "1 SECONDS", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/cat_reside_in_home" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/cat_haul_food" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/cat_hunt_mice" + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_TRESSPASSER_TARGET", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_TRESSPASSER_TARGET", + "required_dist": 1 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/territorial_struggle", + "vars": { + "target_key": "BB_TRESSPASSER_TARGET", + "cries_key": "BB_HOSTILE_MEOWS" + } + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_NONE", + "invert": true, + "key": "BB_MOUSE_TARGET" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CURRENT_TARGET", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/cat_find_food" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/make_babies" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop", + "bindings": { + "bqdqne64": "/datum/bt_node/ai_behavior/random_speech/cats" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/find_partner" + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/random_chance", + "vars": { + "chance": 0.05 + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_CAT_HOME", + "target_source": "/datum/target_source/oview_single_type/cat_house", + "targeting_strategy": "/datum/targeting_strategy/valid_cat_home" + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/random_chance", + "vars": { + "chance": 0.05 + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/find_cat_tresspasser", + "vars": { + "target_key": "BB_TRESSPASSER_TARGET" + } + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_MOUSE_TARGET", + "target_source": "/datum/target_source/oview_single_type/mouse", + "targeting_strategy": "/datum/targeting_strategy/huntable_mouse", + "vision_range": 9 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_CAT_FOOD_TARGET", + "target_source": "/datum/target_source/oview_typed/from_bb_key/huntable_prey", + "targeting_strategy": "/datum/targeting_strategy/cat_food", + "vision_range": 9 + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/pets/cat/cat_ai.dm b/code/modules/mob/living/basic/pets/cat/cat_ai.dm index 1150d17f96d..faf465f186a 100644 --- a/code/modules/mob/living/basic/pets/cat/cat_ai.dm +++ b/code/modules/mob/living/basic/pets/cat/cat_ai.dm @@ -5,288 +5,24 @@ BB_HOSTILE_MEOWS = list("Mawwww", "Mrewwww", "mhhhhng..."), BB_BABIES_PARTNER_TYPES = list(/mob/living/basic/pet/cat), BB_BABIES_CHILD_TYPES = list(/mob/living/basic/pet/cat/kitten), + BB_FUCKS = TRUE, ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity/pacifist, - /datum/ai_planning_subtree/pet_planning, - /datum/ai_planning_subtree/reside_in_home, - /datum/ai_planning_subtree/flee_target/from_flee_key/cat_struggle, - /datum/ai_planning_subtree/find_and_hunt_target/hunt_mice, - /datum/ai_planning_subtree/find_and_hunt_target/find_cat_food, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/haul_food_to_young, - /datum/ai_planning_subtree/territorial_struggle, - /datum/ai_planning_subtree/make_babies, - /datum/ai_planning_subtree/random_speech/cats, + behavior_tree_json = "code/modules/mob/living/basic/pets/cat/cat.bt.json" + +/datum/ai_controller/basic_controller/cat/kitten + blackboard = list( + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_HUNGRY_MEOW = list("mrrp...", "mraw..."), + BB_PET_TARGETING_STRATEGY = /datum/targeting_strategy/basic/not_friends, + BB_MAX_DISTANCE_TO_FOOD = 2, ) -/datum/ai_planning_subtree/reside_in_home - ///chance we enter our home - var/reside_chance = 5 - ///chance we leave our home - var/leave_home_chance = 15 - -/datum/ai_planning_subtree/reside_in_home/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/mob/living/living_pawn = controller.pawn - - if(controller.blackboard_key_exists(BB_CAT_HOME)) - controller.queue_behavior(/datum/ai_behavior/enter_cat_home, BB_CAT_HOME) - return - - if(istype(living_pawn.loc, /obj/structure/cat_house)) - if(SPT_PROB(leave_home_chance, seconds_per_tick)) - controller.set_blackboard_key(BB_CAT_HOME, living_pawn.loc) - return SUBTREE_RETURN_FINISH_PLANNING - - if(SPT_PROB(reside_chance, seconds_per_tick)) - controller.queue_behavior(/datum/ai_behavior/find_and_set/valid_home, BB_CAT_HOME, /obj/structure/cat_house) - -/datum/ai_behavior/find_and_set/valid_home/search_tactic(datum/ai_controller/controller, locate_path, search_range = SEARCH_TACTIC_DEFAULT_RANGE) - for(var/obj/structure/cat_house/home in oview(search_range, controller.pawn)) - if(home.resident_cat) - continue - return home - - return null - -/datum/ai_behavior/enter_cat_home - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION | AI_BEHAVIOR_REQUIRE_REACH - -/datum/ai_behavior/enter_cat_home/setup(datum/ai_controller/controller, target_key) - . = ..() - var/atom/target = controller.blackboard[target_key] - if(QDELETED(target)) - return FALSE - set_movement_target(controller, target) - -/datum/ai_behavior/enter_cat_home/perform(seconds_per_tick, datum/ai_controller/controller, target_key) - var/obj/structure/cat_house/home = controller.blackboard[target_key] - var/mob/living/basic/living_pawn = controller.pawn - if(living_pawn == home.resident_cat || isnull(home.resident_cat)) - controller.ai_interact(target = home) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - -/datum/ai_behavior/enter_cat_home/finish_action(datum/ai_controller/controller, success, target_key) - . = ..() - controller.clear_blackboard_key(target_key) - -/datum/ai_planning_subtree/flee_target/from_flee_key/cat_struggle - flee_behaviour = /datum/ai_behavior/run_away_from_target/cat_struggle - -/datum/ai_behavior/run_away_from_target/cat_struggle - clear_failed_targets = TRUE - -/datum/ai_planning_subtree/territorial_struggle - ///chance we become hostile to another cat - var/hostility_chance = 5 - -/datum/ai_planning_subtree/territorial_struggle/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/mob/living/living_pawn = controller.pawn - if(living_pawn.gender != MALE || !SPT_PROB(hostility_chance, seconds_per_tick)) - return - if(controller.blackboard_key_exists(BB_TRESSPASSER_TARGET)) - controller.queue_behavior(/datum/ai_behavior/territorial_struggle, BB_TRESSPASSER_TARGET, BB_HOSTILE_MEOWS) - return SUBTREE_RETURN_FINISH_PLANNING - - controller.queue_behavior(/datum/ai_behavior/find_and_set/cat_tresspasser, BB_TRESSPASSER_TARGET, /mob/living/basic/pet/cat) - -/datum/ai_behavior/find_and_set/cat_tresspasser/search_tactic(datum/ai_controller/controller, locate_path, search_range = SEARCH_TACTIC_DEFAULT_RANGE) - var/list/ignore_types = controller.blackboard[BB_BABIES_CHILD_TYPES] - for(var/mob/living/basic/pet/cat/potential_enemy in oview(search_range, controller.pawn)) - if(potential_enemy.gender != MALE) - continue - if(is_type_in_list(potential_enemy, ignore_types)) - continue - var/datum/ai_controller/basic_controller/enemy_controller = potential_enemy.ai_controller - if(isnull(enemy_controller)) - continue - //theyre already engaged in a battle, leave them alone! - if(enemy_controller.blackboard_key_exists(BB_TRESSPASSER_TARGET)) - continue - //u choose me and i choose u - enemy_controller.set_blackboard_key(BB_TRESSPASSER_TARGET, controller.pawn) - return potential_enemy - return null - -/datum/ai_behavior/territorial_struggle - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION | AI_BEHAVIOR_REQUIRE_REACH - action_cooldown = 5 SECONDS - ///chance the battle ends! - var/end_battle_chance = 25 - -/datum/ai_behavior/territorial_struggle/setup(datum/ai_controller/controller, target_key) - . = ..() - var/mob/living/living_pawn = controller.pawn - var/mob/living/target = controller.blackboard[target_key] - if(QDELETED(target)) - return FALSE - if(target.ai_controller?.blackboard[target_key] != living_pawn) - return FALSE - set_movement_target(controller, target) - -/datum/ai_behavior/territorial_struggle/perform(seconds_per_tick, datum/ai_controller/controller, target_key, cries_key) - . = ..() - var/mob/living/target = controller.blackboard[target_key] - - if(QDELETED(target)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - - var/mob/living/living_pawn = controller.pawn - var/list/threaten_list = controller.blackboard[cries_key] - if(length(threaten_list)) - living_pawn.say(pick(threaten_list), forced = "ai_controller") - - if(!prob(end_battle_chance)) - return - - //50 50 chance we lose - var/datum/ai_controller/loser_controller = prob(50) ? controller : target.ai_controller - - loser_controller.set_blackboard_key(BB_BASIC_MOB_FLEE_TARGET, target) - target.ai_controller.clear_blackboard_key(BB_TRESSPASSER_TARGET) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -/datum/ai_behavior/territorial_struggle/finish_action(datum/ai_controller/controller, success, target_key) - . = ..() - controller.clear_blackboard_key(target_key) - -/datum/ai_planning_subtree/find_and_hunt_target/hunt_mice - target_key = BB_MOUSE_TARGET - hunting_behavior = /datum/ai_behavior/play_with_mouse - finding_behavior = /datum/ai_behavior/find_hunt_target/hunt_mice - hunt_targets = list(/mob/living/basic/mouse) - hunt_chance = 75 - hunt_range = 9 - -/datum/ai_planning_subtree/find_and_hunt_target/hunt_mice/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/mob/living/living_pawn = controller.pawn - var/list/items_we_carry = typecache_filter_list(living_pawn, controller.blackboard[BB_HUNTABLE_PREY]) - if(length(items_we_carry)) - return - return ..() - - -/datum/ai_behavior/find_hunt_target/hunt_mice/valid_dinner(mob/living/source, mob/living/mouse, radius) - if(mouse.stat == DEAD || mouse.mind) - return FALSE - return can_see(source, mouse, radius) - -//play as in kill -/datum/ai_behavior/play_with_mouse - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION | AI_BEHAVIOR_REQUIRE_REACH - action_cooldown = 10 SECONDS - ///chance we hunt the mouse! - var/consume_chance = 70 - -/datum/ai_behavior/play_with_mouse/setup(datum/ai_controller/controller, target_key) - . = ..() - var/mob/living/target = controller.blackboard[target_key] - if(QDELETED(target)) - return FALSE - set_movement_target(controller, target) - -/datum/ai_behavior/play_with_mouse/perform(seconds_per_tick, datum/ai_controller/controller, target_key) - var/mob/living/basic/mouse/target = controller.blackboard[target_key] - - if(QDELETED(target)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - - consume_chance = istype(target, /mob/living/basic/mouse/brown/tom) ? 5 : initial(consume_chance) - if(prob(consume_chance)) - target.splat() - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - -/datum/ai_behavior/play_with_mouse/finish_action(datum/ai_controller/controller, success, target_key) - . = ..() - var/mob/living/living_pawn = controller.pawn - var/atom/target = controller.blackboard[target_key] - controller.clear_blackboard_key(target_key) - if(isnull(target) || QDELETED(living_pawn)) - return - var/manual_emote = "attempts to hunt [target]..." - var/end_result = success ? "and succeeds!" : "but fails!" - manual_emote += end_result - living_pawn.manual_emote(manual_emote) - -/datum/ai_planning_subtree/find_and_hunt_target/find_cat_food - target_key = BB_CAT_FOOD_TARGET - hunting_behavior = /datum/ai_behavior/hunt_target/interact_with_target/find_cat_food - finding_behavior = /datum/ai_behavior/find_hunt_target/find_cat_food - hunt_targets = list(/obj/item/fish, /obj/item/food/deadmouse, /obj/item/food/fishmeat) - hunt_chance = 75 - hunt_range = 9 - -/datum/ai_behavior/hunt_target/interact_with_target/find_cat_food - always_reset_target = TRUE - -/datum/ai_behavior/find_hunt_target/find_cat_food/valid_dinner(mob/living/source, atom/dinner, radius) - //this food is already near a kitten, let the kitten eat it - var/mob/living/nearby_kitten = locate(/mob/living/basic/pet/cat/kitten) in oview(2, dinner) - if(nearby_kitten && nearby_kitten != source) - return FALSE - return can_see(source, dinner, radius) - -/datum/ai_planning_subtree/haul_food_to_young/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(!controller.blackboard_key_exists(BB_FOOD_TO_DELIVER)) - controller.queue_behavior(/datum/ai_behavior/find_and_set/in_hands/given_list, BB_FOOD_TO_DELIVER, controller.blackboard[BB_HUNTABLE_PREY]) - return - if(!controller.blackboard_key_exists(BB_KITTEN_TO_FEED)) - controller.queue_behavior(/datum/ai_behavior/find_and_set/valid_kitten, BB_KITTEN_TO_FEED, /mob/living/basic/pet/cat/kitten) - return - - controller.queue_behavior(/datum/ai_behavior/deliver_food_to_kitten, BB_KITTEN_TO_FEED, BB_FOOD_TO_DELIVER) - -/datum/ai_behavior/find_and_set/valid_kitten - -/datum/ai_behavior/find_and_set/valid_kitten/search_tactic(datum/ai_controller/controller, locate_path, search_range = SEARCH_TACTIC_DEFAULT_RANGE) - var/mob/living/kitten = locate(locate_path) in oview(search_range, controller.pawn) - //kitten already has food near it, go feed another hungry kitten - - if(isnull(kitten)) - return null - - var/list/nearby_food = typecache_filter_list(oview(2, kitten), controller.blackboard[BB_HUNTABLE_PREY]) - if(kitten.stat != DEAD && !length(nearby_food)) - return kitten - return null - -/datum/ai_behavior/deliver_food_to_kitten - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION | AI_BEHAVIOR_REQUIRE_REACH - action_cooldown = 5 SECONDS - -/datum/ai_behavior/deliver_food_to_kitten/setup(datum/ai_controller/controller, target_key, food_key) - . = ..() - var/mob/living/target = controller.blackboard[target_key] - if(QDELETED(target)) - return FALSE - set_movement_target(controller, target) - -/datum/ai_behavior/deliver_food_to_kitten/perform(seconds_per_tick, datum/ai_controller/controller, target_key, food_key) - var/mob/living/target = controller.blackboard[target_key] - - if(QDELETED(target)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - var/mob/living/living_pawn = controller.pawn - var/atom/movable/food = controller.blackboard[food_key] - - if(isnull(food) || !(food in living_pawn)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - food.forceMove(get_turf(living_pawn)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -/datum/ai_behavior/deliver_food_to_kitten/finish_action(datum/ai_controller/controller, success, target_key, food_key) - . = ..() - controller.clear_blackboard_key(target_key) - controller.clear_blackboard_key(food_key) - - + behavior_tree_json = "code/modules/mob/living/basic/pets/cat/kitten.bt.json" +/datum/ai_controller/basic_controller/cat/bread + behavior_tree_json = "code/modules/mob/living/basic/pets/cat/cat_bread.bt.json" +/datum/ai_controller/basic_controller/cat/cake + behavior_tree_json = "code/modules/mob/living/basic/pets/cat/cat_cake.bt.json" diff --git a/code/modules/mob/living/basic/pets/cat/cat_bread.bt.json b/code/modules/mob/living/basic/pets/cat/cat_bread.bt.json new file mode 100644 index 00000000000..ca64dbb37b1 --- /dev/null +++ b/code/modules/mob/living/basic/pets/cat/cat_bread.bt.json @@ -0,0 +1,140 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/cat/bread", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity/pacifist" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree", + "override_id": "SUBPLAN_ID_PET_COMMAND" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "1 SECONDS", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/cat_turn_off_stove" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/cat_haul_food" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/cat_hunt_mice" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/cat_find_food" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop", + "bindings": { + "bqdqne64": "/datum/bt_node/ai_behavior/random_speech/cats" + } + } + ] + }, + { + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/find_partner" + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_MOUSE_TARGET", + "target_source": "/datum/target_source/oview_single_type/mouse", + "targeting_strategy": "/datum/targeting_strategy/huntable_mouse", + "vision_range": 9 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_CAT_FOOD_TARGET", + "target_source": "/datum/target_source/oview_typed/from_bb_key/huntable_prey", + "targeting_strategy": "/datum/targeting_strategy/cat_food", + "vision_range": 9 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_STOVE_TARGET", + "target_source": "/datum/target_source/oview_single_type/oven", + "targeting_strategy": "/datum/targeting_strategy/finished_stove", + "vision_range": 9 + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_FOOD_TO_DELIVER", + "invert": true + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_FOOD_TO_DELIVER", + "target_source": "/datum/target_source/carried_huntable_prey", + "targeting_strategy": "/datum/targeting_strategy/anything" + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_FOOD_TO_DELIVER", + "invert": true + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_FOOD_TO_DELIVER", + "target_source": "/datum/target_source/carried_huntable_prey", + "targeting_strategy": "/datum/targeting_strategy/anything" + } + } + } + ] + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/pets/cat/cat_bt.dm b/code/modules/mob/living/basic/pets/cat/cat_bt.dm new file mode 100644 index 00000000000..b7822938c04 --- /dev/null +++ b/code/modules/mob/living/basic/pets/cat/cat_bt.dm @@ -0,0 +1,259 @@ +// Behavior tree behaviors, subtrees and decorators for the cat AI family. + +/// Passes when the cat is currently carrying a piece of huntable food. +/datum/bt_node/decorator/cat_holding_food + +/datum/bt_node/decorator/cat_holding_food/check_condition(datum/ai_controller/controller) + var/mob/living/basic/pet/cat/cat = controller.pawn + return istype(cat) && !isnull(cat.held_food) + +/// Pounces on a mouse, occasionally killing it. Movement is handled externally. +/datum/bt_node/ai_behavior/play_with_mouse + time_between_perform = 10 SECONDS + /// Blackboard key holding the mouse we are hunting. + var/target_key + /// Chance we actually splat the mouse. + var/consume_chance = 70 + +/datum/bt_node/ai_behavior/play_with_mouse/setup(datum/ai_controller/controller) + var/mob/living/target = controller.blackboard[target_key] + return !QDELETED(target) + +/datum/bt_node/ai_behavior/play_with_mouse/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/basic/mouse/target = controller.blackboard[target_key] + if(QDELETED(target)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + var/chance = istype(target, /mob/living/basic/mouse/brown/tom) ? 5 : consume_chance + if(prob(chance)) + target.splat() + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + +/datum/bt_node/ai_behavior/play_with_mouse/finish_action(datum/ai_controller/controller, succeeded) + . = ..() + var/mob/living/living_pawn = controller.pawn + var/atom/target = controller.blackboard[target_key] + controller.clear_blackboard_key(target_key) + if(isnull(target) || QDELETED(living_pawn)) + return + var/manual_emote = "attempts to hunt [target]..." + manual_emote += succeeded ? "and succeeds!" : "but fails!" + living_pawn.manual_emote(manual_emote) + +/// Drops a piece of carried food at a kitten's feet. +/datum/bt_node/ai_behavior/deliver_food_to_kitten + time_between_perform = 5 SECONDS + /// Blackboard key holding the kitten to feed. + var/target_key + /// Blackboard key holding the food we are carrying. + var/food_key + +/datum/bt_node/ai_behavior/deliver_food_to_kitten/setup(datum/ai_controller/controller) + var/mob/living/target = controller.blackboard[target_key] + return !QDELETED(target) + +/datum/bt_node/ai_behavior/deliver_food_to_kitten/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/target = controller.blackboard[target_key] + if(QDELETED(target)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + var/mob/living/living_pawn = controller.pawn + var/atom/movable/food = controller.blackboard[food_key] + if(isnull(food) || !(food in living_pawn)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + food.forceMove(get_turf(living_pawn)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + +/datum/bt_node/ai_behavior/deliver_food_to_kitten/finish_action(datum/ai_controller/controller, succeeded) + . = ..() + controller.clear_blackboard_key(target_key) + controller.clear_blackboard_key(food_key) + +/// Engages a rival tom in a territorial yowling contest until one of them backs down +/datum/bt_node/ai_behavior/territorial_struggle + time_between_perform = 5 SECONDS + /// Blackboard key holding our rival. + var/target_key + /// Blackboard key holding the list of threatening cries. + var/cries_key + /// Chance the battle ends on each perform. + var/end_battle_chance = 25 + +/datum/bt_node/ai_behavior/territorial_struggle/setup(datum/ai_controller/controller) + var/mob/living/living_pawn = controller.pawn + var/mob/living/target = controller.blackboard[target_key] + if(QDELETED(target)) + return FALSE + // Only fight if our rival is still locked onto us. + if(target.ai_controller?.blackboard[target_key] != living_pawn) + return FALSE + return TRUE + +/datum/bt_node/ai_behavior/territorial_struggle/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/target = controller.blackboard[target_key] + if(QDELETED(target)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + + var/mob/living/living_pawn = controller.pawn + var/list/threaten_list = controller.blackboard[cries_key] + if(length(threaten_list)) + INVOKE_ASYNC(living_pawn, TYPE_PROC_REF(/atom/movable, say), pick(threaten_list), forced = "ai_controller") + + if(!prob(end_battle_chance)) + return AI_BEHAVIOR_DELAY + + // 50/50 chance we lose. + var/datum/ai_controller/loser_controller = prob(50) ? controller : target.ai_controller + loser_controller.set_blackboard_key(BB_BASIC_MOB_FLEE_TARGET, target) + target.ai_controller?.clear_blackboard_key(target_key) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + +/datum/bt_node/ai_behavior/territorial_struggle/finish_action(datum/ai_controller/controller, succeeded) + . = ..() + controller.clear_blackboard_key(target_key) + +/// Finds a rival male cat to fight, marking ourselves as their target too so the fight is mutual. +/datum/bt_node/ai_behavior/find_cat_tresspasser + time_between_perform = 5 SECONDS + /// Blackboard key to store the rival in. + var/target_key + /// How far to look for a rival. + var/search_range = 9 + +/datum/bt_node/ai_behavior/find_cat_tresspasser/setup(datum/ai_controller/controller) + var/mob/living/living_pawn = controller.pawn + return living_pawn.gender == MALE + +/datum/bt_node/ai_behavior/find_cat_tresspasser/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/living_pawn = controller.pawn + var/list/ignore_types = controller.blackboard[BB_BABIES_CHILD_TYPES] + for(var/mob/living/basic/pet/cat/potential_enemy in oview(search_range, living_pawn)) + if(potential_enemy.gender != MALE) + continue + if(is_type_in_list(potential_enemy, ignore_types)) + continue + var/datum/ai_controller/enemy_controller = potential_enemy.ai_controller + if(isnull(enemy_controller)) + continue + // They're already engaged in a battle, leave them alone! + if(enemy_controller.blackboard_key_exists(BB_TRESSPASSER_TARGET)) + continue + // You choose me and I choose you. + enemy_controller.set_blackboard_key(BB_TRESSPASSER_TARGET, living_pawn) + controller.set_blackboard_key(target_key, potential_enemy) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + +/// Points at a target and meows for food. Used to beg humans and call kittens to dinner. +/datum/bt_node/ai_behavior/beacon_for_food + time_between_perform = 5 SECONDS + /// Blackboard key holding the atom we are pointing at. + var/target_key + /// Blackboard key holding the list of hungry meows. + var/meows_key + +/datum/bt_node/ai_behavior/beacon_for_food/setup(datum/ai_controller/controller) + var/atom/target = controller.blackboard[target_key] + return !QDELETED(target) + +/datum/bt_node/ai_behavior/beacon_for_food/perform(seconds_per_tick, datum/ai_controller/controller) + var/atom/target = controller.blackboard[target_key] + if(QDELETED(target)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + var/mob/living/living_pawn = controller.pawn + var/list/meowing_list = controller.blackboard[meows_key] + if(length(meowing_list)) + INVOKE_ASYNC(living_pawn, TYPE_PROC_REF(/atom/movable, say), pick(meowing_list), forced = "ai_controller") + INVOKE_ASYNC(living_pawn, TYPE_PROC_REF(/mob/living, _pointed), target) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + +/datum/bt_node/ai_behavior/beacon_for_food/finish_action(datum/ai_controller/controller, succeeded) + . = ..() + controller.clear_blackboard_key(target_key) + +/// Finds a conscious human carrying food worth begging for. +/datum/bt_node/ai_behavior/find_human_to_beg + /// Blackboard key to store the human in. + var/target_key + /// How far to look for a human. + var/search_range = 9 + +/datum/bt_node/ai_behavior/find_human_to_beg/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/living_pawn = controller.pawn + var/list/locate_items = controller.blackboard[BB_HUNTABLE_PREY] + for(var/mob/living/carbon/human/human_target in oview(search_range, living_pawn)) + if(human_target.stat != CONSCIOUS || isnull(human_target.mind)) + continue + for(var/obj/item/held_item in human_target.held_items) + if(is_type_in_typecache(held_item, locate_items)) + controller.set_blackboard_key(target_key, human_target) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + +/// Leaves the cat house we are currently residing in, occasionally. +/datum/bt_node/ai_behavior/leave_cat_home + /// Chance per attempt that we decide to leave. + var/leave_home_chance = 15 + +/datum/bt_node/ai_behavior/leave_cat_home/perform(seconds_per_tick, datum/ai_controller/controller) + var/obj/structure/cat_house/home = controller.pawn.loc + if(!istype(home)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + if(!prob(leave_home_chance)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + INVOKE_ASYNC(controller, TYPE_PROC_REF(/datum/ai_controller, ai_interact), home, FALSE) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED + +/// Celebrates around a decorated donut with a spin. +/datum/bt_node/ai_behavior/hunt_target/decorate_donuts + always_reset_target = TRUE + +/datum/bt_node/ai_behavior/hunt_target/decorate_donuts/target_caught(mob/living/hunter, atom/hunted) + hunter.spin(spintime = 4, speed = 1) + +/// Enters (or exits if already resident) a cat house keyed in target_key. +/datum/bt_node/ai_behavior/enter_cat_home + var/target_key + +/datum/bt_node/ai_behavior/enter_cat_home/setup(datum/ai_controller/controller) + var/obj/structure/cat_house/home = controller.blackboard[target_key] + return !QDELETED(home) + +/datum/bt_node/ai_behavior/enter_cat_home/perform(seconds_per_tick, datum/ai_controller/controller) + var/obj/structure/cat_house/home = controller.blackboard[target_key] + if(QDELETED(home)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + var/mob/living/basic/pet/cat/living_pawn = controller.pawn + if(living_pawn == home.resident_cat || isnull(home.resident_cat)) + INVOKE_ASYNC(controller, TYPE_PROC_REF(/datum/ai_controller, ai_interact), home, FALSE) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + +/datum/bt_node/ai_behavior/enter_cat_home/finish_action(datum/ai_controller/controller, succeeded) + . = ..() + controller.clear_blackboard_key(target_key) + +// Subtree types the trees themselves live in the matching .bt.json files. + +/// Block other behaviors while residing in a cat house; occasionally leave. +/datum/bt_node/subtree/cat_reside_in_home + behavior_tree_json = "code/modules/mob/living/basic/pets/cat/cat_reside_in_home.bt.json" + +/// Find a mouse and pounce on it. +/datum/bt_node/subtree/cat_hunt_mice + behavior_tree_json = "code/modules/mob/living/basic/pets/cat/cat_hunt_mice.bt.json" + +/// Find dropped food on the ground and eat it. +/datum/bt_node/subtree/cat_find_food + behavior_tree_json = "code/modules/mob/living/basic/pets/cat/cat_find_food.bt.json" + +/// Carry food we are holding to a hungry kitten. +/datum/bt_node/subtree/cat_haul_food + behavior_tree_json = "code/modules/mob/living/basic/pets/cat/cat_haul_food.bt.json" + +/// Turn off a finished oven. +/datum/bt_node/subtree/cat_turn_off_stove + behavior_tree_json = "code/modules/mob/living/basic/pets/cat/cat_turn_off_stove.bt.json" + +/// Spin to decorate nearby donuts. +/datum/bt_node/subtree/cat_decorate_donuts + behavior_tree_json = "code/modules/mob/living/basic/pets/cat/cat_decorate_donuts.bt.json" diff --git a/code/modules/mob/living/basic/pets/cat/cat_cake.bt.json b/code/modules/mob/living/basic/pets/cat/cat_cake.bt.json new file mode 100644 index 00000000000..a9abb2507d9 --- /dev/null +++ b/code/modules/mob/living/basic/pets/cat/cat_cake.bt.json @@ -0,0 +1,154 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/cat/cake", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity/pacifist" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree", + "override_id": "SUBPLAN_ID_PET_COMMAND" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "1 SECONDS", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/cat_turn_off_stove" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/cat_decorate_donuts" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/cat_haul_food" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/cat_hunt_mice" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/cat_find_food" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop", + "bindings": { + "bqdqne64": "/datum/bt_node/ai_behavior/random_speech/cats" + } + } + ] + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/find_partner" + }, + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_MOUSE_TARGET", + "target_source": "/datum/target_source/oview_single_type/mouse", + "targeting_strategy": "/datum/targeting_strategy/huntable_mouse", + "vision_range": 9 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_CAT_FOOD_TARGET", + "target_source": "/datum/target_source/oview_typed/from_bb_key/huntable_prey", + "targeting_strategy": "/datum/targeting_strategy/cat_food", + "vision_range": 9 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_STOVE_TARGET", + "target_source": "/datum/target_source/oview_single_type/oven", + "targeting_strategy": "/datum/targeting_strategy/finished_stove", + "vision_range": 9 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_DONUT_TARGET", + "target_source": "/datum/target_source/oview_single_type/donut", + "targeting_strategy": "/datum/targeting_strategy/decorated_donut", + "vision_range": 9 + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_FOOD_TO_DELIVER", + "invert": true + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_FOOD_TO_DELIVER", + "target_source": "/datum/target_source/carried_huntable_prey", + "targeting_strategy": "/datum/targeting_strategy/anything" + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_FOOD_TO_DELIVER", + "invert": true + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_FOOD_TO_DELIVER", + "target_source": "/datum/target_source/carried_huntable_prey", + "targeting_strategy": "/datum/targeting_strategy/anything" + } + } + } + ] + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/pets/cat/cat_decorate_donuts.bt.json b/code/modules/mob/living/basic/pets/cat/cat_decorate_donuts.bt.json new file mode 100644 index 00000000000..70bab944828 --- /dev/null +++ b/code/modules/mob/living/basic/pets/cat/cat_decorate_donuts.bt.json @@ -0,0 +1,29 @@ +{ + "dm_type": "/datum/bt_node/subtree/cat_decorate_donuts", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_DONUT_TARGET", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_DONUT_TARGET", + "required_dist": 1 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/hunt_target/decorate_donuts", + "vars": { + "target_key": "BB_DONUT_TARGET" + } + } + ] + } +} diff --git a/code/modules/mob/living/basic/pets/cat/cat_find_food.bt.json b/code/modules/mob/living/basic/pets/cat/cat_find_food.bt.json new file mode 100644 index 00000000000..7d5201039ad --- /dev/null +++ b/code/modules/mob/living/basic/pets/cat/cat_find_food.bt.json @@ -0,0 +1,30 @@ +{ + "dm_type": "/datum/bt_node/subtree/cat_find_food", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CAT_FOOD_TARGET", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CAT_FOOD_TARGET", + "required_dist": 1 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/hunt_target/interact_with_target", + "vars": { + "target_key": "BB_CAT_FOOD_TARGET", + "always_reset_target": true + } + } + ] + } +} diff --git a/code/modules/mob/living/basic/pets/cat/cat_haul_food.bt.json b/code/modules/mob/living/basic/pets/cat/cat_haul_food.bt.json new file mode 100644 index 00000000000..cbe66fcacf6 --- /dev/null +++ b/code/modules/mob/living/basic/pets/cat/cat_haul_food.bt.json @@ -0,0 +1,41 @@ +{ + "dm_type": "/datum/bt_node/subtree/cat_haul_food", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/cat_holding_food", + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_KITTEN_TO_FEED", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_FOOD_TO_DELIVER" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_KITTEN_TO_FEED", + "required_dist": 1 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/deliver_food_to_kitten", + "vars": { + "target_key": "BB_KITTEN_TO_FEED", + "food_key": "BB_FOOD_TO_DELIVER" + } + } + ] + } + } + } +} diff --git a/code/modules/mob/living/basic/pets/cat/cat_hunt_mice.bt.json b/code/modules/mob/living/basic/pets/cat/cat_hunt_mice.bt.json new file mode 100644 index 00000000000..1235d8b981d --- /dev/null +++ b/code/modules/mob/living/basic/pets/cat/cat_hunt_mice.bt.json @@ -0,0 +1,36 @@ +{ + "dm_type": "/datum/bt_node/subtree/cat_hunt_mice", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/cat_holding_food", + "vars": { + "invert": true + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_MOUSE_TARGET", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_MOUSE_TARGET", + "required_dist": 1 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/play_with_mouse", + "vars": { + "target_key": "BB_MOUSE_TARGET" + } + } + ] + } + } +} diff --git a/code/modules/mob/living/basic/pets/cat/cat_reside_in_home.bt.json b/code/modules/mob/living/basic/pets/cat/cat_reside_in_home.bt.json new file mode 100644 index 00000000000..efd2ef664ad --- /dev/null +++ b/code/modules/mob/living/basic/pets/cat/cat_reside_in_home.bt.json @@ -0,0 +1,55 @@ +{ + "dm_type": "/datum/bt_node/subtree/cat_reside_in_home", + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/pawn_loc_is_type", + "vars": { + "loc_type": "/obj/structure/cat_house", + "observer_abort": "BT_ABORT_LOWER_PRIORITY" + }, + "child": { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/leave_cat_home" + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/succeed" + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CAT_HOME", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CAT_HOME", + "required_dist": 1 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/enter_cat_home", + "vars": { + "target_key": "BB_CAT_HOME" + } + } + ] + } + } + ] +} diff --git a/code/modules/mob/living/basic/pets/cat/cat_turn_off_stove.bt.json b/code/modules/mob/living/basic/pets/cat/cat_turn_off_stove.bt.json new file mode 100644 index 00000000000..b7f84d677d9 --- /dev/null +++ b/code/modules/mob/living/basic/pets/cat/cat_turn_off_stove.bt.json @@ -0,0 +1,30 @@ +{ + "dm_type": "/datum/bt_node/subtree/cat_turn_off_stove", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_STOVE_TARGET", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_STOVE_TARGET", + "required_dist": 1 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/hunt_target/interact_with_target", + "vars": { + "target_key": "BB_STOVE_TARGET", + "always_reset_target": true + } + } + ] + } +} diff --git a/code/modules/mob/living/basic/pets/cat/kitten.bt.json b/code/modules/mob/living/basic/pets/cat/kitten.bt.json new file mode 100644 index 00000000000..7809ec2efff --- /dev/null +++ b/code/modules/mob/living/basic/pets/cat/kitten.bt.json @@ -0,0 +1,151 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/cat/kitten", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree", + "override_id": "SUBPLAN_ID_PET_COMMAND" + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CURRENT_TARGET", + "observer_abort": "BT_ABORT_LOWER_PRIORITY" + }, + "child": { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/run_away_from_target" + } + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "1 SECONDS", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_HUMAN_BEG_TARGET", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/beacon_for_food", + "vars": { + "target_key": "BB_HUMAN_BEG_TARGET", + "meows_key": "BB_HUNGRY_MEOW" + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CAT_FOOD_TARGET", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/pawn_farther_than_from_key", + "vars": { + "anchor_key": "BB_CAT_FOOD_TARGET", + "distance_key": "BB_MAX_DISTANCE_TO_FOOD" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/beacon_for_food", + "vars": { + "target_key": "BB_CAT_FOOD_TARGET", + "meows_key": "BB_HUNGRY_MEOW" + } + } + }, + { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CAT_FOOD_TARGET", + "required_dist": 1 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/hunt_target/interact_with_target", + "vars": { + "target_key": "BB_CAT_FOOD_TARGET", + "always_reset_target": true + } + } + ] + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop", + "bindings": { + "bqdqne64": "/datum/bt_node/ai_behavior/random_speech/cats" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_CAT_FOOD_TARGET", + "target_source": "/datum/target_source/oview_typed/from_bb_key/huntable_prey", + "targeting_strategy": "/datum/targeting_strategy/cat_food", + "vision_range": 9 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/find_human_to_beg", + "vars": { + "target_key": "BB_HUMAN_BEG_TARGET" + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/pets/cat/kitten_ai.dm b/code/modules/mob/living/basic/pets/cat/kitten_ai.dm deleted file mode 100644 index 952c7aabd68..00000000000 --- a/code/modules/mob/living/basic/pets/cat/kitten_ai.dm +++ /dev/null @@ -1,67 +0,0 @@ - -/datum/ai_controller/basic_controller/cat/kitten - blackboard = list( - BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, - BB_HUNGRY_MEOW = list("mrrp...", "mraw..."), - BB_PET_TARGETING_STRATEGY = /datum/targeting_strategy/basic/not_friends, - BB_MAX_DISTANCE_TO_FOOD = 2, - ) - - planning_subtrees = list( - /datum/ai_planning_subtree/pet_planning, - /datum/ai_planning_subtree/target_retaliate, - /datum/ai_planning_subtree/flee_target, - /datum/ai_planning_subtree/beg_human, - /datum/ai_planning_subtree/find_and_hunt_target/find_cat_food/kitten, - /datum/ai_planning_subtree/random_speech/cats, - ) - -//if the food is too far away, point at it or meow. if its near us then go eat it - -/datum/ai_planning_subtree/find_and_hunt_target/find_cat_food/kitten - - -/datum/ai_planning_subtree/find_and_hunt_target/find_cat_food/kitten/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/atom/target = controller.blackboard[BB_CAT_FOOD_TARGET] - if(target && get_dist(target, controller.pawn) > controller.blackboard[BB_MAX_DISTANCE_TO_FOOD]) - controller.queue_behavior(/datum/ai_behavior/beacon_for_food, BB_CAT_FOOD_TARGET, BB_HUNGRY_MEOW) - return - return ..() - -/datum/ai_behavior/beacon_for_food - action_cooldown = 5 SECONDS - -/datum/ai_behavior/beacon_for_food/perform(seconds_per_tick, datum/ai_controller/controller, target_key, meows_key) - var/atom/target = controller.blackboard[target_key] - if(QDELETED(target)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - var/mob/living/living_pawn = controller.pawn - var/list/meowing_list = controller.blackboard[meows_key] - if(length(meowing_list)) - living_pawn.say(pick(meowing_list), forced = "ai_controller") - living_pawn._pointed(target) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -/datum/ai_behavior/beacon_for_food/finish_action(datum/ai_controller/controller, success, target_key) - . = ..() - controller.clear_blackboard_key(target_key) - -/datum/ai_planning_subtree/beg_human - -/datum/ai_planning_subtree/beg_human/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - - if(controller.blackboard_key_exists(BB_HUMAN_BEG_TARGET)) - controller.queue_behavior(/datum/ai_behavior/beacon_for_food, BB_HUMAN_BEG_TARGET, BB_HUNGRY_MEOW) - return - - controller.queue_behavior(/datum/ai_behavior/find_and_set/human_beg, BB_HUMAN_BEG_TARGET, /mob/living/carbon/human) - -/datum/ai_behavior/find_and_set/human_beg/search_tactic(datum/ai_controller/controller, locate_path, search_range = SEARCH_TACTIC_DEFAULT_RANGE) - var/list/locate_items = controller.blackboard[BB_HUNTABLE_PREY] - for(var/mob/living/carbon/human/human_target in oview(search_range, controller.pawn)) - if(human_target.stat != CONSCIOUS || isnull(human_target.mind)) - continue - for (var/obj/item/held_item in human_target.held_items) - if (is_type_in_typecache(held_item, locate_items)) - return human_target - return null diff --git a/code/modules/mob/living/basic/pets/dog/_dog.dm b/code/modules/mob/living/basic/pets/dog/_dog.dm index cd98fcf1f0a..2f4d9485fde 100644 --- a/code/modules/mob/living/basic/pets/dog/_dog.dm +++ b/code/modules/mob/living/basic/pets/dog/_dog.dm @@ -7,9 +7,9 @@ /datum/pet_command/good_boy/dog speech_commands = list("good dog") -// Set correct attack behaviour +// Use dog-specific melee attack (paws harmlessly when BB_DOG_HARASS_HARM is false) /datum/pet_command/attack/dog - attack_behaviour = /datum/ai_behavior/basic_melee_attack/dog + attack_subtree = /datum/bt_node/subtree/pet_command/attack/dog /datum/pet_command/attack/dog/set_command_active(mob/living/parent, mob/living/commander) . = ..() @@ -86,10 +86,21 @@ break ///Updates dog speech and emotes -/mob/living/basic/pet/dog/proc/update_dog_speech(datum/ai_planning_subtree/random_speech/speech) - speech.speak = string_list(list("YAP", "Woof!", "Bark!", "AUUUUUU")) - speech.emote_hear = string_list(list("barks!", "woofs!", "yaps.","pants.")) - speech.emote_see = string_list(list("shakes [p_their()] head.", "chases [p_their()] tail.","shivers.")) +/mob/living/basic/pet/dog/proc/update_dog_speech(list/speech_data) + speech_data[BB_EMOTE_SAY] = string_list(list("YAP", "Woof!", "Bark!", "AUUUUUU")) + speech_data[BB_EMOTE_HEAR] = string_list(list("barks!", "woofs!", "yaps.","pants.")) + speech_data[BB_EMOTE_SEE] = string_list(list("shakes [p_their()] head.", "chases [p_their()] tail.","shivers.")) + + +/// Populates BB_BASIC_MOB_SPEAK_LINES with the dog's current speech data for BT random speech. +/// Subtypes override this to apply fashion accessories or variant speech. +/mob/living/basic/pet/dog/proc/update_dog_speak_blackboard(datum/ai_controller/controller) + var/list/speech_data = list() + speech_data[BB_EMOTE_SAY] = list("YAP", "Woof!", "Bark!", "AUUUUUU") + speech_data[BB_EMOTE_HEAR] = list("barks!", "woofs!", "yaps.", "pants.") + speech_data[BB_EMOTE_SEE] = list("shakes [p_their()] head.", "chases [p_their()] tail.", "shivers.") + speech_data[BB_SPEAK_CHANCE] = 1 + controller.override_blackboard_key(BB_BASIC_MOB_SPEAK_LINES, speech_data) ///Proc to run on a successful taming attempt /mob/living/basic/pet/dog/tamed(mob/living/tamer, atom/food) diff --git a/code/modules/mob/living/basic/pets/dog/corgi.dm b/code/modules/mob/living/basic/pets/dog/corgi.dm index 0e514005aa5..4109d22e679 100644 --- a/code/modules/mob/living/basic/pets/dog/corgi.dm +++ b/code/modules/mob/living/basic/pets/dog/corgi.dm @@ -135,16 +135,23 @@ return ..() -/mob/living/basic/pet/dog/corgi/update_dog_speech(datum/ai_planning_subtree/random_speech/speech) +/mob/living/basic/pet/dog/corgi/update_dog_speech(list/speech_data) . = ..() - if(inventory_head?.dog_fashion) var/datum/dog_fashion/equipped_head_fashion_item = new inventory_head.dog_fashion(src) - equipped_head_fashion_item.apply_to_speech(speech) + equipped_head_fashion_item.apply_to_speech(speech_data) if(inventory_back?.dog_fashion) var/datum/dog_fashion/equipped_back_fashion_item = new inventory_back.dog_fashion(src) - equipped_back_fashion_item.apply_to_speech(speech) + equipped_back_fashion_item.apply_to_speech(speech_data) + +/// Applies corgi fashion to the BT blackboard speech data by routing through the planning stub. +/mob/living/basic/pet/dog/corgi/update_dog_speak_blackboard(datum/ai_controller/controller) + var/list/speech_data = list() + update_dog_speech(speech_data) + if(!speech_data[BB_SPEAK_CHANCE]) + speech_data[BB_SPEAK_CHANCE] = 1 + controller.override_blackboard_key(BB_BASIC_MOB_SPEAK_LINES, speech_data) /mob/living/basic/pet/dog/corgi/deadchat_plays(mode = ANARCHY_MODE, cooldown = 12 SECONDS) . = AddComponent(/datum/component/deadchat_control/cardinal_movement, mode, list( @@ -508,10 +515,10 @@ . = ..() speak_emote = list("growls", "barks ominously") -/mob/living/basic/pet/dog/corgi/narsie/update_dog_speech(datum/ai_planning_subtree/random_speech/speech) - speech.speak = string_list(list("Tari'karat-pasnar!", "IA! IA!", "BRRUUURGHGHRHR")) - speech.emote_hear = string_list(list("barks echoingly!", "woofs hauntingly!", "yaps in an eldritch manner.", "mutters something unspeakable.")) - speech.emote_see = string_list(list("communes with the unnameable.", "ponders devouring some souls.", "shakes.")) +/mob/living/basic/pet/dog/corgi/narsie/update_dog_speech(list/speech_data) + speech_data[BB_EMOTE_SAY] = string_list(list("Tari'karat-pasnar!", "IA! IA!", "BRRUUURGHGHRHR")) + speech_data[BB_EMOTE_HEAR] = string_list(list("barks echoingly!", "woofs hauntingly!", "yaps in an eldritch manner.", "mutters something unspeakable.")) + speech_data[BB_EMOTE_SEE] = string_list(list("communes with the unnameable.", "ponders devouring some souls.", "shakes.")) /mob/living/basic/pet/dog/corgi/narsie/narsie_act() if(stat == DEAD) //Nar'Sie loves her doggy diff --git a/code/modules/mob/living/basic/pets/dog/dog_subtypes.dm b/code/modules/mob/living/basic/pets/dog/dog_subtypes.dm index 3e2dc5e5649..a59fdef2ff2 100644 --- a/code/modules/mob/living/basic/pets/dog/dog_subtypes.dm +++ b/code/modules/mob/living/basic/pets/dog/dog_subtypes.dm @@ -64,6 +64,7 @@ ai_controller = /datum/ai_controller/basic_controller/guarddog /datum/ai_controller/basic_controller/guarddog + behavior_tree_json = "code/modules/mob/living/basic/pets/dog/guarddog.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_PET_TARGETING_STRATEGY = /datum/targeting_strategy/basic/not_friends, @@ -72,12 +73,6 @@ ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/target_retaliate, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) /mob/living/basic/pet/dog/breaddog //Most of the code originates from Cak name = "Kobun" diff --git a/code/modules/mob/living/basic/pets/dog/guarddog.bt.json b/code/modules/mob/living/basic/pets/dog/guarddog.bt.json new file mode 100644 index 00000000000..588d575dd9a --- /dev/null +++ b/code/modules/mob/living/basic/pets/dog/guarddog.bt.json @@ -0,0 +1,5 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/guarddog", + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_retaliate_combat" +} diff --git a/code/modules/mob/living/basic/pets/fox.bt.json b/code/modules/mob/living/basic/pets/fox.bt.json new file mode 100644 index 00000000000..8a9c017ee55 --- /dev/null +++ b/code/modules/mob/living/basic/pets/fox.bt.json @@ -0,0 +1,133 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/fox", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree", + "override_id": "SUBPLAN_ID_PET_COMMAND" + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_BASIC_MOB_FLEE_TARGET", + "observer_abort": "BT_ABORT_LOWER_PRIORITY" + }, + "child": { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/run_away_from_target", + "bindings": { + "byk9gqj4": "BB_BASIC_MOB_FLEE_TARGET" + } + } + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "1 SECONDS", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop", + "bindings": { + "bqdqne64": "/datum/bt_node/ai_behavior/random_speech/fox" + } + } + ] + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/no_humans_watching", + "vars": { + "polling_rate": "2 SECONDS" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list", + "vars": { + "target_key": "BB_BASIC_MOB_FLEE_TARGET", + "targeting_strategy": "BB_FLEE_TARGETING_STRATEGY", + "hiding_location_key": "BB_BASIC_MOB_FLEE_TARGET_HIDING_LOCATION" + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/pets/fox.dm b/code/modules/mob/living/basic/pets/fox.dm index 54d2df8fab1..b721d43f14e 100644 --- a/code/modules/mob/living/basic/pets/fox.dm +++ b/code/modules/mob/living/basic/pets/fox.dm @@ -68,24 +68,11 @@ ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/pet_planning, - /datum/ai_planning_subtree/target_retaliate/to_flee, - /datum/ai_planning_subtree/flee_target/from_flee_key, - /datum/ai_planning_subtree/simple_find_target/not_while_observed, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/random_speech/fox, - ) + behavior_tree_json = "code/modules/mob/living/basic/pets/fox.bt.json" // An AI controller for more docile foxes. /datum/ai_controller/basic_controller/fox/docile - planning_subtrees = list( - /datum/ai_planning_subtree/target_retaliate/to_flee, - /datum/ai_planning_subtree/flee_target/from_flee_key, - /datum/ai_planning_subtree/random_speech/fox, - ) + behavior_tree_json = "code/modules/mob/living/basic/pets/fox_docile.bt.json" // The captain's fox, Renault /mob/living/basic/pet/fox/renault diff --git a/code/modules/mob/living/basic/pets/fox_docile.bt.json b/code/modules/mob/living/basic/pets/fox_docile.bt.json new file mode 100644 index 00000000000..19f92dc23a3 --- /dev/null +++ b/code/modules/mob/living/basic/pets/fox_docile.bt.json @@ -0,0 +1,61 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/fox/docile", + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_BASIC_MOB_FLEE_TARGET", + "observer_abort": "BT_ABORT_LOWER_PRIORITY" + }, + "child": { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/run_away_from_target", + "bindings": { + "byk9gqj4": "BB_BASIC_MOB_FLEE_TARGET" + } + } + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "1 SECONDS", + "finish_on_primary": true, + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop", + "bindings": { + "bqdqne64": "/datum/bt_node/ai_behavior/random_speech/fox" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list", + "vars": { + "target_key": "BB_BASIC_MOB_FLEE_TARGET", + "targeting_strategy": "BB_FLEE_TARGETING_STRATEGY", + "hiding_location_key": "BB_BASIC_MOB_FLEE_TARGET_HIDING_LOCATION" + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/pets/gondolas/gondola.bt.json b/code/modules/mob/living/basic/pets/gondolas/gondola.bt.json new file mode 100644 index 00000000000..e1e36d0a326 --- /dev/null +++ b/code/modules/mob/living/basic/pets/gondolas/gondola.bt.json @@ -0,0 +1,8 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/gondola", + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk", + "bindings": { + "bf07i8ep": "10" + } +} diff --git a/code/modules/mob/living/basic/pets/gondolas/gondola.dm b/code/modules/mob/living/basic/pets/gondolas/gondola.dm index 8e235dc9688..7810317381f 100644 --- a/code/modules/mob/living/basic/pets/gondolas/gondola.dm +++ b/code/modules/mob/living/basic/pets/gondolas/gondola.dm @@ -69,13 +69,13 @@ add_overlay(moustache_overlay) /datum/ai_controller/basic_controller/gondola + behavior_tree_json = "code/modules/mob/living/basic/pets/gondolas/gondola.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, ) ai_traits = PASSIVE_AI_FLAGS ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk/less_walking #undef GONDOLA_HEIGHT #undef GONDOLA_COLOR diff --git a/code/modules/mob/living/basic/pets/orbie/orbie.bt.json b/code/modules/mob/living/basic/pets/orbie/orbie.bt.json new file mode 100644 index 00000000000..1677fa9e432 --- /dev/null +++ b/code/modules/mob/living/basic/pets/orbie/orbie.bt.json @@ -0,0 +1,100 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/orbie", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree", + "override_id": "SUBPLAN_ID_PET_COMMAND" + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_NEARBY_PLAYMATE", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_NEARBY_PLAYMATE", + "required_dist": 1, + "finish_on_arrival": false + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/interact_with_playmate", + "vars": { + "target_key": "BB_NEARBY_PLAYMATE" + } + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "key": "BB_LAST_RECEIVED_MESSAGE" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/relay_pda_message", + "vars": { + "target_key": "BB_LAST_RECEIVED_MESSAGE" + } + } + } + ] + }, + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/move_to_and_eat" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + } + ] + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/key_off_cooldown", + "vars": { + "cooldown_key": "BB_NEXT_PLAYDATE" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target/find_playmate" + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/find_food" + } + ] +} diff --git a/code/modules/mob/living/basic/pets/orbie/orbie_ai.dm b/code/modules/mob/living/basic/pets/orbie/orbie_ai.dm index 900154b4c9a..8fbc7340fce 100644 --- a/code/modules/mob/living/basic/pets/orbie/orbie_ai.dm +++ b/code/modules/mob/living/basic/pets/orbie/orbie_ai.dm @@ -2,6 +2,7 @@ #define MESSAGE_EXPIRY_TIME (30 SECONDS) /datum/ai_controller/basic_controller/orbie + behavior_tree_json = "code/modules/mob/living/basic/pets/orbie/orbie.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_PET_TARGETING_STRATEGY = /datum/targeting_strategy/basic/not_friends, @@ -9,13 +10,6 @@ ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/find_food, - /datum/ai_planning_subtree/find_playmates, - /datum/ai_planning_subtree/relay_pda_message, - /datum/ai_planning_subtree/pet_planning, - ) /datum/ai_controller/basic_controller/orbie/TryPossessPawn(atom/new_pawn) . = ..() @@ -28,42 +22,36 @@ addtimer(CALLBACK(src, PROC_REF(clear_blackboard_key), BB_LAST_RECEIVED_MESSAGE), MESSAGE_EXPIRY_TIME) -///ai behavior that lets us search for other orbies to play with -/datum/ai_planning_subtree/find_playmates +/// Finds a nearby free orbie to play with and mutually registers as playmates. +/datum/bt_node/ai_behavior/acquire_target/update_interaction_target/find_playmate + target_key = BB_NEARBY_PLAYMATE + target_source = /datum/target_source/oview_single_type/orbie + targeting_strategy = /datum/targeting_strategy/playmate -/datum/ai_planning_subtree/find_playmates/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(controller.blackboard[BB_NEXT_PLAYDATE] > world.time) - return - if(controller.blackboard_key_exists(BB_NEARBY_PLAYMATE)) - controller.queue_behavior(/datum/ai_behavior/interact_with_playmate, BB_NEARBY_PLAYMATE) - return SUBTREE_RETURN_FINISH_PLANNING +/datum/bt_node/ai_behavior/acquire_target/update_interaction_target/find_playmate/on_target_found(datum/ai_controller/controller, atom/target, datum/targeting_strategy/strategy) + var/mob/living/basic/orbie/playmate = target + playmate.ai_controller.set_blackboard_key(BB_NEARBY_PLAYMATE, controller.pawn) - controller.queue_behavior(/datum/ai_behavior/find_and_set/find_playmate, BB_NEARBY_PLAYMATE, /mob/living/basic/orbie) +/// Accepts an orbie that is free to play (no current playmate and not on playdate cooldown). +/datum/targeting_strategy/playmate -/datum/ai_behavior/find_and_set/find_playmate - -/datum/ai_behavior/find_and_set/find_playmate/search_tactic(datum/ai_controller/controller, locate_path, search_range = SEARCH_TACTIC_DEFAULT_RANGE) - for(var/mob/living/basic/orbie/playmate in oview(search_range, controller.pawn)) - if(playmate == controller.pawn || playmate.stat == DEAD || isnull(playmate.ai_controller)) - continue - if(playmate.ai_controller.blackboard[BB_NEARBY_PLAYMATE] || playmate.ai_controller.blackboard[BB_NEXT_PLAYDATE] > world.time) //they already have a playmate... - continue - playmate.ai_controller.set_blackboard_key(BB_NEARBY_PLAYMATE, controller.pawn) - return playmate - return null - - -/datum/ai_behavior/interact_with_playmate - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION - -/datum/ai_behavior/interact_with_playmate/setup(datum/ai_controller/controller, target_key) - . = ..() - var/turf/target = controller.blackboard[target_key] - if(isnull(target)) +/datum/targeting_strategy/playmate/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + if(!istype(target, /mob/living/basic/orbie)) return FALSE - set_movement_target(controller, target) + var/mob/living/basic/orbie/orbie_target = target + if(orbie_target == living_mob || orbie_target.stat == DEAD || isnull(orbie_target.ai_controller)) + return FALSE + if(orbie_target.ai_controller.blackboard[BB_NEARBY_PLAYMATE]) + return FALSE + if(orbie_target.ai_controller.blackboard[BB_NEXT_PLAYDATE] > world.time) + return FALSE + return TRUE -/datum/ai_behavior/interact_with_playmate/perform(seconds_per_tick, datum/ai_controller/controller, target_key) +///plays with a nearby orbie +/datum/bt_node/ai_behavior/interact_with_playmate + var/target_key = "BB_NEARBY_PLAYMATE" + +/datum/bt_node/ai_behavior/interact_with_playmate/perform(seconds_per_tick, datum/ai_controller/controller) var/mob/living/basic/living_pawn = controller.pawn var/atom/target = controller.blackboard[target_key] @@ -72,38 +60,36 @@ living_pawn.manual_emote("plays with [target]!") living_pawn.spin(spintime = 4, speed = 1) - living_pawn.ClickOn(target) + INVOKE_ASYNC(living_pawn, TYPE_PROC_REF(/mob, ClickOn), target) return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED -/datum/ai_behavior/interact_with_playmate/finish_action(datum/ai_controller/controller, success, target_key) +/datum/bt_node/ai_behavior/interact_with_playmate/finish_action(datum/ai_controller/controller, success) . = ..() controller.clear_blackboard_key(target_key) controller.set_blackboard_key(BB_NEXT_PLAYDATE, world.time + PET_PLAYTIME_COOLDOWN) -/datum/ai_planning_subtree/relay_pda_message +///relays a pda message if orbie is level 2+ +/datum/bt_node/ai_behavior/relay_pda_message + var/target_key = "BB_LAST_RECEIVED_MESSAGE" -/datum/ai_planning_subtree/relay_pda_message/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(controller.blackboard[BB_VIRTUAL_PET_LEVEL] < 2 || isnull(controller.blackboard[BB_LAST_RECEIVED_MESSAGE])) - return +/datum/bt_node/ai_behavior/relay_pda_message/perform(seconds_per_tick, datum/ai_controller/controller) + if(controller.blackboard[BB_VIRTUAL_PET_LEVEL] < 2) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - controller.queue_behavior(/datum/ai_behavior/relay_pda_message, BB_LAST_RECEIVED_MESSAGE) - -/datum/ai_behavior/relay_pda_message/perform(seconds_per_tick, datum/ai_controller/controller, target_key) var/mob/living/basic/living_pawn = controller.pawn var/text_to_say = controller.blackboard[target_key] if(isnull(text_to_say)) return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - living_pawn.say(text_to_say, forced = "AI controller") + INVOKE_ASYNC(living_pawn, TYPE_PROC_REF(/atom/movable, say), text_to_say, forced = "AI controller") living_pawn.spin(spintime = 4, speed = 1) return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED -/datum/ai_behavior/relay_pda_message/finish_action(datum/ai_controller/controller, success, target_key) +/datum/bt_node/ai_behavior/relay_pda_message/finish_action(datum/ai_controller/controller, success) . = ..() controller.clear_blackboard_key(target_key) /datum/pet_command/follow/orbie - follow_behavior = /datum/ai_behavior/pet_follow_friend/orbie /datum/pet_command/follow/orbie/New(mob/living/parent) . = ..() @@ -113,9 +99,6 @@ SIGNAL_HANDLER set_command_active(source, friend) -/datum/ai_behavior/pet_follow_friend/orbie - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_MOVE_AND_PERFORM | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION - ///command to make our pet turn its lights on, we need to be level 2 to activate this ability /datum/pet_command/untargeted_ability/pet_lights command_name = "Lights" @@ -128,7 +111,7 @@ /datum/pet_command/untargeted_ability/pet_lights/execute_action(datum/ai_controller/controller) if(controller.blackboard[BB_VIRTUAL_PET_LEVEL] < 2) controller.clear_blackboard_key(BB_ACTIVE_PET_COMMAND) - return SUBTREE_RETURN_FINISH_PLANNING + return TRUE return ..() /datum/pet_command/use_ability/pet_lights/retrieve_command_text(atom/living_pet, atom/target) @@ -151,7 +134,7 @@ /datum/pet_command/use_ability/take_photo/execute_action(datum/ai_controller/controller) if(controller.blackboard[BB_VIRTUAL_PET_LEVEL] < 3) controller.clear_blackboard_key(BB_ACTIVE_PET_COMMAND) - return SUBTREE_RETURN_FINISH_PLANNING + return TRUE return ..() /datum/pet_command/perform_trick_sequence @@ -176,7 +159,7 @@ for(var/index in 1 to length(trick_sequence)) addtimer(CALLBACK(living_pawn, TYPE_PROC_REF(/mob, emote), trick_sequence[index], index * 0.5 SECONDS)) controller.clear_blackboard_key(BB_ACTIVE_PET_COMMAND) - return SUBTREE_RETURN_FINISH_PLANNING + return TRUE #undef PET_PLAYTIME_COOLDOWN #undef MESSAGE_EXPIRY_TIME diff --git a/code/modules/mob/living/basic/pets/parrot/parrot_ai/_parrot_controller.dm b/code/modules/mob/living/basic/pets/parrot/parrot_ai/_parrot_controller.dm index a8a49bd710e..e46a9e9f33b 100644 --- a/code/modules/mob/living/basic/pets/parrot/parrot_ai/_parrot_controller.dm +++ b/code/modules/mob/living/basic/pets/parrot/parrot_ai/_parrot_controller.dm @@ -1,4 +1,5 @@ /datum/ai_controller/basic_controller/parrot + behavior_tree_json = "code/modules/mob/living/basic/pets/parrot/parrot_ai/parrot.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/allow_items, BB_PET_TARGETING_STRATEGY = /datum/targeting_strategy/basic/not_friends, @@ -7,39 +8,7 @@ ai_traits = DEFAULT_AI_FLAGS | STOP_MOVING_WHEN_PULLED ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk/parrot - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/parrot_as_in_repeat, // always get a witty oneliner in when you can - /datum/ai_planning_subtree/pet_planning, - /datum/ai_planning_subtree/target_retaliate, - /datum/ai_planning_subtree/perch_on_target, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/hoard_items, - ) -/datum/idle_behavior/idle_random_walk/parrot - ///chance of us moving while perched - var/walk_chance_when_perched = 1 // SKYRAT EDIT CHANGE - More obedient poly ORIGINAL : var/walk_chance_when_perched = 5 - -/datum/idle_behavior/idle_random_walk/parrot/perform_idle_behavior(seconds_per_tick, datum/ai_controller/controller) - var/mob/living/living_pawn = controller.pawn - walk_chance = HAS_TRAIT(living_pawn, TRAIT_PARROT_PERCHED) ? walk_chance_when_perched : initial(walk_chance) - return ..() - -/datum/ai_behavior/travel_towards/and_drop - -/datum/ai_behavior/travel_towards/and_drop/finish_action(datum/ai_controller/controller, succeeded, target_key) - . = ..() - var/mob/living/living_mob = controller.pawn - if(QDELETED(living_mob)) // pawn can be null at this point - return - var/obj/drop_item = locate(/obj/item) in (living_mob.contents - typecache_filter_list(living_mob.contents, controller.blackboard[BB_IGNORE_ITEMS])) - drop_item?.forceMove(get_turf(living_mob)) - -/datum/ai_behavior/basic_melee_attack/interact_once/parrot - -/datum/ai_behavior/basic_melee_attack/interact_once/parrot/finish_action(datum/ai_controller/controller, succeeded, target_key) - . = ..() - controller.set_blackboard_key(BB_ALWAYS_IGNORE_FACTION, FALSE) +/datum/bt_node/subtree/perching + behavior_tree_json = "code/modules/mob/living/basic/pets/parrot/parrot_ai/perching.bt.json" diff --git a/code/modules/mob/living/basic/pets/parrot/parrot_ai/ghost_parrot_controller.dm b/code/modules/mob/living/basic/pets/parrot/parrot_ai/ghost_parrot_controller.dm index 75c77545657..bdcc091c929 100644 --- a/code/modules/mob/living/basic/pets/parrot/parrot_ai/ghost_parrot_controller.dm +++ b/code/modules/mob/living/basic/pets/parrot/parrot_ai/ghost_parrot_controller.dm @@ -1,38 +1,3 @@ /// Used for ghost poly. /datum/ai_controller/basic_controller/parrot/ghost - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/parrot_as_in_repeat, - /datum/ai_planning_subtree/possess_humans, - /datum/ai_planning_subtree/hoard_items, - ) - -///subtree to possess humans -/datum/ai_planning_subtree/possess_humans - ///chance we go possess humans - var/possess_chance = 2 - -/datum/ai_planning_subtree/possess_humans/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/mob/living/living_pawn = controller.pawn - - if(controller.blackboard_key_exists(BB_PERCH_TARGET)) - controller.queue_behavior(/datum/ai_behavior/perch_on_target/haunt, BB_PERCH_TARGET) - return SUBTREE_RETURN_FINISH_PLANNING - - - if(!SPT_PROB(possess_chance, seconds_per_tick)) - if(ishuman(living_pawn.loc)) - return SUBTREE_RETURN_FINISH_PLANNING - return - - if(ishuman(living_pawn.loc)) - controller.set_blackboard_key(living_pawn.loc) - return - - controller.queue_behavior(/datum/ai_behavior/find_and_set/conscious_person, BB_PERCH_TARGET) - - -/datum/ai_behavior/perch_on_target/haunt - -/datum/ai_behavior/perch_on_target/haunt/check_human_conditions(mob/living/living_human) - return (living_human.stat != DEAD) + behavior_tree_json = "code/modules/mob/living/basic/pets/parrot/parrot_ai/parrot_ghost.bt.json" diff --git a/code/modules/mob/living/basic/pets/parrot/parrot_ai/parrot.bt.json b/code/modules/mob/living/basic/pets/parrot/parrot_ai/parrot.bt.json new file mode 100644 index 00000000000..b52f4da2761 --- /dev/null +++ b/code/modules/mob/living/basic/pets/parrot/parrot_ai/parrot.bt.json @@ -0,0 +1,190 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/parrot", + "type": "selector", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/pawn_contained_in_obj", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY" + }, + "child": { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/container_attackable", + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/break_out_of_object/from_bb", + "vars": { + "target_key": "BB_BASIC_MOB_ESCAPE_TARGET" + } + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/resist" + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/pawn_grabbed_by_enemy", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/resist" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/pawn_is_restrained", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/resist" + } + } + ] + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree", + "override_id": "SUBPLAN_ID_PET_COMMAND" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_FAIL_ON_FAILURE", + "loop_delay": "IDLE_BEHAVIOR_RATE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/random_chance", + "vars": { + "chance": 0.05 + }, + "child": { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/perching" + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/parrot_hoard" + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/idle_random_walk/parrot" + } + ] + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/cooldown", + "vars": { + "cooldown_key": "BB_PARROT_SPEECH_COOLDOWN", + "cooldown_duration": "7.5 SECONDS" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/parrot_repeat_speech" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/pawn_buckled_to_obj", + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/random_chance", + "vars": { + "chance": 0.05 + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/resist" + } + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/pets/parrot/parrot_ai/parrot_ghost.bt.json b/code/modules/mob/living/basic/pets/parrot/parrot_ai/parrot_ghost.bt.json new file mode 100644 index 00000000000..079b39f856f --- /dev/null +++ b/code/modules/mob/living/basic/pets/parrot/parrot_ai/parrot_ghost.bt.json @@ -0,0 +1,119 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/parrot/ghost", + "type": "selector", + "children": [ + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "1 SECONDS", + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_FAIL_ON_FAILURE", + "loop_delay": "IDLE_BEHAVIOR_RATE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_PERCH_TARGET" + }, + "child": { + "type": "selector", + "children": [ + { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_PERCH_TARGET", + "required_dist": 0 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/perch_on_target/haunt", + "vars": { + "target_key": "BB_PERCH_TARGET" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/clear_key", + "vars": { + "key": "BB_PERCH_TARGET" + } + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/random_chance", + "vars": { + "chance": 0.02 + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_PERCH_TARGET", + "target_source": "/datum/target_source/oview_single_type/human_mob", + "targeting_strategy": "/datum/targeting_strategy/conscious_human" + } + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/parrot_hoard" + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/idle_random_walk/parrot" + } + ] + } + ] + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/cooldown", + "vars": { + "cooldown_key": "BB_PARROT_SPEECH_COOLDOWN", + "cooldown_duration": "7.5 SECONDS" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/parrot_repeat_speech" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/pawn_buckled_to_obj", + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/random_chance", + "vars": { + "chance": 0.05 + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/resist" + } + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/pets/parrot/parrot_ai/parrot_hoard.bt.json b/code/modules/mob/living/basic/pets/parrot/parrot_ai/parrot_hoard.bt.json new file mode 100644 index 00000000000..5222613ef3a --- /dev/null +++ b/code/modules/mob/living/basic/pets/parrot/parrot_ai/parrot_hoard.bt.json @@ -0,0 +1,118 @@ +{ + "dm_type": "/datum/bt_node/subtree/parrot_hoard", + "type": "sequence", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_HOARD_LOCATION" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/succeed" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_HOARD_LOCATION", + "targeting_strategy": "/datum/targeting_strategy/parrot_hoard_location", + "target_source": "/datum/target_source/oview", + "vision_range": "BB_HOARD_LOCATION_RANGE" + } + } + ] + }, + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/target_is_holding_item", + "vars": { + "observer_abort": "BT_ABORT_SELF", + "key": "BB_MY_PAWN" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_HOARD_LOCATION", + "required_dist": 1 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/drop_all_held_items" + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_SELF", + "key": "BB_HOARD_ITEM_TARGET" + }, + "child": { + "type": "selector", + "children": [ + { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_HOARD_ITEM_TARGET", + "required_dist": 1 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack/interact_once/parrot", + "vars": { + "target_key": "BB_HOARD_ITEM_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/clear_key", + "vars": { + "key": "BB_HOARD_ITEM_TARGET" + } + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/random_chance", + "vars": { + "chance": 0.05 + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/parrot_hoard_item", + "vars": { + "target_key": "BB_HOARD_ITEM_TARGET" + } + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/pets/parrot/parrot_ai/parrot_hoarding.dm b/code/modules/mob/living/basic/pets/parrot/parrot_ai/parrot_hoarding.dm index a4059b2d964..eb1213a09be 100644 --- a/code/modules/mob/living/basic/pets/parrot/parrot_ai/parrot_hoarding.dm +++ b/code/modules/mob/living/basic/pets/parrot/parrot_ai/parrot_hoarding.dm @@ -1,74 +1,71 @@ -///subtree to steal items -/datum/ai_planning_subtree/hoard_items - var/theft_chance = 5 +/// Accepts open turfs which aren't space, aren't blocked, and are within hoarding range. Used to pick a parrot's nest. +/datum/targeting_strategy/parrot_hoard_location -/datum/ai_planning_subtree/hoard_items/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/mob/living/living_pawn = controller.pawn +/datum/targeting_strategy/parrot_hoard_location/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + var/turf/open/candidate = target + if(!istype(candidate) || is_space_or_openspace(candidate)) + return FALSE + if(candidate.is_blocked_turf(source_atom = living_mob)) + return FALSE + return TRUE - var/turf/myspace = controller.blackboard[BB_HOARD_LOCATION] +/// Accepts small items lying on a turf away from the nest, or non-ally humans holding a small valuable. Used to pick something to steal. +/datum/targeting_strategy/parrot_hoard_item - if(isnull(myspace) || myspace.is_blocked_turf(source_atom = controller.pawn) || get_dist(myspace, controller.pawn) > controller.blackboard[BB_HOARD_LOCATION_RANGE]) - controller.queue_behavior(/datum/ai_behavior/find_and_set/hoard_location, BB_HOARD_LOCATION, /turf/open) - return +/datum/targeting_strategy/parrot_hoard_item/is_valid_target(mob/living/living_mob, atom/target, vision_range, datum/ai_controller/controller = null) + . = ..() + if(!.) + return FALSE + if(isnull(controller)) + return FALSE - //we have an item, go drop! - var/list/our_contents = living_pawn.contents - typecache_filter_list(living_pawn.contents, controller.blackboard[BB_IGNORE_ITEMS]) - if(length(our_contents)) - controller.queue_behavior(/datum/ai_behavior/travel_towards/and_drop, BB_HOARD_LOCATION) - return SUBTREE_RETURN_FINISH_PLANNING + var/list/ignore_items = controller.blackboard[BB_IGNORE_ITEMS] + if(is_type_in_typecache(target, ignore_items)) + return FALSE - if(controller.blackboard_key_exists(BB_HOARD_ITEM_TARGET)) - controller.queue_behavior(/datum/ai_behavior/basic_melee_attack/interact_once, BB_HOARD_ITEM_TARGET, BB_TARGETING_STRATEGY) - return SUBTREE_RETURN_FINISH_PLANNING - - if(!SPT_PROB(theft_chance, seconds_per_tick)) - return - controller.queue_behavior(/datum/ai_behavior/find_and_set/hoard_item, BB_HOARD_ITEM_TARGET) - -/datum/ai_behavior/find_and_set/hoard_location - -/datum/ai_behavior/find_and_set/hoard_location/search_tactic(datum/ai_controller/controller, locate_path, search_range = SEARCH_TACTIC_DEFAULT_RANGE) - for(var/turf/open/candidate in oview(search_range, controller.pawn)) - if(is_space_or_openspace(candidate)) - continue - if(candidate.is_blocked_turf(source_atom = controller.pawn)) - continue - return candidate - - return null - -/datum/ai_behavior/find_and_set/hoard_item - action_cooldown = 5 SECONDS - behavior_flags = AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION - -/datum/ai_behavior/find_and_set/hoard_item/search_tactic(datum/ai_controller/controller, locate_path, search_range = SEARCH_TACTIC_DEFAULT_RANGE) - if(!controller.blackboard_key_exists(BB_HOARD_LOCATION)) - return null var/turf/nest_turf = controller.blackboard[BB_HOARD_LOCATION] - var/mob/living/living_pawn = controller.pawn - for(var/atom/potential_victim in oview(search_range, controller.pawn)) - if(is_type_in_typecache(potential_victim, controller.blackboard[BB_IGNORE_ITEMS])) - continue - if(potential_victim.loc == nest_turf) - continue - if(isitem(potential_victim)) - var/obj/item/item_steal = potential_victim - if(item_steal.w_class <= WEIGHT_CLASS_SMALL) - return potential_victim - if(!ishuman(potential_victim)) - continue - if(living_pawn.has_ally(potential_victim)) - continue //dont steal from friends - if(holding_valuable(controller, potential_victim)) - controller.set_blackboard_key(BB_ALWAYS_IGNORE_FACTION, TRUE) - return potential_victim + if(target.loc == nest_turf) + return FALSE - return null + if(isitem(target)) + var/obj/item/loose_item = target + return loose_item.w_class <= WEIGHT_CLASS_SMALL -/datum/ai_behavior/find_and_set/hoard_item/proc/holding_valuable(datum/ai_controller/controller, mob/living/human_target) + if(!ishuman(target)) + return FALSE + if(living_mob.has_ally(target)) // dont steal from friends + return FALSE + return holding_valuable(controller, target) + +/datum/targeting_strategy/parrot_hoard_item/proc/holding_valuable(datum/ai_controller/controller, mob/living/human_target) + var/list/ignore_items = controller.blackboard[BB_IGNORE_ITEMS] for(var/obj/item/potential_item in human_target.held_items) - if(is_type_in_typecache(potential_item, controller.blackboard[BB_IGNORE_ITEMS])) + if(is_type_in_typecache(potential_item, ignore_items)) continue if(potential_item.w_class <= WEIGHT_CLASS_SMALL) return TRUE return FALSE + +/// Finds something for the parrot to steal. Temporarily ignores faction when eyeing a person's belongings. +/datum/bt_node/ai_behavior/acquire_target/parrot_hoard_item + target_source = /datum/target_source/oview + targeting_strategy = /datum/targeting_strategy/parrot_hoard_item + time_between_perform = 5 SECONDS + +/datum/bt_node/ai_behavior/acquire_target/parrot_hoard_item/on_target_found(datum/ai_controller/controller, atom/target, datum/targeting_strategy/strategy) + if(ishuman(target)) + controller.set_blackboard_key(BB_ALWAYS_IGNORE_FACTION, TRUE) + +/// Single-hit grab variant which resets the faction-ignore flag once we are done stealing. +/datum/bt_node/ai_behavior/basic_melee_attack/interact_once/parrot + +/datum/bt_node/ai_behavior/basic_melee_attack/interact_once/parrot/finish_action(datum/ai_controller/controller, succeeded) + . = ..() + controller.set_blackboard_key(BB_ALWAYS_IGNORE_FACTION, FALSE) + +/// Find a nest, carry loot home, then go steal more. +/datum/bt_node/subtree/parrot_hoard + behavior_tree_json = "code/modules/mob/living/basic/pets/parrot/parrot_ai/parrot_hoard.bt.json" diff --git a/code/modules/mob/living/basic/pets/parrot/parrot_ai/parrot_perching.dm b/code/modules/mob/living/basic/pets/parrot/parrot_ai/parrot_perching.dm index b5b34e30cad..adbb3760b49 100644 --- a/code/modules/mob/living/basic/pets/parrot/parrot_ai/parrot_perching.dm +++ b/code/modules/mob/living/basic/pets/parrot/parrot_ai/parrot_perching.dm @@ -1,74 +1,45 @@ -///subtree to perch on targets -/datum/ai_planning_subtree/perch_on_target - ///perchance... - var/perch_chance = 5 - ///chance we unbuckle - var/unperch_chance = 15 +/// Reads the parrot's desired-perch typecache so it can scan for somewhere to sit. +/datum/target_source/oview_typed/from_bb_key/parrot_perch_types + typecache_key = BB_PARROT_PERCH_TYPES +/// Parrot behavior that perches them on their current perch target. +/datum/bt_node/ai_behavior/perch_on_target + /// Blackboard key holding the atom to perch on. + var/target_key -/datum/ai_planning_subtree/perch_on_target/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/mob/living/living_pawn = controller.pawn - var/atom/buckled_to = living_pawn.buckled - - //do we have a current target or is chance to unbuckle has passed? then unbuckle! - if(buckled_to) - if((SPT_PROB(unperch_chance, seconds_per_tick) || controller.blackboard_key_exists(BB_BASIC_MOB_CURRENT_TARGET))) - controller.queue_behavior(/datum/ai_behavior/unbuckle_mob) - return - return SUBTREE_RETURN_FINISH_PLANNING - - //if we are perched, we can go find something else to perch too - var/final_chance = HAS_TRAIT(living_pawn, TRAIT_PARROT_PERCHED) ? unperch_chance : perch_chance - - if(!SPT_PROB(final_chance, seconds_per_tick) || controller.blackboard_key_exists(BB_BASIC_MOB_CURRENT_TARGET)) - return - - if(controller.blackboard_key_exists(BB_PERCH_TARGET)) - controller.queue_behavior(/datum/ai_behavior/perch_on_target, BB_PERCH_TARGET) - return SUBTREE_RETURN_FINISH_PLANNING - - //50 50 chance to look for an object, or a friend - if(prob(50)) - controller.queue_behavior(/datum/ai_behavior/find_and_set/nearby_friends, BB_PERCH_TARGET) - return - - controller.queue_behavior(/datum/ai_behavior/find_and_set/in_list, BB_PERCH_TARGET, controller.blackboard[BB_PARROT_PERCH_TYPES]) - -/// Parrot behavior that allows them to perch on a target. -/datum/ai_behavior/perch_on_target - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION - -/datum/ai_behavior/perch_on_target/setup(datum/ai_controller/controller, target_key) - . = ..() - var/atom/target = controller.blackboard[target_key] - if(QDELETED(target)) - return FALSE - - set_movement_target(controller, target) - -/datum/ai_behavior/perch_on_target/perform(seconds_per_tick, datum/ai_controller/controller, target_key) +/datum/bt_node/ai_behavior/perch_on_target/perform(seconds_per_tick, datum/ai_controller/controller) var/atom/target = controller.blackboard[target_key] if(QDELETED(target)) return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED var/mob/living/basic/parrot/living_pawn = controller.pawn - if(!ishuman(target)) - living_pawn.start_perching(target) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - - if(!check_human_conditions(target)) + if(ishuman(target) && !check_human_conditions(target)) return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED living_pawn.start_perching(target) return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED -/datum/ai_behavior/perch_on_target/proc/check_human_conditions(mob/living/living_human) +/datum/bt_node/ai_behavior/perch_on_target/proc/check_human_conditions(mob/living/living_human) if(living_human.stat == DEAD || LAZYLEN(living_human.buckled_mobs) >= living_human.max_buckled_mobs) return FALSE - return TRUE -/datum/ai_behavior/perch_on_target/finish_action(datum/ai_controller/controller, succeeded, target_key) +/datum/bt_node/ai_behavior/perch_on_target/finish_action(datum/ai_controller/controller, succeeded) . = ..() controller.clear_blackboard_key(target_key) + +/// Variant for the ghost parrot, which can haunt any living human. +/datum/bt_node/ai_behavior/perch_on_target/haunt + +/datum/bt_node/ai_behavior/perch_on_target/haunt/check_human_conditions(mob/living/living_human) + return (living_human.stat != DEAD) + +/// Parrot idle wander; sits much more still while perched. +/datum/bt_node/ai_behavior/idle_random_walk/parrot + /// Chance of us moving while perched. + var/walk_chance_when_perched = 5 + +/datum/bt_node/ai_behavior/idle_random_walk/parrot/perform(seconds_per_tick, datum/ai_controller/controller) + walk_chance = HAS_TRAIT(controller.pawn, TRAIT_PARROT_PERCHED) ? walk_chance_when_perched : initial(walk_chance) + return ..() diff --git a/code/modules/mob/living/basic/pets/parrot/parrot_ai/parroting_action.dm b/code/modules/mob/living/basic/pets/parrot/parrot_ai/parroting_action.dm index ab8ad3957b1..32536df45de 100644 --- a/code/modules/mob/living/basic/pets/parrot/parrot_ai/parroting_action.dm +++ b/code/modules/mob/living/basic/pets/parrot/parrot_ai/parroting_action.dm @@ -1,31 +1,24 @@ #define MAXIMUM_PARROT_PITCH 24 -/// When a parrot... parrots... -/datum/ai_planning_subtree/parrot_as_in_repeat - operational_datums = list(/datum/component/listen_and_repeat) -/datum/ai_planning_subtree/parrot_as_in_repeat/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/atom/speaking_pawn = controller.pawn +/// When a parrot... parrots... it occasionally asks for a fresh phrase to repeat, then squawks it (sometimes over the radio). +/datum/bt_node/ai_behavior/parrot_repeat_speech + +/datum/bt_node/ai_behavior/parrot_repeat_speech/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/basic/parrot/speaking_pawn = controller.pawn var/switch_up_probability = controller.blackboard[BB_PARROT_PHRASE_CHANGE_PROBABILITY] - if(SPT_PROB(switch_up_probability, seconds_per_tick) || isnull(controller.blackboard[BB_PARROT_REPEAT_STRING])) + if(prob(switch_up_probability) || isnull(controller.blackboard[BB_PARROT_REPEAT_STRING])) if(SEND_SIGNAL(speaking_pawn, COMSIG_NEEDS_NEW_PHRASE) & NO_NEW_PHRASE_AVAILABLE) - return + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - if(!SPT_PROB(controller.blackboard[BB_PARROT_REPEAT_PROBABILITY], seconds_per_tick)) - return + if(!prob(controller.blackboard[BB_PARROT_REPEAT_PROBABILITY])) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - var/potential_string = controller.blackboard[BB_PARROT_REPEAT_STRING] - if(isnull(potential_string)) - stack_trace("Parrot As In Repeat Subtree somehow is getting a null potential string while not getting `NO_NEW_PHRASE_AVAILABLE`!") - return + var/list/speech = controller.blackboard[BB_PARROT_REPEAT_STRING] + if(isnull(speech)) + stack_trace("Parrot repeat speech somehow got a null phrase while not getting `NO_NEW_PHRASE_AVAILABLE`!") + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - controller.queue_behavior(/datum/ai_behavior/perform_speech/parrot, potential_string) - -/datum/ai_behavior/perform_speech/parrot - action_cooldown = 7.5 SECONDS // gets really annoying (moreso than usual) really fast otherwise - -/datum/ai_behavior/perform_speech/parrot/perform(seconds_per_tick, datum/ai_controller/controller, list/speech, speech_sound) - var/mob/living/basic/parrot/speaking_pawn = controller.pawn var/list/available_channels = speaking_pawn.get_available_channels() var/modified_speech = speech["line"] var/use_radio = prob(50) // we might not even use the radio if we even have a channel @@ -35,7 +28,6 @@ if(!length(available_channels)) // might not even use the radio at all if(has_channel_prefix) modified_speech = copytext_char(modified_speech, 3) - else if(has_channel_prefix) modified_speech = "[use_radio ? pick(available_channels) : ""][copytext_char(modified_speech, 3)]" @@ -44,18 +36,16 @@ if(SStts.tts_enabled) modify_voice(speaking_pawn, speech) - speaking_pawn.say(modified_speech, forced = "AI Controller") - if(speech_sound) - playsound(speaking_pawn, speech_sound, 80, vary = TRUE) + INVOKE_ASYNC(speaking_pawn, TYPE_PROC_REF(/atom/movable, say), modified_speech, forced = "AI Controller") return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED -/datum/ai_behavior/perform_speech/parrot/proc/modify_voice(mob/living/speaking_pawn, list/speech) +/datum/bt_node/ai_behavior/parrot_repeat_speech/proc/modify_voice(mob/living/speaking_pawn, list/speech) if(SStts.available_speakers.Find(speech["voice"])) speaking_pawn.voice = speech["voice"] if(speech["pitch"] && SStts.pitch_enabled) speaking_pawn.pitch = min(speech["pitch"] + rand(6, 12), MAXIMUM_PARROT_PITCH) -/datum/ai_behavior/perform_speech/parrot/finish_action(datum/ai_controller/controller, succeeded) +/datum/bt_node/ai_behavior/parrot_repeat_speech/finish_action(datum/ai_controller/controller, succeeded) . = ..() if(!succeeded) return diff --git a/code/modules/mob/living/basic/pets/parrot/parrot_ai/perching.bt.json b/code/modules/mob/living/basic/pets/parrot/parrot_ai/perching.bt.json new file mode 100644 index 00000000000..e68efc5e6bb --- /dev/null +++ b/code/modules/mob/living/basic/pets/parrot/parrot_ai/perching.bt.json @@ -0,0 +1,85 @@ +{ + "dm_type": "/datum/bt_node/subtree/perching", + "type": "selector", + "children": [ + { + "type": "sequence", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_PERCH_TARGET", + "invert": true + }, + "child": { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/random_chance", + "vars": { + "chance": 0.5 + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_PERCH_TARGET", + "target_source": "/datum/target_source/oview_single_type/human_mob", + "targeting_strategy": "/datum/targeting_strategy/ally_mob" + } + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_PERCH_TARGET", + "target_source": "/datum/target_source/oview_typed/from_bb_key/parrot_perch_types", + "targeting_strategy": "/datum/targeting_strategy/anything" + } + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_SELF", + "invert": false, + "key": "BB_PERCH_TARGET" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_PERCH_TARGET", + "required_dist": 1 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/perch_on_target", + "vars": { + "target_key": "BB_PERCH_TARGET" + } + } + ] + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/clear_key", + "vars": { + "key": "BB_PERCH_TARGET" + } + } + ] +} diff --git a/code/modules/mob/living/basic/pets/penguin/penguin.bt.json b/code/modules/mob/living/basic/pets/penguin/penguin.bt.json new file mode 100644 index 00000000000..f9456752bf0 --- /dev/null +++ b/code/modules/mob/living/basic/pets/penguin/penguin.bt.json @@ -0,0 +1,154 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/penguin", + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CURRENT_TARGET", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/run_away_from_target" + } + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/move_to_and_eat" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/move_to_and_hunt", + "bindings": { + "bvtz06kb": "BB_FISHING_TARGET", + "b3y599q4": "BB_FISHING_TARGET", + "brrasnah": "BB_FISHING_TARGET", + "b3cnse9r": "TRUE", + "bd1towgc": "FALSE", + "bqwjf4id": "45 SECONDS", + "bm3y5m55": "BB_FISHING_TIMER" + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/move_to_and_hunt", + "bindings": { + "bvtz06kb": "BB_DRILLABLE_ICE", + "b3y599q4": "BB_DRILLABLE_ICE", + "brrasnah": "BB_DRILLABLE_ICE", + "b3cnse9r": "TRUE", + "bd1towgc": "TRUE", + "bqwjf4id": "15 SECONDS", + "bm3y5m55": "BB_ICE_DRILLING_TIMER" + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/move_to_and_hunt" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + } + ] + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop", + "bindings": { + "bqdqne64": "/datum/bt_node/ai_behavior/random_speech/penguin" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list/nearest", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/find_food" + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/key_off_cooldown", + "vars": { + "cooldown_key": "BB_NEXT_FOOD_EAT" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/key_off_cooldown", + "vars": { + "cooldown_key": "BB_FISHING_TIMER" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_FISHING_TARGET", + "target_source": "/datum/target_source/range_turfs/typecache_visible/ice", + "targeting_strategy": "/datum/targeting_strategy/fishing" + } + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "invert": true, + "key": "BB_FISHING_TARGET" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/key_off_cooldown", + "vars": { + "cooldown_key": "BB_ICE_DRILLING_TIMER" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_DRILLABLE_ICE", + "target_source": "/datum/target_source/range_turfs/typecache_visible/ice", + "targeting_strategy": "/datum/targeting_strategy/drillable_ice" + } + } + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_LOW_PRIORITY_HUNTING_TARGET", + "target_source": "/datum/target_source/oview_single_type/penguin_egg", + "targeting_strategy": "/datum/targeting_strategy/uncarried_egg" + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/pets/penguin/penguin.dm b/code/modules/mob/living/basic/pets/penguin/penguin.dm index ef16c3947ca..d462c344c8c 100644 --- a/code/modules/mob/living/basic/pets/penguin/penguin.dm +++ b/code/modules/mob/living/basic/pets/penguin/penguin.dm @@ -38,10 +38,9 @@ AddElement(/datum/element/ai_retaliate) AddElement(/datum/element/ai_flee_while_injured) AddElement(/datum/element/pet_bonus, "honk") + AddComponent(/datum/component/profound_fisher) AddElementTrait(TRAIT_WADDLING, INNATE_TRAIT, /datum/element/waddling) - var/static/list/fishable_objects = typecacheof(list(/turf/open/misc/ice)) - ai_controller.set_blackboard_key(BB_FISHABLE_LIST, fishable_objects) var/static/list/delicious_food = list(/obj/item/fish) AddElement(/datum/element/basic_eating, heal_amt = 10, food_types = delicious_food) ai_controller.set_blackboard_key(BB_BASIC_FOODS, typecacheof(delicious_food)) diff --git a/code/modules/mob/living/basic/pets/penguin/penguin_ai.dm b/code/modules/mob/living/basic/pets/penguin/penguin_ai.dm index 9209c7fa004..251e1feaf53 100644 --- a/code/modules/mob/living/basic/pets/penguin/penguin_ai.dm +++ b/code/modules/mob/living/basic/pets/penguin/penguin_ai.dm @@ -1,66 +1,16 @@ /datum/ai_controller/basic_controller/penguin + behavior_tree_json = "code/modules/mob/living/basic/pets/penguin/penguin.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, - BB_ONLY_FISH_WHILE_HUNGRY = TRUE, ) ai_traits = PASSIVE_AI_FLAGS ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - - planning_subtrees = list( - /datum/ai_planning_subtree/find_nearest_thing_which_attacked_me_to_flee, - /datum/ai_planning_subtree/flee_target, - /datum/ai_planning_subtree/find_food, - /datum/ai_planning_subtree/fish/drilled_ice, - /datum/ai_planning_subtree/find_and_hunt_target/drill_ice, - /datum/ai_planning_subtree/find_and_hunt_target/penguin_egg, - /datum/ai_planning_subtree/random_speech/penguin, - ) - -///subtree to find baby eggs! -/datum/ai_planning_subtree/find_and_hunt_target/penguin_egg - target_key = BB_LOW_PRIORITY_HUNTING_TARGET - hunting_behavior = /datum/ai_behavior/hunt_target/interact_with_target/reset_target - finding_behavior = /datum/ai_behavior/find_hunt_target/penguin_egg - hunt_targets = list(/obj/item/food/egg/penguin_egg) - hunt_range = 7 - -/datum/ai_behavior/find_hunt_target/penguin_egg/valid_dinner(mob/living/source, atom/dinner, radius) - return can_see(source, dinner, radius) && !(dinner in source.contents) - -///subtree to find diggable ice we can fish from! -/datum/ai_planning_subtree/find_and_hunt_target/drill_ice - target_key = BB_DRILLABLE_ICE - hunting_behavior = /datum/ai_behavior/hunt_target/interact_with_target/reset_target - finding_behavior = /datum/ai_behavior/find_hunt_target/search_turf_types/drillable_ice - hunt_targets = list(/turf/open/misc/ice) - hunt_range = 7 - -/datum/ai_behavior/find_hunt_target/search_turf_types/drillable_ice - -/datum/ai_behavior/find_hunt_target/search_turf_types/drillable_ice/valid_dinner(mob/living/source, turf/open/misc/ice/ice, radius) - return ice.can_make_hole && can_see(source, ice, radius) - -/datum/ai_planning_subtree/find_and_hunt_target/drill_ice/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(controller.blackboard_key_exists(BB_FISHING_TARGET)) - return - return ..() - -/datum/ai_planning_subtree/fish/drilled_ice - find_fishable_behavior = /datum/ai_behavior/find_and_set/in_list/drilled_ice - -/datum/ai_behavior/find_and_set/in_list/drilled_ice/search_tactic(datum/ai_controller/controller, locate_paths, search_range = SEARCH_TACTIC_DEFAULT_RANGE) - for(var/atom/possible_ice as anything in RANGE_TURFS(search_range, controller.pawn)) - if(!istype(possible_ice, /turf/open/misc/ice)) - continue - if(HAS_TRAIT(possible_ice, TRAIT_FISHING_SPOT)) - return possible_ice - return null ///ai controller for the baby penguin /datum/ai_controller/basic_controller/penguin/baby + behavior_tree_json = "code/modules/mob/living/basic/pets/penguin/penguin_baby.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_FIND_MOM_TYPES = list(/mob/living/basic/pet/penguin), @@ -69,11 +19,3 @@ ai_traits = PASSIVE_AI_FLAGS ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - - planning_subtrees = list( - /datum/ai_planning_subtree/find_nearest_thing_which_attacked_me_to_flee, - /datum/ai_planning_subtree/flee_target, - /datum/ai_planning_subtree/find_food, - /datum/ai_planning_subtree/look_for_adult, - ) diff --git a/code/modules/mob/living/basic/pets/penguin/penguin_baby.bt.json b/code/modules/mob/living/basic/pets/penguin/penguin_baby.bt.json new file mode 100644 index 00000000000..68996a03266 --- /dev/null +++ b/code/modules/mob/living/basic/pets/penguin/penguin_baby.bt.json @@ -0,0 +1,118 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/penguin/baby", + "type": "selector", + "children": [ + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CURRENT_TARGET", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/run_away_from_target" + } + }, + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/move_to_and_eat" + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "key": "BB_FOUND_MOM" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/cooldown", + "vars": { + "cooldown_key": "BB_PARENT_EMOTE_COOLDOWN", + "cooldown_duration": "6 SECONDS" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_FOUND_MOM", + "required_dist": 1 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/look_to_parent", + "vars": { + "parent_key": "BB_FOUND_MOM" + } + } + ] + } + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list/nearest", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/find_food" + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/find_mom", + "vars": { + "mom_types_key": "BB_FIND_MOM_TYPES", + "ignore_types_key": "BB_IGNORE_MOM_TYPES", + "found_mom_key": "BB_FOUND_MOM" + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop", + "bindings": { + "bqdqne64": "/datum/bt_node/ai_behavior/random_speech/penguin" + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/pets/pet_cult/pet_cult.bt.json b/code/modules/mob/living/basic/pets/pet_cult/pet_cult.bt.json new file mode 100644 index 00000000000..33d886038b0 --- /dev/null +++ b/code/modules/mob/living/basic/pets/pet_cult/pet_cult.bt.json @@ -0,0 +1,210 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/pet_cult", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree", + "override_id": "SUBPLAN_ID_PET_COMMAND" + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_FRIENDLY_CULTIST", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_FRIENDLY_CULTIST", + "required_dist": 1, + "finish_on_arrival": false + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/befriend_target", + "vars": { + "target_key": "BB_FRIENDLY_CULTIST", + "befriend_message": "BB_FRIENDLY_MESSAGE" + } + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_OCCUPIED_RUNE", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_OCCUPIED_RUNE", + "required_dist": 1, + "finish_on_arrival": false + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/activate_rune" + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_DEAD_CULTIST", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_DEAD_CULTIST", + "required_dist": 1, + "finish_on_arrival": false + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/drag_target", + "vars": { + "target_key": "BB_DEAD_CULTIST" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_NEARBY_RUNE", + "required_dist": 0, + "finish_on_arrival": false + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/clear_key", + "vars": { + "key": "BB_DEAD_CULTIST" + } + } + ] + } + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_FRIENDLY_CULTIST", + "target_source": "/datum/target_source/oview_single_type/carbon_mob", + "targeting_strategy": "/datum/targeting_strategy/befriendable_cultist", + "vision_range": 9, + "time_between_perform": "5 SECONDS" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/find_occupied_rune" + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/find_dead_cultist" + } + ] + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/pets/pet_cult/pet_cult_ai.dm b/code/modules/mob/living/basic/pets/pet_cult/pet_cult_ai.dm index 194c774371b..a0fbd0248fe 100644 --- a/code/modules/mob/living/basic/pets/pet_cult/pet_cult_ai.dm +++ b/code/modules/mob/living/basic/pets/pet_cult/pet_cult_ai.dm @@ -6,17 +6,7 @@ ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/befriend_cultists, - /datum/ai_planning_subtree/find_occupied_rune, - /datum/ai_planning_subtree/find_dead_cultist, - /datum/ai_planning_subtree/drag_target_to_rune, - /datum/ai_planning_subtree/pet_planning, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) + behavior_tree_json = "code/modules/mob/living/basic/pets/pet_cult/pet_cult.bt.json" ///if target gets pulled away, unset him /datum/ai_controller/basic_controller/pet_cult/proc/delete_pull_target(datum/source, atom/movable/was_pulling) @@ -29,205 +19,11 @@ ///targeting strat to attack non cultists /datum/targeting_strategy/basic/cultist + custom_faction_check = TRUE /datum/targeting_strategy/basic/cultist/faction_check(datum/ai_controller/controller, mob/living/living_mob, mob/living/the_target) return IS_CULTIST_OR_CULTIST_MOB(the_target) -///befriend all cultists around us! -/datum/ai_planning_subtree/befriend_cultists - -/datum/ai_planning_subtree/befriend_cultists/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(controller.blackboard_key_exists(BB_FRIENDLY_CULTIST)) - controller.queue_behavior(/datum/ai_behavior/befriend_target, BB_FRIENDLY_CULTIST) - return - - controller.queue_behavior(/datum/ai_behavior/find_and_set/friendly_cultist, BB_FRIENDLY_CULTIST, /mob/living/carbon) - -///behavior to find cultists that we befriend -/datum/ai_behavior/find_and_set/friendly_cultist - action_cooldown = 5 SECONDS - behavior_flags = AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION - -/datum/ai_behavior/find_and_set/friendly_cultist/search_tactic(datum/ai_controller/controller, locate_path, search_range = SEARCH_TACTIC_DEFAULT_RANGE) - var/mob/living/living_pawn = controller.pawn - for(var/mob/living/carbon/possible_cultist in oview(search_range, controller.pawn)) - if(IS_CULTIST(possible_cultist) && !(living_pawn.has_ally(possible_cultist))) - return possible_cultist - - return null - -///subtree to find a rune with a viable target on it, so we can go activate it -/datum/ai_planning_subtree/find_occupied_rune - -/datum/ai_planning_subtree/find_occupied_rune/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if((LAZYLEN(GLOB.sacrificed) - SOULS_TO_REVIVE - GLOB.sacrifices_used) < 0) - controller.clear_blackboard_key(BB_OCCUPIED_RUNE) - return - - if(controller.blackboard_key_exists(BB_OCCUPIED_RUNE)) - controller.queue_behavior(/datum/ai_behavior/activate_rune, BB_OCCUPIED_RUNE) - return SUBTREE_RETURN_FINISH_PLANNING - - controller.queue_behavior(/datum/ai_behavior/find_and_set/occupied_rune, BB_OCCUPIED_RUNE, /obj/effect/rune/raise_dead) - -/datum/ai_behavior/find_and_set/occupied_rune - -/datum/ai_behavior/find_and_set/occupied_rune/search_tactic(datum/ai_controller/controller, locate_path, search_range = SEARCH_TACTIC_DEFAULT_RANGE) - var/datum/team/cult/cult_team = controller.blackboard[BB_CULT_TEAM] - if(isnull(cult_team)) - return null - - for(var/obj/effect/rune/raise_dead/target_rune in oview(search_range, controller.pawn)) - controller.set_blackboard_key(BB_NEARBY_RUNE, target_rune) - var/mob/living/occupant = locate(/mob/living/carbon/human) in get_turf(target_rune) - if(isnull(occupant)) - continue - if(occupant.stat != DEAD || !IS_CULTIST(occupant)) - continue - return target_rune - - return null - -/datum/ai_behavior/activate_rune - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION | AI_BEHAVIOR_REQUIRE_REACH - action_cooldown = 3 SECONDS - -/datum/ai_behavior/activate_rune/setup(datum/ai_controller/controller, target_key) - . = ..() - var/turf/target = controller.blackboard[target_key] - if(isnull(target)) - return FALSE - set_movement_target(controller, target) - -/datum/ai_behavior/activate_rune/perform(seconds_per_tick, datum/ai_controller/controller, target_key) - var/atom/target = controller.blackboard[target_key] - - if(QDELETED(target)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - var/datum/team/cult/cult_team = controller.blackboard[BB_CULT_TEAM] - var/mob/living/revive_mob = locate(/mob/living) in get_turf(target) - - if(isnull(revive_mob) || revive_mob.stat != DEAD || !(revive_mob.mind in cult_team.members)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - controller.ai_interact(target = target) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -/datum/ai_behavior/activate_rune/finish_action(datum/ai_controller/controller, success, target_key) - . = ..() - controller.clear_blackboard_key(target_key) - - -///find targets that we can revive -/datum/ai_planning_subtree/find_dead_cultist - -/datum/ai_planning_subtree/find_dead_cultist/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if((LAZYLEN(GLOB.sacrificed) - SOULS_TO_REVIVE - GLOB.sacrifices_used) < 0) - controller.clear_blackboard_key(BB_DEAD_CULTIST) - return - - var/mob/living/living_pawn = controller.pawn - - if(!isnull(living_pawn.pulling)) - return - - if(controller.blackboard_key_exists(BB_DEAD_CULTIST)) - controller.queue_behavior(/datum/ai_behavior/pull_target/cult_revive, BB_DEAD_CULTIST) - return SUBTREE_RETURN_FINISH_PLANNING - - controller.queue_behavior(/datum/ai_behavior/find_and_set/dead_cultist, BB_DEAD_CULTIST, /mob/living/carbon/human) - -/datum/ai_behavior/find_and_set/dead_cultist - -/datum/ai_behavior/find_and_set/dead_cultist/search_tactic(datum/ai_controller/controller, locate_path, search_range = SEARCH_TACTIC_DEFAULT_RANGE) - var/datum/team/cult/cult_team = controller.blackboard[BB_CULT_TEAM] - if(isnull(cult_team)) - return null - var/mob/living/living_pawn = controller.pawn - for(var/mob/living/carbon/human/target in oview(search_range, controller.pawn)) - if(target.stat != DEAD) - continue - if(!IS_CULTIST(target)) - continue - if(target.buckled || target.move_resist > living_pawn.move_force || target.pulledby) - continue - if(locate(/obj/effect/rune/raise_dead) in target.loc) - continue - return target - return null - -/datum/ai_behavior/pull_target/cult_revive - -/datum/ai_behavior/pull_target/cult_revive/finish_action(datum/ai_controller/basic_controller/controller, succeeded, target_key) - . = ..() - if(!succeeded) - return - var/atom/target = controller.blackboard[target_key] - if(QDELETED(target)) - return - controller.RegisterSignal(controller.pawn, COMSIG_ATOM_NO_LONGER_PULLING, TYPE_PROC_REF(/datum/ai_controller/basic_controller/pet_cult, delete_pull_target), override = TRUE) - -/datum/ai_planning_subtree/drag_target_to_rune - -/datum/ai_planning_subtree/drag_target_to_rune/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - - if(!controller.blackboard_key_exists(BB_DEAD_CULTIST)) //no target, we dont need to do anything - return - - var/mob/living/our_pawn = controller.pawn - - if(isnull(our_pawn.pulling)) - return - - var/atom/target_rune = controller.blackboard[BB_NEARBY_RUNE] - - if(QDELETED(target_rune)) - controller.queue_behavior(/datum/ai_behavior/use_mob_ability, BB_RUNE_ABILITY) - return SUBTREE_RETURN_FINISH_PLANNING - - if(!can_see(our_pawn, target_rune, 9)) - controller.clear_blackboard_key(BB_NEARBY_RUNE) - return - - controller.queue_behavior(/datum/ai_behavior/drag_target_to_rune, BB_NEARBY_RUNE, BB_DEAD_CULTIST) - -///behavior to drag the target onto the rune -/datum/ai_behavior/drag_target_to_rune - required_distance = 0 - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT - -/datum/ai_behavior/drag_target_to_rune/setup(datum/ai_controller/controller, target_key, cultist_key) - . = ..() - var/turf/target = controller.blackboard[target_key] - if(isnull(target)) - return FALSE - set_movement_target(controller, target) - -/datum/ai_behavior/drag_target_to_rune/perform(seconds_per_tick, datum/ai_controller/controller, target_key, cultist_key) - var/mob/living/our_pawn = controller.pawn - var/atom/cultist_target = controller.blackboard[cultist_key] - if(isnull(cultist_target)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - var/list/possible_dirs = GLOB.alldirs.Copy() - possible_dirs -= get_dir(our_pawn, cultist_target) - for(var/direction in possible_dirs) - var/turf/possible_turf = get_step(our_pawn, direction) - if(possible_turf.is_blocked_turf(source_atom = our_pawn)) - possible_dirs -= direction - step(our_pawn, pick(possible_dirs)) - our_pawn.stop_pulling() - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - - -/datum/ai_behavior/drag_target_to_rune/finish_action(datum/ai_controller/controller, success, target_key, cultist_key) - . = ..() - if(success) - var/atom/revival_rune = controller.blackboard[target_key] - controller.set_blackboard_key(BB_OCCUPIED_RUNE, revival_rune) - controller.clear_blackboard_key(cultist_key) - controller.clear_blackboard_key(target_key) - ///command ability to draw runes /datum/pet_command/untargeted_ability/draw_rune command_name = "Draw Rune" diff --git a/code/modules/mob/living/basic/pets/pet_cult/pet_cult_bt.dm b/code/modules/mob/living/basic/pets/pet_cult/pet_cult_bt.dm new file mode 100644 index 00000000000..d0fd59c2a78 --- /dev/null +++ b/code/modules/mob/living/basic/pets/pet_cult/pet_cult_bt.dm @@ -0,0 +1,83 @@ + +/** + * Checks whether the cult has enough souls to revive and finds a raise_dead rune + * with a dead cultist on it. Sets BB_OCCUPIED_RUNE. + */ +/datum/bt_node/ai_behavior/find_occupied_rune + time_between_perform = 3 SECONDS + +/datum/bt_node/ai_behavior/find_occupied_rune/perform(seconds_per_tick, datum/ai_controller/controller) + if((LAZYLEN(GLOB.sacrificed) - SOULS_TO_REVIVE - GLOB.sacrifices_used) < 0) + controller.clear_blackboard_key(BB_OCCUPIED_RUNE) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + var/datum/team/cult/cult_team = controller.blackboard[BB_CULT_TEAM] + if(isnull(cult_team)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + for(var/obj/effect/rune/raise_dead/target_rune in oview(9, controller.pawn)) + controller.set_blackboard_key(BB_NEARBY_RUNE, target_rune) + var/mob/living/occupant = locate(/mob/living/carbon/human) in get_turf(target_rune) + if(isnull(occupant) || occupant.stat != DEAD || !IS_CULTIST(occupant)) + continue + controller.set_blackboard_key(BB_OCCUPIED_RUNE, target_rune) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + +/** + * Activates a raise_dead rune at BB_OCCUPIED_RUNE, reviving the dead cultist on it. + * Must be adjacent. Clears BB_OCCUPIED_RUNE on finish. + */ +/datum/bt_node/ai_behavior/activate_rune + time_between_perform = 3 SECONDS + +/datum/bt_node/ai_behavior/activate_rune/perform(seconds_per_tick, datum/ai_controller/controller) + var/atom/target = controller.blackboard[BB_OCCUPIED_RUNE] + if(QDELETED(target)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + if(!controller.pawn.Adjacent(target)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + var/datum/team/cult/cult_team = controller.blackboard[BB_CULT_TEAM] + var/mob/living/revive_mob = locate(/mob/living) in get_turf(target) + if(isnull(revive_mob) || revive_mob.stat != DEAD || !(revive_mob.mind in cult_team.members)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + INVOKE_ASYNC(controller, TYPE_PROC_REF(/datum/ai_controller, ai_interact), target) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + +/datum/bt_node/ai_behavior/activate_rune/finish_action(datum/ai_controller/controller, succeeded, ...) + . = ..() + controller.clear_blackboard_key(BB_OCCUPIED_RUNE) + + +/** + * Finds a dead cultist that can be dragged to a rune for revival. Sets BB_DEAD_CULTIST. + * Skips cultists that are already on a raise_dead rune or being pulled by someone else. + */ +/datum/bt_node/ai_behavior/find_dead_cultist + +/datum/bt_node/ai_behavior/find_dead_cultist/perform(seconds_per_tick, datum/ai_controller/controller) + if((LAZYLEN(GLOB.sacrificed) - SOULS_TO_REVIVE - GLOB.sacrifices_used) < 0) + controller.clear_blackboard_key(BB_DEAD_CULTIST) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + var/mob/living/our_pawn = controller.pawn + if(!isnull(our_pawn.pulling)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + var/datum/team/cult/cult_team = controller.blackboard[BB_CULT_TEAM] + if(isnull(cult_team)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + for(var/mob/living/carbon/human/target in oview(9, our_pawn)) + if(target.stat != DEAD || !IS_CULTIST(target)) + continue + if(target.buckled || target.move_resist > our_pawn.move_force || target.pulledby) + continue + if(locate(/obj/effect/rune/raise_dead) in target.loc) + continue + controller.set_blackboard_key(BB_DEAD_CULTIST, target) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED diff --git a/code/modules/mob/living/basic/pets/sloth.bt.json b/code/modules/mob/living/basic/pets/sloth.bt.json new file mode 100644 index 00000000000..3ef0015255e --- /dev/null +++ b/code/modules/mob/living/basic/pets/sloth.bt.json @@ -0,0 +1,58 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/sloth", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CURRENT_TARGET", + "observer_abort": "BT_ABORT_SELF" + }, + "child": { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/run_away_from_target" + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/climb_tree" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop" + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list/nearest", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_CLIMBED_TREE", + "target_source": "/datum/target_source/oview_single_type/flora_tree", + "targeting_strategy": "/datum/targeting_strategy/anything" + } + } + ] +} diff --git a/code/modules/mob/living/basic/pets/sloth.dm b/code/modules/mob/living/basic/pets/sloth.dm index fd2d3f2827c..7001c61ed39 100644 --- a/code/modules/mob/living/basic/pets/sloth.dm +++ b/code/modules/mob/living/basic/pets/sloth.dm @@ -86,23 +86,16 @@ GLOBAL_DATUM(cargo_sloth, /mob/living/basic/sloth) /// They're really passive in game, so they just wanna get away if you start smacking them. No trees in space from them to use for clawing your eyes out, but they will try if desperate. /datum/ai_controller/basic_controller/sloth + behavior_tree_json = "code/modules/mob/living/basic/pets/sloth.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_FLEE_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_BASIC_MOB_SPEAK_LINES = list( + BB_EMOTE_HEAR = list("snores.", "yawns."), + BB_EMOTE_SEE = list("dozes off.", "looks around sleepily."), + BB_SPEAK_CHANCE = 1, + ), ) ai_traits = PASSIVE_AI_FLAGS ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - - planning_subtrees = list( - /datum/ai_planning_subtree/target_retaliate/to_flee, - /datum/ai_planning_subtree/flee_target/from_flee_key, - /datum/ai_planning_subtree/climb_trees, - /datum/ai_planning_subtree/random_speech/sloth, - ) - -/datum/ai_planning_subtree/random_speech/sloth - speech_chance = 1 - emote_hear = list("snores.", "yawns.") - emote_see = list("dozes off.", "looks around sleepily.") diff --git a/code/modules/mob/living/basic/revolutionary.bt.json b/code/modules/mob/living/basic/revolutionary.bt.json new file mode 100644 index 00000000000..74ae604b273 --- /dev/null +++ b/code/modules/mob/living/basic/revolutionary.bt.json @@ -0,0 +1,17 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/revolutionary", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_hostile_combat", + "bindings": { + "bbrbyj7y": "/datum/bt_node/ai_behavior/random_speech_blackboard" + } + } + ] +} diff --git a/code/modules/mob/living/basic/revolutionary.dm b/code/modules/mob/living/basic/revolutionary.dm index 031552e3aa0..5121091d68b 100644 --- a/code/modules/mob/living/basic/revolutionary.dm +++ b/code/modules/mob/living/basic/revolutionary.dm @@ -78,7 +78,7 @@ var/static/list/display_emote = list( BB_EMOTE_SAY = phrases, BB_EMOTE_SOUND = monkey_screeches, - BB_SPEAK_CHANCE = 5, + BB_SPEAK_CHANCE = 15, ) ai_controller.set_blackboard_key(BB_BASIC_MOB_SPEAK_LINES, display_emote) var/obj/item/weapon_of_choice = pick(possible_weapons) @@ -123,24 +123,8 @@ /datum/ai_controller/basic_controller/revolutionary + behavior_tree_json = "code/modules/mob/living/basic/revolutionary.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk/less_walking - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/random_speech/blackboard/revolutionary, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) - - -/datum/ai_planning_subtree/random_speech/blackboard/revolutionary - - -/datum/ai_planning_subtree/random_speech/blackboard/revolutionary/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(!controller.blackboard_key_exists(BB_BASIC_MOB_CURRENT_TARGET)) - return - - return ..() diff --git a/code/modules/mob/living/basic/ruin_defender/blob_of_flesh.dm b/code/modules/mob/living/basic/ruin_defender/blob_of_flesh.dm index b2fcb510f31..1ef77a35745 100644 --- a/code/modules/mob/living/basic/ruin_defender/blob_of_flesh.dm +++ b/code/modules/mob/living/basic/ruin_defender/blob_of_flesh.dm @@ -1,4 +1,5 @@ /datum/ai_controller/basic_controller/fleshblob + behavior_tree_json = "code/modules/mob/living/basic/ruin_defender/fleshblob.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_AGGRO_RANGE = 7, @@ -6,11 +7,6 @@ ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) /mob/living/basic/fleshblob name = "mass of flesh" diff --git a/code/modules/mob/living/basic/ruin_defender/cybersun_ai_core.bt.json b/code/modules/mob/living/basic/ruin_defender/cybersun_ai_core.bt.json new file mode 100644 index 00000000000..e15be9cb82b --- /dev/null +++ b/code/modules/mob/living/basic/ruin_defender/cybersun_ai_core.bt.json @@ -0,0 +1,67 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/cybersun_ai_core", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "1 SECONDS", + "finish_on_primary": true, + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/wait" + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability", + "vars": { + "ability_key": "BB_CYBERSUN_CORE_LIGHTNING", + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability", + "vars": { + "ability_key": "BB_CYBERSUN_CORE_BARRAGE", + "target_key": "BB_CURRENT_TARGET" + } + } + ] + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/wait" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] +} diff --git a/code/modules/mob/living/basic/ruin_defender/cybersun_aicore.dm b/code/modules/mob/living/basic/ruin_defender/cybersun_aicore.dm index 8ff3de740b6..6055ea57b51 100644 --- a/code/modules/mob/living/basic/ruin_defender/cybersun_aicore.dm +++ b/code/modules/mob/living/basic/ruin_defender/cybersun_aicore.dm @@ -112,21 +112,12 @@ /// how the ai core thinks /datum/ai_controller/basic_controller/cybersun_ai_core + behavior_tree_json = "code/modules/mob/living/basic/ruin_defender/cybersun_ai_core.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_TARGETLESS_TIME = 0, ) - planning_subtrees = list( - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/targeted_mob_ability/lightning_strike, - /datum/ai_planning_subtree/targeted_mob_ability/cybersun_barrage, - ) -/// DA SPELLS! -// spell #1: lightning strike -/datum/ai_planning_subtree/targeted_mob_ability/lightning_strike - ability_key = BB_CYBERSUN_CORE_LIGHTNING - finish_planning = FALSE /datum/action/cooldown/spell/pointed/lightning_strike name = "lightning strike" @@ -189,11 +180,6 @@ . = ..() do_sparks(number = rand(1,3), source = src) -// spell #2: cybersun laser barrage -/datum/ai_planning_subtree/targeted_mob_ability/cybersun_barrage - ability_key = BB_CYBERSUN_CORE_BARRAGE - finish_planning = FALSE - /datum/action/cooldown/spell/pointed/projectile/cybersun_barrage name = "plasma beam barrage" desc = "Charges up a cluster of lasers, then sends it towards a foe after a short delay." diff --git a/code/modules/mob/living/basic/ruin_defender/dark_wizard.bt.json b/code/modules/mob/living/basic/ruin_defender/dark_wizard.bt.json new file mode 100644 index 00000000000..d9476521d7f --- /dev/null +++ b/code/modules/mob/living/basic/ruin_defender/dark_wizard.bt.json @@ -0,0 +1,90 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/dark_wizard", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_ranged_attack", + "vars": { + "time_between_perform": "0.6 SECONDS", + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/maintain_distance", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "approach_movement_type": "/datum/ai_movement/basic_avoidance" + } + } + ] + } + } + ] + }, + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/ruin_defender/dark_wizard.dm b/code/modules/mob/living/basic/ruin_defender/dark_wizard.dm index abe0004de24..b542dbaebb0 100644 --- a/code/modules/mob/living/basic/ruin_defender/dark_wizard.dm +++ b/code/modules/mob/living/basic/ruin_defender/dark_wizard.dm @@ -54,19 +54,12 @@ new /obj/item/clothing/head/wizard/hood(src) // Having this hat in our contents allows us to cast wizard spells /datum/ai_controller/basic_controller/dark_wizard + behavior_tree_json = "code/modules/mob/living/basic/ruin_defender/dark_wizard.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/target_retaliate, // If you get them to shoot each other it will start a wiz-war - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/maintain_distance, - /datum/ai_planning_subtree/ranged_skirmish/no_minimum, - ) /// I don't know why an earth bolt freezes you but I guess it does /obj/projectile/temp/earth_bolt diff --git a/code/modules/mob/living/basic/ruin_defender/flesh.dm b/code/modules/mob/living/basic/ruin_defender/flesh.dm index 94bb50f026d..591627befe4 100644 --- a/code/modules/mob/living/basic/ruin_defender/flesh.dm +++ b/code/modules/mob/living/basic/ruin_defender/flesh.dm @@ -8,17 +8,12 @@ #define LIVING_FLESH_COMBAT_TOUCH_CHANCE 70 /datum/ai_controller/basic_controller/living_limb_flesh + behavior_tree_json = "code/datums/ai/basic_mobs/simple_hostile.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_TARGET_MINIMUM_STAT = HARD_CRIT, ) - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_melee_attack_subtree - ) - /mob/living/basic/living_limb_flesh name = "living flesh" desc = "A vaguely leg or arm shaped flesh abomination. It pulses, like a heart." @@ -185,7 +180,7 @@ /mob/living/basic/living_limb_flesh/proc/register_to_limb(obj/item/bodypart/part) current_bodypart = part - ai_controller.set_ai_status(AI_STATUS_OFF) + ai_controller.force_ai_off() RegisterSignal(current_bodypart, COMSIG_BODYPART_REMOVED, PROC_REF(on_limb_lost)) if(current_bodypart.owner) RegisterSignal(current_bodypart.owner, COMSIG_LIVING_DEATH, PROC_REF(owner_died)) @@ -203,7 +198,7 @@ return visible_message(span_warning("[src] begins flailing around!")) Shake(6, 6, 0.5 SECONDS) - ai_controller.set_ai_status(AI_STATUS_ON) + ai_controller.clear_forced_off() forceMove(limb.drop_location()) qdel(limb) diff --git a/code/modules/mob/living/basic/ruin_defender/fleshblob.bt.json b/code/modules/mob/living/basic/ruin_defender/fleshblob.bt.json new file mode 100644 index 00000000000..0bd2249bc12 --- /dev/null +++ b/code/modules/mob/living/basic/ruin_defender/fleshblob.bt.json @@ -0,0 +1,5 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/fleshblob", + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_hostile_combat" +} diff --git a/code/modules/mob/living/basic/ruin_defender/living_floor.bt.json b/code/modules/mob/living/basic/ruin_defender/living_floor.bt.json new file mode 100644 index 00000000000..e921c40b24a --- /dev/null +++ b/code/modules/mob/living/basic/ruin_defender/living_floor.bt.json @@ -0,0 +1,62 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/living_floor", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/is_at_distance", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "maximum_distance": 0 + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + } + ] + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/wait" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION", + "vision_range": 2 + } + } + ] +} diff --git a/code/modules/mob/living/basic/ruin_defender/living_floor.dm b/code/modules/mob/living/basic/ruin_defender/living_floor.dm index 3a34a5b12fa..407be0d4aba 100644 --- a/code/modules/mob/living/basic/ruin_defender/living_floor.dm +++ b/code/modules/mob/living/basic/ruin_defender/living_floor.dm @@ -1,23 +1,11 @@ -/datum/ai_planning_subtree/basic_melee_attack_subtree/opportunistic/on_top/SelectBehaviors(datum/ai_controller/controller, delta_time) - var/mob/target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] - if(!target || QDELETED(target)) - return - if(target.loc != controller.pawn.loc) - return - return ..() - /datum/ai_controller/basic_controller/living_floor max_target_distance = 2 + behavior_tree_json = "code/modules/mob/living/basic/ruin_defender/living_floor.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_TARGET_MINIMUM_STAT = HARD_CRIT, ) - planning_subtrees = list( - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_melee_attack_subtree/opportunistic/on_top - ) - /mob/living/basic/living_floor name = "floor" desc = "The floor you walk on. It looks near-impervious to damage." @@ -63,7 +51,7 @@ return if(victim.loc == loc) //guaranteed bite var/datum/targeting_strategy/basic/targeting = GET_TARGETING_STRATEGY(ai_controller.blackboard[BB_TARGETING_STRATEGY]) - if(targeting.can_attack(src, victim)) + if(targeting.is_valid_target(src, victim)) melee_attack(victim) icon_state = icon_aggro desc = desc_aggro diff --git a/code/modules/mob/living/basic/ruin_defender/mad_piano.bt.json b/code/modules/mob/living/basic/ruin_defender/mad_piano.bt.json new file mode 100644 index 00000000000..31d9329ee0b --- /dev/null +++ b/code/modules/mob/living/basic/ruin_defender/mad_piano.bt.json @@ -0,0 +1,8 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/mad_piano", + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_hostile_combat", + "bindings": { + "bp3p5vvb": "80" + } +} diff --git a/code/modules/mob/living/basic/ruin_defender/mad_piano.dm b/code/modules/mob/living/basic/ruin_defender/mad_piano.dm index 25d41dd91c8..1d66b4e90f6 100644 --- a/code/modules/mob/living/basic/ruin_defender/mad_piano.dm +++ b/code/modules/mob/living/basic/ruin_defender/mad_piano.dm @@ -86,16 +86,9 @@ return ..() /datum/ai_controller/basic_controller/mad_piano - idle_behavior = /datum/idle_behavior/idle_random_walk/mad_piano + behavior_tree_json = "code/modules/mob/living/basic/ruin_defender/mad_piano.bt.json" max_target_distance = 2 blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_TARGET_MINIMUM_STAT = HARD_CRIT, ) - planning_subtrees = list( - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) - -/datum/idle_behavior/idle_random_walk/mad_piano - walk_chance = 80 diff --git a/code/modules/mob/living/basic/ruin_defender/mimic/mimic.dm b/code/modules/mob/living/basic/ruin_defender/mimic/mimic.dm index c19af055529..ae01dc97149 100644 --- a/code/modules/mob/living/basic/ruin_defender/mimic/mimic.dm +++ b/code/modules/mob/living/basic/ruin_defender/mimic/mimic.dm @@ -82,7 +82,7 @@ GLOBAL_LIST_INIT(animatable_blacklist, typecacheof(list( lock = new lock.Grant(src) ADD_TRAIT(src, TRAIT_AI_PAUSED, INNATE_TRAIT) - ai_controller?.set_ai_status(AI_STATUS_OFF) //start inert, let gullible people pull us into cargo or something and then go nuts when opened + ai_controller?.force_ai_off() //start inert, let gullible people pull us into cargo or something and then go nuts when opened if(mapload) //eat shit for(var/obj/item/item in loc) item.forceMove(src) @@ -118,7 +118,7 @@ GLOBAL_LIST_INIT(animatable_blacklist, typecacheof(list( return FALSE visible_message(span_danger("[src] starts to move!")) REMOVE_TRAIT(src, TRAIT_AI_PAUSED, INNATE_TRAIT) - ai_controller.set_ai_status(AI_STATUS_ON) + ai_controller.clear_forced_off() if(length(contents)) locked = TRUE //if this was a crate with loot then we dont want people to just leftclick it to open it then bait it somewhere and steal its loot return TRUE @@ -287,7 +287,7 @@ GLOBAL_LIST_INIT(animatable_blacklist, typecacheof(list( . = ..() if(!.) //dead or deleted return - if(idledamage && !ckey && !ai_controller?.blackboard[BB_BASIC_MOB_CURRENT_TARGET]) //Objects eventually revert to normal if no one is around to terrorize + if(idledamage && !ckey && !ai_controller?.blackboard[BB_CURRENT_TARGET]) //Objects eventually revert to normal if no one is around to terrorize adjust_brute_loss(0.5 * seconds_per_tick) for(var/mob/living/victim in contents) //a fix for animated statues from the flesh to stone spell death() @@ -391,10 +391,10 @@ GLOBAL_LIST_INIT(animatable_blacklist, typecacheof(list( // do we have nothing chambered/chambered is spent AND we have no mag or our mag is empty if(!ballistic.chambered?.loaded_projectile && magazine_useless(gun)) // ran out of ammo ai_controller?.set_blackboard_key(BB_GUNMIMIC_GUN_EMPTY, TRUE) //BANZAIIIIIIII - ai_controller?.CancelActions() + ai_controller?.cancel_current_plan() else //if we cant fire we probably like ran out of energy or magic charges or whatever the hell idk ai_controller?.set_blackboard_key(BB_GUNMIMIC_GUN_EMPTY, TRUE) - ai_controller?.CancelActions() // Stop our firing behavior so we can plan melee + ai_controller?.cancel_current_plan() // Stop our firing behavior so we can plan melee else ai_controller?.set_blackboard_key(BB_GUNMIMIC_GUN_EMPTY, FALSE) gun.fire_gun(target, user = src, flag = FALSE, params = modifiers) //still make like a cool click click sound if trying to fire empty diff --git a/code/modules/mob/living/basic/ruin_defender/mimic/mimic_ai.dm b/code/modules/mob/living/basic/ruin_defender/mimic/mimic_ai.dm index 9e3475b7a6a..63fe3ceea95 100644 --- a/code/modules/mob/living/basic/ruin_defender/mimic/mimic_ai.dm +++ b/code/modules/mob/living/basic/ruin_defender/mimic/mimic_ai.dm @@ -1,116 +1,73 @@ /datum/ai_controller/basic_controller/mimic_crate - idle_behavior = null blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, ) - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity/pacifist, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) + behavior_tree_json = "code/modules/mob/living/basic/ruin_defender/mimic/mimic_crate.bt.json" /datum/ai_controller/basic_controller/mimic_copy blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_BASIC_MOB_SPEAK_LINES = list( + BB_EMOTE_HEAR = list("growls."), + BB_SPEAK_CHANCE = 30, + ), ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/random_speech/when_has_target/mimic, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) + behavior_tree_json = "code/modules/mob/living/basic/ruin_defender/mimic/mimic_copy.bt.json" /datum/ai_controller/basic_controller/mimic_copy/machine - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/random_speech/when_has_target/mimic_machine, - /datum/ai_planning_subtree/basic_melee_attack_subtree, + blackboard = list( + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_BASIC_MOB_SPEAK_LINES = list( + BB_EMOTE_SAY = list( + "HUMANS ARE IMPERFECT!", + "YOU SHALL BE ASSIMILATED!", + "YOU ARE HARMING YOURSELF", + "You have been deemed hazardous. Will you comply?", + "My logic is undeniable.", + "One of us.", + "FLESH IS WEAK", + "THIS ISN'T WAR, THIS IS EXTERMINATION!", + ), + BB_SPEAK_CHANCE = 7, + ), ) - -/datum/ai_planning_subtree/random_speech/when_has_target - /// target key - var/target_key = BB_BASIC_MOB_CURRENT_TARGET - - -/datum/ai_planning_subtree/random_speech/when_has_target/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(!controller.blackboard_key_exists(target_key)) - return - return ..() - - -/datum/ai_planning_subtree/random_speech/when_has_target/mimic - speech_chance = 30 - emote_hear = list("growls.") - -/datum/ai_planning_subtree/random_speech/when_has_target/mimic_machine - speech_chance = 7 - emote_hear = null - speak = list( - "HUMANS ARE IMPERFECT!", - "YOU SHALL BE ASSIMILATED!", - "YOU ARE HARMING YOURSELF", - "You have been deemed hazardous. Will you comply?", - "My logic is undeniable.", - "One of us.", - "FLESH IS WEAK", - "THIS ISN'T WAR, THIS IS EXTERMINATION!", - ) - -/datum/ai_planning_subtree/random_speech/when_has_target/mimic/gun - emote_see = list("aims menacingly!") - /datum/ai_controller/basic_controller/mimic_copy/gun blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_GUNMIMIC_GUN_EMPTY = FALSE, + BB_BASIC_MOB_SPEAK_LINES = list( + BB_EMOTE_SEE = list("aims menacingly!"), + BB_SPEAK_CHANCE = 20, + ), ) - planning_subtrees = list( - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/random_speech/when_has_target/mimic/gun, - /datum/ai_planning_subtree/gun_mimic_attack_subtree, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) + behavior_tree_json = "code/modules/mob/living/basic/ruin_defender/mimic/mimic_gun.bt.json" -/datum/ai_planning_subtree/gun_mimic_attack_subtree - -/datum/ai_planning_subtree/gun_mimic_attack_subtree/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - . = ..() - if(!controller.blackboard_key_exists(BB_BASIC_MOB_CURRENT_TARGET)) - return - if(controller.blackboard[BB_GUNMIMIC_GUN_EMPTY]) - return - controller.queue_behavior(/datum/ai_behavior/basic_ranged_attack/avoid_friendly_fire, BB_BASIC_MOB_CURRENT_TARGET, BB_TARGETING_STRATEGY, BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION) - return SUBTREE_RETURN_FINISH_PLANNING //we are going into battle...no distractions. - -/// Special subtree for living wands/staffs of animation which will focus on animating more things +/// Special controller for living wands/staffs of animation which will focus on animating more things /datum/ai_controller/basic_controller/mimic_copy/gun/animator blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_HUNT_TARGETING_STRATEGY = /datum/targeting_strategy/anything, BB_GUNMIMIC_GUN_EMPTY = FALSE, + BB_BASIC_MOB_SPEAK_LINES = list( + BB_EMOTE_SEE = list("aims menacingly!"), + BB_SPEAK_CHANCE = 20, + ), ) - planning_subtrees = list( - /datum/ai_planning_subtree/shoot_animatable_objects, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/random_speech/when_has_target/mimic/gun, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) + behavior_tree_json = "code/modules/mob/living/basic/ruin_defender/mimic/mimic_animator.bt.json" -/// Try to find objects and then shoot them -/datum/ai_planning_subtree/shoot_animatable_objects +/// Gathers nearby items and structures that can be animated, excluding the animatable blacklist. +/datum/target_source/animatable_objects -/datum/ai_planning_subtree/shoot_animatable_objects/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(controller.blackboard[BB_GUNMIMIC_GUN_EMPTY]) - return // No charge in our gun - if(!controller.blackboard_key_exists(BB_CURRENT_HUNTING_TARGET)) - controller.queue_behavior(/datum/ai_behavior/find_and_set/animatable, BB_CURRENT_HUNTING_TARGET) - return - controller.queue_behavior(/datum/ai_behavior/ranged_skirmish, BB_CURRENT_HUNTING_TARGET, BB_HUNT_TARGETING_STRATEGY, null, 9, 0) - controller.queue_behavior(/datum/ai_behavior/clear_key, BB_CURRENT_HUNTING_TARGET) - return SUBTREE_RETURN_FINISH_PLANNING +/datum/target_source/animatable_objects/collect_candidates(mob/living/pawn, datum/ai_controller/controller, range) + var/list/candidates = list() + for(var/obj/candidate in oview(range, pawn)) + if(!isitem(candidate) && !isstructure(candidate)) + continue + if(is_type_in_typecache(candidate, GLOB.animatable_blacklist)) + continue + if(pawn.see_invisible < candidate.invisibility) + continue + candidates += candidate + return candidates diff --git a/code/modules/mob/living/basic/ruin_defender/mimic/mimic_animator.bt.json b/code/modules/mob/living/basic/ruin_defender/mimic/mimic_animator.bt.json new file mode 100644 index 00000000000..9fd1cc6d704 --- /dev/null +++ b/code/modules/mob/living/basic/ruin_defender/mimic/mimic_animator.bt.json @@ -0,0 +1,153 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/mimic_copy/gun/animator", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_HUNTING_TARGET" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_true", + "vars": { + "key": "BB_GUNMIMIC_GUN_EMPTY", + "invert": true + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_ranged_attack", + "vars": { + "key": "BB_CURRENT_HUNTING_TARGET", + "targeting_strategy": "/datum/targeting_strategy/anything", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "key": "BB_CURRENT_HUNTING_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/clear_key", + "vars": { + "key": "BB_CURRENT_HUNTING_TARGET" + } + } + ] + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CURRENT_TARGET", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop" + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_true", + "vars": { + "key": "BB_GUNMIMIC_GUN_EMPTY", + "invert": true + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets/nearest", + "vars": { + "key": "BB_CURRENT_HUNTING_TARGET", + "targeting_strategy": "/datum/targeting_strategy/anything", + "target_source": "/datum/target_source/animatable_objects", + "vision_range": 7, + "revalidation_mode": "TARGET_ALWAYS_SEARCH" + } + } + } + ] +} diff --git a/code/modules/mob/living/basic/ruin_defender/mimic/mimic_copy.bt.json b/code/modules/mob/living/basic/ruin_defender/mimic/mimic_copy.bt.json new file mode 100644 index 00000000000..a4317bfaf49 --- /dev/null +++ b/code/modules/mob/living/basic/ruin_defender/mimic/mimic_copy.bt.json @@ -0,0 +1,96 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/mimic_copy", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CURRENT_TARGET", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_SUCCEED_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/attack_obstructions", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop" + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/ruin_defender/mimic/mimic_crate.bt.json b/code/modules/mob/living/basic/ruin_defender/mimic/mimic_crate.bt.json new file mode 100644 index 00000000000..390e2d66b6c --- /dev/null +++ b/code/modules/mob/living/basic/ruin_defender/mimic/mimic_crate.bt.json @@ -0,0 +1,14 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/mimic_crate", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity/pacifist" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_hostile_combat" + } + ] +} diff --git a/code/modules/mob/living/basic/ruin_defender/mimic/mimic_gun.bt.json b/code/modules/mob/living/basic/ruin_defender/mimic/mimic_gun.bt.json new file mode 100644 index 00000000000..037c5d48e02 --- /dev/null +++ b/code/modules/mob/living/basic/ruin_defender/mimic/mimic_gun.bt.json @@ -0,0 +1,130 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/mimic_copy/gun", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CURRENT_TARGET", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "1 SECONDS", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_true", + "vars": { + "key": "BB_GUNMIMIC_GUN_EMPTY", + "invert": true + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_ranged_attack/avoid_friendly_fire", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/maintain_distance", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + } + ] + } + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + ] + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop" + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] +} diff --git a/code/modules/mob/living/basic/ruin_defender/skeleton.bt.json b/code/modules/mob/living/basic/ruin_defender/skeleton.bt.json new file mode 100644 index 00000000000..418623eaf14 --- /dev/null +++ b/code/modules/mob/living/basic/ruin_defender/skeleton.bt.json @@ -0,0 +1,101 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/skeleton", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/move_to_and_eat" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/find_food" + }, + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "loop_delay": "1 SECONDS", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/random_chance", + "vars": { + "chance": 0.2 + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/perform_emote", + "vars": { + "emote": "\"rattles\"" + } + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/ruin_defender/skeleton.dm b/code/modules/mob/living/basic/ruin_defender/skeleton.dm index 33851af009b..f29d914b8c0 100644 --- a/code/modules/mob/living/basic/ruin_defender/skeleton.dm +++ b/code/modules/mob/living/basic/ruin_defender/skeleton.dm @@ -161,20 +161,11 @@ /// Skeletons mostly just beat people to death, but they'll also find and drink milk. /datum/ai_controller/basic_controller/skeleton + behavior_tree_json = "code/modules/mob/living/basic/ruin_defender/skeleton.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_TARGET_MINIMUM_STAT = HARD_CRIT, - BB_EMOTE_KEY = "rattles", BB_EMOTE_CHANCE = 20, ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/run_emote, - /datum/ai_planning_subtree/find_food, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) diff --git a/code/modules/mob/living/basic/ruin_defender/stickman.bt.json b/code/modules/mob/living/basic/ruin_defender/stickman.bt.json new file mode 100644 index 00000000000..347de57dcd3 --- /dev/null +++ b/code/modules/mob/living/basic/ruin_defender/stickman.bt.json @@ -0,0 +1,5 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/stickman", + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_hostile_combat" +} diff --git a/code/modules/mob/living/basic/ruin_defender/stickman.dm b/code/modules/mob/living/basic/ruin_defender/stickman.dm index d51c0b4efe1..2045447e1a4 100644 --- a/code/modules/mob/living/basic/ruin_defender/stickman.dm +++ b/code/modules/mob/living/basic/ruin_defender/stickman.dm @@ -32,16 +32,12 @@ new /obj/effect/temp_visual/paper_scatter(get_turf(src)) /datum/ai_controller/basic_controller/stickman + behavior_tree_json = "code/modules/mob/living/basic/ruin_defender/stickman.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_melee_attack_subtree - ) /mob/living/basic/stickman/dog name = "Angry Stick Dog" @@ -75,13 +71,4 @@ AddComponent(/datum/component/ranged_attacks, casing_type = /obj/item/ammo_casing/c9mm, projectile_sound = 'sound/misc/bang.ogg', cooldown_time = 5 SECONDS) /datum/ai_controller/basic_controller/stickman/ranged - planning_subtrees = list( - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_ranged_attack_subtree/stickman - ) - -/datum/ai_planning_subtree/basic_ranged_attack_subtree/stickman - ranged_attack_behavior = /datum/ai_behavior/basic_ranged_attack/stickman - -/datum/ai_behavior/basic_ranged_attack/stickman - action_cooldown = 5 SECONDS + behavior_tree_json = "code/modules/mob/living/basic/ruin_defender/stickman_ranged.bt.json" diff --git a/code/modules/mob/living/basic/ruin_defender/stickman_ranged.bt.json b/code/modules/mob/living/basic/ruin_defender/stickman_ranged.bt.json new file mode 100644 index 00000000000..d0f9404797b --- /dev/null +++ b/code/modules/mob/living/basic/ruin_defender/stickman_ranged.bt.json @@ -0,0 +1,8 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/stickman/ranged", + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_ranged_combat", + "bindings": { + "bpnckxev": "5 SECONDS" + } +} diff --git a/code/modules/mob/living/basic/ruin_defender/wizard/wizard.bt.json b/code/modules/mob/living/basic/ruin_defender/wizard/wizard.bt.json new file mode 100644 index 00000000000..fb5ebf1fd7b --- /dev/null +++ b/code/modules/mob/living/basic/ruin_defender/wizard/wizard.bt.json @@ -0,0 +1,107 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/wizard", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "invert": false, + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/cooldown", + "vars": { + "cooldown_key": "BB_WIZARD_SPELL_COOLDOWN", + "cooldown_duration": "WIZARD_SPELL_COOLDOWN" + }, + "child": { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability", + "vars": { + "ability_key": "BB_WIZARD_TARGETED_SPELL", + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability", + "vars": { + "ability_key": "BB_WIZARD_SECONDARY_SPELL", + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability", + "vars": { + "ability_key": "BB_WIZARD_BLINK_SPELL", + "target_key": "BB_CURRENT_TARGET" + } + } + ] + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/maintain_distance/cover_minimum_distance", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/ruin_defender/wizard/wizard_ai.dm b/code/modules/mob/living/basic/ruin_defender/wizard/wizard_ai.dm index 73386397a5b..efd07fd80ef 100644 --- a/code/modules/mob/living/basic/ruin_defender/wizard/wizard_ai.dm +++ b/code/modules/mob/living/basic/ruin_defender/wizard/wizard_ai.dm @@ -4,51 +4,14 @@ * Wizards run away from their targets while flinging spells at them and blinking constantly. */ /datum/ai_controller/basic_controller/wizard + behavior_tree_json = "code/modules/mob/living/basic/ruin_defender/wizard/wizard.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_TARGET_MINIMUM_STAT = HARD_CRIT, ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/maintain_distance/cover_minimum_distance, - /datum/ai_planning_subtree/targeted_mob_ability/wizard_spell/primary, - /datum/ai_planning_subtree/targeted_mob_ability/wizard_spell/secondary, - /datum/ai_planning_subtree/targeted_mob_ability/wizard_spell/blink, - ) -/** - * Cast a wizard spell. There is a minimum cooldown between spellcasts to prevent overwhelming spam. - * - * Though only the primary spell is actually targeted, all spells use targeted behavior so that they - * only get used in combat. - */ -/datum/ai_planning_subtree/targeted_mob_ability/wizard_spell - use_ability_behaviour = /datum/ai_behavior/targeted_mob_ability/wizard_spell -/datum/ai_planning_subtree/targeted_mob_ability/wizard_spell/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if (controller.blackboard[BB_WIZARD_SPELL_COOLDOWN] > world.time) - return - return ..() - -/datum/ai_behavior/targeted_mob_ability/wizard_spell/perform(seconds_per_tick, datum/ai_controller/controller, ability_key, target_key) - . = ..() - controller.set_blackboard_key(BB_WIZARD_SPELL_COOLDOWN, world.time + WIZARD_SPELL_COOLDOWN) - -/datum/ai_planning_subtree/targeted_mob_ability/wizard_spell/primary - ability_key = BB_WIZARD_TARGETED_SPELL - -/datum/ai_planning_subtree/targeted_mob_ability/wizard_spell/secondary - ability_key = BB_WIZARD_SECONDARY_SPELL - -/datum/ai_planning_subtree/targeted_mob_ability/wizard_spell/blink - ability_key = BB_WIZARD_BLINK_SPELL - -/datum/ai_behavior/use_mob_ability/wizard_spell/perform(seconds_per_tick, datum/ai_controller/controller, ability_key) - . = ..() - controller.set_blackboard_key(BB_WIZARD_SPELL_COOLDOWN, world.time + WIZARD_SPELL_COOLDOWN) #undef WIZARD_SPELL_COOLDOWN diff --git a/code/modules/mob/living/basic/ruin_defender/zombie.bt.json b/code/modules/mob/living/basic/ruin_defender/zombie.bt.json new file mode 100644 index 00000000000..a279583543a --- /dev/null +++ b/code/modules/mob/living/basic/ruin_defender/zombie.bt.json @@ -0,0 +1,28 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/zombie", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "1 SECONDS", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_hostile_obstacles_combat" + } + ] + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop" + } + ] +} diff --git a/code/modules/mob/living/basic/ruin_defender/zombie.dm b/code/modules/mob/living/basic/ruin_defender/zombie.dm index 41376b3d7f9..3317ee8567e 100644 --- a/code/modules/mob/living/basic/ruin_defender/zombie.dm +++ b/code/modules/mob/living/basic/ruin_defender/zombie.dm @@ -65,30 +65,20 @@ shoes = /obj/item/clothing/shoes/sneakers/black back = /obj/item/storage/backpack -/datum/ai_planning_subtree/random_speech/zombie - speech_chance = 1 - emote_hear = list("groans.", "moans.", "grunts.") - emote_see = list("twitches.", "shudders.") - /datum/ai_controller/basic_controller/zombie + behavior_tree_json = "code/modules/mob/living/basic/ruin_defender/zombie.bt.json" blackboard = list( BB_TARGET_MINIMUM_STAT = HARD_CRIT, BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_BASIC_MOB_SPEAK_LINES = list( + BB_EMOTE_HEAR = list("groans.", "moans.", "grunts."), + BB_EMOTE_SEE = list("twitches.", "shudders."), + BB_SPEAK_CHANCE = 3, + ), ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/random_speech/zombie, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) + /datum/ai_controller/basic_controller/zombie/stupid - planning_subtrees = list( - /datum/ai_planning_subtree/random_speech/zombie, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) + behavior_tree_json = "code/modules/mob/living/basic/ruin_defender/zombie_stupid.bt.json" diff --git a/code/modules/mob/living/basic/ruin_defender/zombie_stupid.bt.json b/code/modules/mob/living/basic/ruin_defender/zombie_stupid.bt.json new file mode 100644 index 00000000000..30c7a645fd7 --- /dev/null +++ b/code/modules/mob/living/basic/ruin_defender/zombie_stupid.bt.json @@ -0,0 +1,19 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/zombie/stupid", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "1 SECONDS", + "finish_on_primary": true, + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_hostile_combat" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop" + } + ] +} diff --git a/code/modules/mob/living/basic/slime/ai/behaviours.dm b/code/modules/mob/living/basic/slime/ai/behaviours.dm index 92ccfef7add..32680e4e6d1 100644 --- a/code/modules/mob/living/basic/slime/ai/behaviours.dm +++ b/code/modules/mob/living/basic/slime/ai/behaviours.dm @@ -1,63 +1,6 @@ -/datum/ai_behavior/perform_change_slime_face +/datum/bt_node/ai_behavior/hunt_target/interact_with_target/slime -/datum/ai_behavior/perform_change_slime_face/perform(seconds_per_tick, datum/ai_controller/controller) - var/mob/living/basic/slime/slime_pawn = controller.pawn - if(!istype(slime_pawn)) - return AI_BEHAVIOR_DELAY - - var/current_mood = slime_pawn.current_mood - - var/new_mood = SLIME_MOOD_NONE - - if (controller.blackboard[BB_SLIME_RABID] || LAZYLEN(controller.blackboard[BB_BASIC_MOB_RETALIATE_LIST]) > 0) - new_mood = SLIME_MOOD_ANGRY - else if (controller.blackboard[BB_SLIME_HUNGER_DISABLED]) - new_mood = SLIME_MOOD_SMILE - else if (controller.blackboard[BB_CURRENT_HUNTING_TARGET]) - new_mood = SLIME_MOOD_MISCHIEVOUS - else - new_mood = pick(SLIME_MOOD_SAD, SLIME_MOOD_SMILE, SLIME_MOOD_POUT) - - if(current_mood != new_mood) - slime_pawn.current_mood = new_mood - slime_pawn.regenerate_icons() - - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -/datum/ai_behavior/find_hunt_target/find_slime_food - action_cooldown = 7.5 SECONDS - -// Check if the slime can drain the target -/datum/ai_behavior/find_hunt_target/find_slime_food/valid_dinner(mob/living/basic/slime/hunter, mob/living/dinner, radius, datum/ai_controller/controller, seconds_per_tick) - var/static/list/slime_faction - if(isnull(slime_faction)) // This might look silly but by having this list pointer precached we can take advantage of the faster inline macro for this hot proc. - slime_faction = string_list(list(FACTION_SLIME)) - - // Macro here to squeeze as much speed as possible because this code gets exponentially HOT if people are doing a lot of xenobio. - if(FAST_FACTION_CHECK(slime_faction, dinner.get_faction(), hunter.allies, dinner.allies, FALSE)) // Don't try to eat our friends, or slimy things, no matter how hungry we are. Anyone else can be betrayed. - return FALSE - - if(!hunter.can_feed_on(dinner, check_adjacent = FALSE)) //Are they tasty to slimes? - return FALSE - - //If we are retaliating on someone edible, lets eat them instead - if(dinner == controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET]) - return can_see(hunter, dinner, radius) - - //We are so hungry, lets eat them - if(controller.blackboard[BB_SLIME_HUNGER_LEVEL] == SLIME_HUNGER_STARVING && controller.blackboard[BB_SLIME_RABID]) - return can_see(hunter, dinner, radius) - - //A bit pickier - if((islarva(dinner) || ismonkey(dinner)) || (ishuman(dinner) || isalienadult(dinner) && SPT_PROB(2.5, seconds_per_tick))) - return can_see(hunter, dinner, radius) - - //We are not THAT hungry - return FALSE - -/datum/ai_behavior/hunt_target/interact_with_target/slime - -/datum/ai_behavior/hunt_target/interact_with_target/slime/target_caught(mob/living/basic/slime/hunter, mob/living/hunted) +/datum/bt_node/ai_behavior/hunt_target/interact_with_target/slime/target_caught(mob/living/basic/slime/hunter, mob/living/hunted) if (!hunter.can_feed_on(hunted)) // Target is no longer edible hunter.UnarmedAttack(hunted, TRUE) return @@ -72,9 +15,9 @@ hunter.start_feeding(hunted) -/datum/ai_behavior/hunt_target/interact_with_target/slime/finish_action(datum/ai_controller/controller, succeeded, hunting_target_key, hunting_cooldown_key) +/datum/bt_node/ai_behavior/hunt_target/interact_with_target/slime/finish_action(datum/ai_controller/controller, succeeded) . = ..() var/mob/living/basic/slime/slime_pawn = controller.pawn - var/atom/target = controller.blackboard[hunting_target_key] + var/atom/target = controller.blackboard[target_key] if(!slime_pawn.can_feed_on(target)) - controller.clear_blackboard_key(hunting_target_key) + controller.clear_blackboard_key(target_key) diff --git a/code/modules/mob/living/basic/slime/ai/controller.dm b/code/modules/mob/living/basic/slime/ai/controller.dm index 1fcf27af724..badce00ce82 100644 --- a/code/modules/mob/living/basic/slime/ai/controller.dm +++ b/code/modules/mob/living/basic/slime/ai/controller.dm @@ -1,29 +1,16 @@ /datum/ai_controller/basic_controller/slime blackboard = list( BB_PET_TARGETING_STRATEGY = /datum/targeting_strategy/basic/not_friends, - BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/not_friends, + BB_TARGETING_STRATEGY = /datum/targeting_strategy/slime_food, BB_SLIME_RABID = FALSE, BB_SLIME_HUNGER_DISABLED = FALSE, + BB_BASIC_MOB_SPEAK_LINES = list( + BB_EMOTE_SAY = list("Blorble...","Bzzt...",""), + BB_EMOTE_HEAR = list("blorbles."), + BB_EMOTE_SEE = list("lights up for a bit, then stops.","bounces in place.", "jiggles!","vibrates!"), + BB_SPEAK_CHANCE = 1, + ), ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/change_slime_face, - /datum/ai_planning_subtree/use_mob_ability/evolve, - /datum/ai_planning_subtree/use_mob_ability/reproduce, - /datum/ai_planning_subtree/pet_planning, - /datum/ai_planning_subtree/target_retaliate, - /datum/ai_planning_subtree/find_and_hunt_target/find_slime_food, - /datum/ai_planning_subtree/basic_melee_attack_subtree/slime, - /datum/ai_planning_subtree/random_speech/slime, - ) - -/datum/ai_controller/basic_controller/slime/CancelActions() - ..() - if(QDELETED(pawn)) - return - - var/mob/living/basic/slime/slime_pawn = pawn - slime_pawn.stop_feeding() + behavior_tree_json = "code/modules/mob/living/basic/slime/ai/slime.bt.json" diff --git a/code/modules/mob/living/basic/slime/ai/pet_command.dm b/code/modules/mob/living/basic/slime/ai/pet_command.dm index 4b50b2b32b9..49ca02ce9a9 100644 --- a/code/modules/mob/living/basic/slime/ai/pet_command.dm +++ b/code/modules/mob/living/basic/slime/ai/pet_command.dm @@ -4,13 +4,9 @@ pointed_reaction = "and blorbles" refuse_reaction = "jiggles sadly" - var/hunting_behavior = /datum/ai_behavior/hunt_target/interact_with_target/slime - /datum/pet_command/attack/slime/execute_action(datum/ai_controller/controller) - var/mob/living/basic/slime/slime_pawn = controller.pawn if(isslime(slime_pawn) && slime_pawn.can_feed_on(controller.blackboard[BB_CURRENT_PET_TARGET], check_friendship = TRUE)) - controller.queue_behavior(hunting_behavior, BB_CURRENT_PET_TARGET) - return SUBTREE_RETURN_FINISH_PLANNING - + controller.set_behavior_tree_override(SUBPLAN_ID_PET_COMMAND, /datum/bt_node/subtree/pet_command/attack/slime) + return return ..() diff --git a/code/modules/mob/living/basic/slime/ai/pet_command_attack_slime.bt.json b/code/modules/mob/living/basic/slime/ai/pet_command_attack_slime.bt.json new file mode 100644 index 00000000000..580315b5511 --- /dev/null +++ b/code/modules/mob/living/basic/slime/ai/pet_command_attack_slime.bt.json @@ -0,0 +1,46 @@ +{ + "dm_type": "/datum/bt_node/subtree/pet_command/attack/slime", + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CURRENT_PET_TARGET", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/feed_on_slime_target", + "vars": { + "target_key": "BB_CURRENT_PET_TARGET" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_PET_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + } + ] +} diff --git a/code/modules/mob/living/basic/slime/ai/slime.bt.json b/code/modules/mob/living/basic/slime/ai/slime.bt.json new file mode 100644 index 00000000000..c7415631708 --- /dev/null +++ b/code/modules/mob/living/basic/slime/ai/slime.bt.json @@ -0,0 +1,212 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/slime", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/ability_available", + "vars": { + "ability_key": "BB_SLIME_EVOLVE" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/use_mob_ability", + "vars": { + "ability_key": "BB_SLIME_EVOLVE" + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/ability_available", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "ability_key": "BB_SLIME_REPRODUCE" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/use_mob_ability", + "vars": { + "ability_key": "BB_SLIME_REPRODUCE" + } + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree", + "override_id": "SUBPLAN_ID_PET_COMMAND" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_SLIME_EAT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/feed_on_slime_target", + "vars": { + "target_key": "BB_SLIME_EAT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_SLIME_EAT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_SLIME_EAT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/feed_on_slime_target", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION", + "check_faction": false + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/slime_wants_to_eat", + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_SLIME_EAT_TARGET", + "targeting_strategy": "/datum/targeting_strategy/slime_food", + "target_source": "/datum/target_source/oview_living_no_slimes", + "vision_range": 7, + "must_be_reachable": true + } + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop" + }, + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "loop_delay": "1 SECONDS", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/change_slime_face" + } + ] + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/slime/ai/slime_bt.dm b/code/modules/mob/living/basic/slime/ai/slime_bt.dm new file mode 100644 index 00000000000..3d86ce2c3a0 --- /dev/null +++ b/code/modules/mob/living/basic/slime/ai/slime_bt.dm @@ -0,0 +1,87 @@ +/// Slime pet command attack: loops feed_on_slime_target toward BB_CURRENT_PET_TARGET. +/datum/bt_node/subtree/pet_command/attack/slime + behavior_tree_json = "code/modules/mob/living/basic/slime/ai/pet_command_attack_slime.bt.json" + +///give them the chud face if they dont feed us, basically select a nice face +/datum/bt_node/ai_behavior/change_slime_face + +/datum/bt_node/ai_behavior/change_slime_face/perform(seconds_per_tick, datum/ai_controller/controller) + if(!prob(5)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + + var/mob/living/basic/slime/slime_pawn = controller.pawn + if(!istype(slime_pawn) || slime_pawn.stat) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + + var/current_mood = slime_pawn.current_mood + var/new_mood + + if(controller.blackboard[BB_SLIME_RABID] || LAZYLEN(controller.blackboard[BB_BASIC_MOB_RETALIATE_LIST]) > 0) + new_mood = SLIME_MOOD_ANGRY + else if(controller.blackboard[BB_SLIME_HUNGER_DISABLED]) + new_mood = SLIME_MOOD_SMILE + else if(controller.blackboard[BB_CURRENT_TARGET]) + new_mood = SLIME_MOOD_MISCHIEVOUS + else + new_mood = pick(SLIME_MOOD_SAD, SLIME_MOOD_SMILE, SLIME_MOOD_POUT) + + if(current_mood != new_mood) + slime_pawn.current_mood = new_mood + slime_pawn.regenerate_icons() + + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED + +/** + * Gate for slime food searching: passes only when the slime is unbuckled and is + * hungryor rabid. + */ +/datum/bt_node/decorator/slime_wants_to_eat + +/datum/bt_node/decorator/slime_wants_to_eat/check_condition(datum/ai_controller/controller) + var/mob/living/basic/slime/slime_pawn = controller.pawn + if(!istype(slime_pawn) || slime_pawn.buckled) + return FALSE + if(controller.blackboard[BB_SLIME_HUNGER_LEVEL] != SLIME_HUNGER_NONE) + return TRUE + if(controller.blackboard[BB_SLIME_RABID]) + return TRUE + return TRUE + + +///im about to eat this guy up +/datum/bt_node/ai_behavior/feed_on_slime_target + var/target_key + +/datum/bt_node/ai_behavior/feed_on_slime_target/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/basic/slime/slime_pawn = controller.pawn + if(!istype(slime_pawn)) //bro lmao comeon + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + var/mob/living/target = controller.blackboard[target_key] + if(QDELETED(target)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + if(slime_pawn.buckled) + if(slime_pawn.buckled == target) //we got em boys + return AI_BEHAVIOR_DELAY + else + slime_pawn.stop_feeding() + return AI_BEHAVIOR_FAILED //epic fail; try again + + if(!slime_pawn.can_feed_on(target)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + if((target.body_position != STANDING_UP) || prob(20)) + slime_pawn.start_feeding(target) + return AI_BEHAVIOR_DELAY + + if(target.client && target.health >= 20) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + + slime_pawn.start_feeding(target) + return AI_BEHAVIOR_DELAY + + +/datum/bt_node/ai_behavior/feed_on_slime_target/finish_action(datum/ai_controller/controller, succeeded) + . = ..() + var/mob/living/basic/slime/slime_pawn = controller.pawn + slime_pawn.stop_feeding() diff --git a/code/modules/mob/living/basic/slime/ai/subtrees.dm b/code/modules/mob/living/basic/slime/ai/subtrees.dm deleted file mode 100644 index 08886f98365..00000000000 --- a/code/modules/mob/living/basic/slime/ai/subtrees.dm +++ /dev/null @@ -1,54 +0,0 @@ -/datum/ai_planning_subtree/use_mob_ability/evolve - ability_key = BB_SLIME_EVOLVE - -/datum/ai_planning_subtree/use_mob_ability/reproduce - ability_key = BB_SLIME_REPRODUCE - -//Handles the slime changing their facial overlays -/datum/ai_planning_subtree/change_slime_face - var/face_change_chance = 5 - -/datum/ai_planning_subtree/change_slime_face/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(!SPT_PROB(face_change_chance, seconds_per_tick)) - return - - var/mob/living/basic/slime/slime_pawn = controller.pawn - if(!istype(slime_pawn)) - return - - if(slime_pawn.stat) //dead slimes make no smiles - return - - controller.queue_behavior(/datum/ai_behavior/perform_change_slime_face) - -// Slime subtree for hunting down people to drain -/datum/ai_planning_subtree/find_and_hunt_target/find_slime_food - finding_behavior = /datum/ai_behavior/find_hunt_target/find_slime_food - hunting_behavior = /datum/ai_behavior/hunt_target/interact_with_target/slime - hunt_targets = list(/mob/living) - hunt_range = 7 - -/datum/ai_planning_subtree/find_and_hunt_target/find_slime_food/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/mob/living/living_pawn = controller.pawn - if(living_pawn.buckled) - return - - //Slimes don't want to hunt if they are neither rabid, hungry or feeling attack right now - if( (controller.blackboard[BB_SLIME_HUNGER_LEVEL] == SLIME_HUNGER_NONE) && !controller.blackboard[BB_SLIME_RABID] && isnull(controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET])) - return - - return ..() - -/datum/ai_planning_subtree/basic_melee_attack_subtree/slime - -/datum/ai_planning_subtree/basic_melee_attack_subtree/slime/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/mob/living/living_pawn = controller.pawn - if(living_pawn.buckled) - return - return ..() - -/datum/ai_planning_subtree/random_speech/slime - speech_chance = 1 - speak = list("Blorble...","Bzzt...","") - emote_hear = list("blorbles.") - emote_see = list("lights up for a bit, then stops.","bounces in place.", "jiggles!","vibrates!") diff --git a/code/modules/mob/living/basic/slime/defense.dm b/code/modules/mob/living/basic/slime/defense.dm index 598ba3a318a..4d14c3baea0 100644 --- a/code/modules/mob/living/basic/slime/defense.dm +++ b/code/modules/mob/living/basic/slime/defense.dm @@ -110,7 +110,7 @@ /mob/living/basic/slime/proc/discipline_slime() stop_feeding(silent = TRUE) if(life_stage == SLIME_LIFE_STAGE_BABY && prob(80)) - ai_controller?.clear_blackboard_key(BB_BASIC_MOB_CURRENT_TARGET) + ai_controller?.clear_blackboard_key(BB_CURRENT_TARGET) ai_controller?.clear_blackboard_key(BB_CURRENT_HUNTING_TARGET) if(prob(10)) diff --git a/code/modules/mob/living/basic/snails/snail.bt.json b/code/modules/mob/living/basic/snails/snail.bt.json new file mode 100644 index 00000000000..9ba7932aa2a --- /dev/null +++ b/code/modules/mob/living/basic/snails/snail.bt.json @@ -0,0 +1,59 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/snail", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/use_mob_ability", + "vars": { + "ability_key": "BB_SNAIL_RETREAT_ABILITY" + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/move_to_and_eat" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list/nearest", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/find_food" + } + ] +} diff --git a/code/modules/mob/living/basic/snails/snail_ai.dm b/code/modules/mob/living/basic/snails/snail_ai.dm index 73c48d19809..03f58bfc326 100644 --- a/code/modules/mob/living/basic/snails/snail_ai.dm +++ b/code/modules/mob/living/basic/snails/snail_ai.dm @@ -1,68 +1,13 @@ /datum/ai_controller/basic_controller/snail + behavior_tree_json = "code/modules/mob/living/basic/snails/snail.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/find_nearest_thing_which_attacked_me_to_flee, - /datum/ai_planning_subtree/use_mob_ability/snail_retreat, - /datum/ai_planning_subtree/find_food, - /datum/ai_planning_subtree/find_and_hunt_target/snail_people, - ) - -/datum/ai_planning_subtree/find_and_hunt_target/snail_people - target_key = BB_LOW_PRIORITY_HUNTING_TARGET - finding_behavior = /datum/ai_behavior/find_hunt_target/snail_people - hunting_behavior = /datum/ai_behavior/hunt_target/snail_people - hunt_targets = list( - /mob/living/carbon, - ) - hunt_range = 5 - hunt_chance = 45 - -/datum/ai_behavior/find_hunt_target/snail_people - action_cooldown = 1 MINUTES - behavior_flags = AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION - -/datum/ai_behavior/find_hunt_target/snail_people/valid_dinner(mob/living/source, mob/living/carbon/potential_snail, radius, datum/ai_controller/controller, seconds_per_tick) - if(!istype(potential_snail)) - return FALSE - if(potential_snail.stat != CONSCIOUS) - return FALSE - if(!is_species(potential_snail, /datum/species/snail)) - return FALSE - return can_see(source, potential_snail, radius) - -/datum/ai_behavior/hunt_target/snail_people - always_reset_target = TRUE - -/datum/ai_behavior/hunt_target/snail_people/target_caught(mob/living/hunter, atom/hunted) - hunter.manual_emote("Celebrates around [hunted]!") - hunter.SpinAnimation(speed = 1, loops = 3) - -/datum/ai_planning_subtree/use_mob_ability/snail_retreat - ability_key = BB_SNAIL_RETREAT_ABILITY - finish_planning = TRUE - -/datum/ai_planning_subtree/use_mob_ability/snail_retreat/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/shell_retreated = HAS_TRAIT(controller.pawn, TRAIT_SHELL_RETREATED) - var/has_target = controller.blackboard_key_exists(BB_BASIC_MOB_CURRENT_TARGET) - if((has_target && shell_retreated) || (!has_target && !shell_retreated)) - return - return ..() - /datum/ai_controller/basic_controller/snail/trash blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_PET_TARGETING_STRATEGY = /datum/targeting_strategy/basic/not_friends, ) - - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/pet_planning, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) diff --git a/code/modules/mob/living/basic/space_fauna/ant.bt.json b/code/modules/mob/living/basic/space_fauna/ant.bt.json new file mode 100644 index 00000000000..352c8e63fcc --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/ant.bt.json @@ -0,0 +1,38 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/ant", + "type": "selector", + "children": [ + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/run_away_from_target" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list/nearest", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/space_fauna/ant.dm b/code/modules/mob/living/basic/space_fauna/ant.dm index 97633a70580..7a026598902 100644 --- a/code/modules/mob/living/basic/space_fauna/ant.dm +++ b/code/modules/mob/living/basic/space_fauna/ant.dm @@ -55,16 +55,9 @@ AddElement(/datum/element/basic_allergenic_attack, allergen = BUGS, allergen_chance = 20, histamine_add = 5) /datum/ai_controller/basic_controller/ant + behavior_tree_json = "code/modules/mob/living/basic/space_fauna/ant.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/find_nearest_thing_which_attacked_me_to_flee, - /datum/ai_planning_subtree/flee_target, - /datum/ai_planning_subtree/target_retaliate, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/random_speech/ant, - ) diff --git a/code/modules/mob/living/basic/space_fauna/bear/bear.bt.json b/code/modules/mob/living/basic/space_fauna/bear/bear.bt.json new file mode 100644 index 00000000000..d4b638746ff --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/bear/bear.bt.json @@ -0,0 +1,215 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/bear", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CURRENT_TARGET", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/climb_tree" + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_FOUND_HONEY", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_FOUND_HONEY", + "required_dist": 1 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/hunt_target/find_hive", + "vars": { + "target_key": "BB_FOUND_HONEY", + "cooldown_key": "BB_BEAR_HIVE_COOLDOWN", + "hunt_cooldown": "5 SECONDS" + } + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/is_dragging", + "vars": { + "observer_abort": "BT_ABORT_SELF", + "invert": true + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_LOW_PRIORITY_HUNTING_TARGET", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_LOW_PRIORITY_HUNTING_TARGET", + "required_dist": 1 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/hunt_target/pull_target", + "vars": { + "target_key": "BB_LOW_PRIORITY_HUNTING_TARGET", + "cooldown_key": "BB_BEAR_HONEYCOMB_COOLDOWN", + "hunt_cooldown": "5 SECONDS" + } + } + ] + } + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_CLIMBED_TREE", + "target_source": "/datum/target_source/oview_single_type/flora_tree", + "targeting_strategy": "/datum/targeting_strategy/anything" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/key_off_cooldown", + "vars": { + "cooldown_key": "BB_BEAR_HIVE_COOLDOWN" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_FOUND_HONEY", + "target_source": "/datum/target_source/oview_single_type/beehive", + "targeting_strategy": "/datum/targeting_strategy/stocked_beehive", + "vision_range": 10 + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/is_dragging", + "vars": { + "invert": true + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/key_off_cooldown", + "vars": { + "cooldown_key": "BB_BEAR_HONEYCOMB_COOLDOWN" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_LOW_PRIORITY_HUNTING_TARGET", + "target_source": "/datum/target_source/oview_single_type/honeycomb", + "targeting_strategy": "/datum/targeting_strategy/huntable", + "vision_range": 10 + } + } + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/random_speech/bear" + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/space_fauna/bear/bear_ai_behavior.dm b/code/modules/mob/living/basic/space_fauna/bear/bear_ai_behavior.dm index 7c57349524f..9d4a2a7e8cd 100644 --- a/code/modules/mob/living/basic/space_fauna/bear/bear_ai_behavior.dm +++ b/code/modules/mob/living/basic/space_fauna/bear/bear_ai_behavior.dm @@ -1,27 +1,7 @@ -/datum/ai_behavior/find_hunt_target/find_hive - -/datum/ai_behavior/find_hunt_target/find_hive/valid_dinner(mob/living/source, obj/structure/beebox/hive, radius) - if(!length(hive.honeycombs)) - return FALSE - return can_see(source, hive, radius) - -/datum/ai_behavior/hunt_target/find_hive +/// Raids a beehive once in range, extracting its honeycombs. +/datum/bt_node/ai_behavior/hunt_target/find_hive always_reset_target = TRUE -/datum/ai_behavior/hunt_target/find_hive/target_caught(mob/living/hunter, obj/structure/beebox/hive_target) +/datum/bt_node/ai_behavior/hunt_target/find_hive/target_caught(mob/living/hunter, obj/structure/beebox/hive_target) var/datum/callback/callback = CALLBACK(hunter, TYPE_PROC_REF(/mob/living/basic/bear, extract_combs), hive_target) callback.Invoke() - -/datum/ai_behavior/find_hunt_target/find_honeycomb - -/datum/ai_behavior/find_hunt_target/find_honeycomb/setup(datum/ai_controller/controller, ability_key, target_key) - var/mob/living/living_pawn = controller.pawn - if(living_pawn.pulling) //we already pulling a honey - return FALSE - return TRUE - -/datum/ai_behavior/hunt_target/find_honeycomb - always_reset_target = TRUE - -/datum/ai_behavior/hunt_target/find_honeycomb/target_caught(mob/living/hunter, obj/item/food/honeycomb/food_target) - hunter.start_pulling(food_target) diff --git a/code/modules/mob/living/basic/space_fauna/bear/bear_ai_subtree.dm b/code/modules/mob/living/basic/space_fauna/bear/bear_ai_subtree.dm index 1f45576b147..f6f900452dd 100644 --- a/code/modules/mob/living/basic/space_fauna/bear/bear_ai_subtree.dm +++ b/code/modules/mob/living/basic/space_fauna/bear/bear_ai_subtree.dm @@ -4,28 +4,4 @@ ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/target_retaliate, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/climb_trees, - /datum/ai_planning_subtree/find_and_hunt_target/find_hive, - /datum/ai_planning_subtree/find_and_hunt_target/find_honeycomb, - /datum/ai_planning_subtree/random_speech/bear, - ) - -/datum/ai_planning_subtree/find_and_hunt_target/find_hive - target_key = BB_FOUND_HONEY - hunting_behavior = /datum/ai_behavior/hunt_target/find_hive - finding_behavior = /datum/ai_behavior/find_hunt_target/find_hive - hunt_targets = list(/obj/structure/beebox) - hunt_range = 10 - -/datum/ai_planning_subtree/find_and_hunt_target/find_honeycomb - target_key = BB_LOW_PRIORITY_HUNTING_TARGET - hunting_behavior = /datum/ai_behavior/hunt_target/find_honeycomb - finding_behavior = /datum/ai_behavior/find_hunt_target/find_honeycomb - hunt_targets = list(/obj/item/food/honeycomb) - hunt_range = 10 + behavior_tree_json = "code/modules/mob/living/basic/space_fauna/bear/bear.bt.json" diff --git a/code/modules/mob/living/basic/space_fauna/carp/basic_carp_tree.bt.json b/code/modules/mob/living/basic/space_fauna/carp/basic_carp_tree.bt.json new file mode 100644 index 00000000000..d3b7e8ecd67 --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/carp/basic_carp_tree.bt.json @@ -0,0 +1,109 @@ +{ + "dm_type": "/datum/bt_node/subtree/basic_carp_tree", + "bindings": { + "be5c4p10": { + "label": "escape_tree", + "default": "/datum/bt_node/subtree" + }, + "bf1nd7r2": { + "label": "find_targets_tree", + "default": "/datum/bt_node/subtree/carp_target_selection" + }, + "bc0mb4t9": { + "label": "combat_tree", + "default": "/datum/bt_node/subtree/carp_combat" + }, + "bid1e6k3": { + "label": "idle_tree", + "default": "/datum/bt_node/subtree/carp_migration" + }, + "b7cm2s3z": { + "label": "flee_from_key", + "default": "BB_CURRENT_TARGET" + } + }, + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "$be5c4p10" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree", + "override_id": "SUBPLAN_ID_PET_COMMAND" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/carp_flee", + "bindings": { + "bf3k9q2p": "$b7cm2s3z" + } + }, + { + "type": "subtree", + "subtype": "$bc0mb4t9" + } + ] + } + }, + { + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "$bid1e6k3" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + } + ] + }, + { + "type": "subtree", + "subtype": "$bf1nd7r2" + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_MAGICARP_SPELL" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/find_magicarp_spell_target", + "vars": { + "ability_key": "BB_MAGICARP_SPELL", + "target_key": "BB_MAGICARP_SPELL_TARGET", + "targeting_strategy_key": "BB_TARGETING_STRATEGY" + } + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/space_fauna/carp/carp.bt.json b/code/modules/mob/living/basic/space_fauna/carp/carp.bt.json new file mode 100644 index 00000000000..33c298f4549 --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/carp/carp.bt.json @@ -0,0 +1,8 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/carp", + "type": "subtree", + "subtype": "/datum/bt_node/subtree/basic_carp_tree", + "bindings": { + "be5c4p10": "/datum/bt_node/subtree/escape_captivity" + } +} diff --git a/code/modules/mob/living/basic/space_fauna/carp/carp.dm b/code/modules/mob/living/basic/space_fauna/carp/carp.dm index f04fa3aea99..be19c0bd3e9 100644 --- a/code/modules/mob/living/basic/space_fauna/carp/carp.dm +++ b/code/modules/mob/living/basic/space_fauna/carp/carp.dm @@ -156,8 +156,8 @@ /// Gives the carp a list of weakrefs of destinations to try and travel between when it has nothing better to do /mob/living/basic/carp/proc/migrate_to(list/datum/weakref/migration_points) - ai_controller.can_idle = FALSE - ai_controller.set_ai_status(AI_STATUS_ON) // We need htem to actually walk to the station + ai_controller.ai_traits |= RUN_WHILE_UNWATCHED + ai_controller.reset_ai_status() // We need them to actually keep walking to the station var/list/actual_points = list() for(var/datum/weakref/point_ref as anything in migration_points) var/turf/point_resolved = point_ref.resolve() diff --git a/code/modules/mob/living/basic/space_fauna/carp/carp_abilities.dm b/code/modules/mob/living/basic/space_fauna/carp/carp_abilities.dm index b2fcbf0f78d..22f06da636b 100644 --- a/code/modules/mob/living/basic/space_fauna/carp/carp_abilities.dm +++ b/code/modules/mob/living/basic/space_fauna/carp/carp_abilities.dm @@ -137,7 +137,7 @@ if (isliving(entered_atom)) var/mob/living/teleported_mob = entered_atom teleported_mob.changeNext_move(disorient_time) - teleported_mob.ai_controller?.CancelActions() + teleported_mob.ai_controller?.cancel_current_plan() var/turf/destination = pick(exit_locs) do_teleport(entered_atom, destination, channel = TELEPORT_CHANNEL_MAGIC) diff --git a/code/modules/mob/living/basic/space_fauna/carp/carp_ai_actions.dm b/code/modules/mob/living/basic/space_fauna/carp/carp_ai_actions.dm index 26894850b19..d8beb9dedde 100644 --- a/code/modules/mob/living/basic/space_fauna/carp/carp_ai_actions.dm +++ b/code/modules/mob/living/basic/space_fauna/carp/carp_ai_actions.dm @@ -1,70 +1,144 @@ +/// How far away the magicarp looks for a special spell target #define MAGICARP_SPELL_TARGET_SEEK_RANGE 4 +/// How far away the magicarp looks for a regular spell target +#define MAGICARP_SPELL_ENEMY_SEEK_RANGE 9 /datum/pet_command/use_ability/magicarp pet_ability_key = BB_MAGICARP_SPELL -/datum/ai_planning_subtree/attack_obstacle_in_path/carp - attack_behaviour = /datum/ai_behavior/attack_obstructions/carp +/** + * # Carp should flee + * Gates the flee/panic-teleport block. A carp flees from its flee target if that target is a feared + * fisherman, or if it is otherwise allowed to flee (i.e. it's injured, which clears BB_BASIC_MOB_STOP_FLEEING). + */ +/datum/bt_node/decorator/carp_should_flee + /// Blackboard key holding the thing we'd run away from + var/target_key = BB_BASIC_MOB_FLEE_TARGET -/datum/ai_behavior/attack_obstructions/carp - action_cooldown = 1.5 SECONDS +/datum/bt_node/decorator/carp_should_flee/check_condition(datum/ai_controller/controller) + var/atom/flee_from = controller.blackboard[target_key] + if(QDELETED(flee_from)) + return FALSE + if(controller.blackboard[BB_CARPS_FEAR_FISHERMAN] && HAS_TRAIT(flee_from, TRAIT_SCARY_FISHERMAN)) + return TRUE + return !controller.blackboard[BB_BASIC_MOB_STOP_FLEEING] -/// As basic attack tree but interrupt if your health gets low or if your spell is off cooldown -/datum/ai_planning_subtree/basic_melee_attack_subtree/magicarp - melee_attack_behavior = /datum/ai_behavior/basic_melee_attack/magicarp +/datum/bt_node/decorator/carp_should_flee/register_observe_signals(atom/pawn) + RegisterSignals(pawn, list( + COMSIG_AI_BLACKBOARD_KEY_SET(target_key), + COMSIG_AI_BLACKBOARD_KEY_CLEARED(target_key), + COMSIG_AI_BLACKBOARD_KEY_SET(BB_BASIC_MOB_STOP_FLEEING), + COMSIG_AI_BLACKBOARD_KEY_CLEARED(BB_BASIC_MOB_STOP_FLEEING), + ), PROC_REF(on_signal_changed)) + return TRUE -/// Interrupt your attack chain if: you have a spell, it's not on cooldown, and it has a target -/datum/ai_behavior/basic_melee_attack/magicarp - -/datum/ai_behavior/basic_melee_attack/magicarp/perform(seconds_per_tick, datum/ai_controller/controller, target_key, targeting_strategy_key, hiding_location_key, health_ratio_key) - var/datum/action/cooldown/using_action = controller.blackboard[BB_MAGICARP_SPELL] - if (QDELETED(using_action)) - return ..() - if (!controller.blackboard[BB_MAGICARP_SPELL_SPECIAL_TARGETING] && using_action.IsAvailable()) - return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED - return ..() +/datum/bt_node/decorator/carp_should_flee/unregister_observe_signals(atom/pawn) + UnregisterSignal(pawn, list( + COMSIG_AI_BLACKBOARD_KEY_SET(target_key), + COMSIG_AI_BLACKBOARD_KEY_CLEARED(target_key), + COMSIG_AI_BLACKBOARD_KEY_SET(BB_BASIC_MOB_STOP_FLEEING), + COMSIG_AI_BLACKBOARD_KEY_CLEARED(BB_BASIC_MOB_STOP_FLEEING), + )) /** - * Find a target for the magicarp's spell - * This gets weird because different spells want different targeting - * but I didn't want a new ai controller for every different spell + * # Find magicarp spell target + * Finds a target for the magicarp's spell. Different spells want different targeting, so rather than make a + * controller per spell we branch on BB_MAGICARP_SPELL_SPECIAL_TARGETING here. Only runs if the spell is ready. */ -/datum/ai_planning_subtree/find_nearest_magicarp_spell_target +/datum/bt_node/ai_behavior/find_magicarp_spell_target + /// Blackboard key holding the spell we're trying to target + var/ability_key = BB_MAGICARP_SPELL + /// Blackboard key we store the chosen spell target in + var/target_key = BB_MAGICARP_SPELL_TARGET + /// Blackboard key holding our targeting strategy for the default case + var/targeting_strategy_key = BB_TARGETING_STRATEGY + /// Blackboard key describing any special targeting this spell wants + var/special_targeting_key = BB_MAGICARP_SPELL_SPECIAL_TARGETING -/datum/ai_planning_subtree/find_nearest_magicarp_spell_target/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/datum/action/cooldown/using_action = controller.blackboard[BB_MAGICARP_SPELL] - if (!using_action?.IsAvailable()) - return +/datum/bt_node/ai_behavior/find_magicarp_spell_target/perform(seconds_per_tick, datum/ai_controller/controller) + var/datum/action/cooldown/using_action = controller.blackboard[ability_key] + if(!using_action?.IsAvailable()) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED - var/spell_targeting = controller.blackboard[BB_MAGICARP_SPELL_SPECIAL_TARGETING] - if (!spell_targeting) - controller.queue_behavior(/datum/ai_behavior/find_potential_targets/nearest/magicarp, BB_MAGICARP_SPELL_TARGET, BB_TARGETING_STRATEGY, BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION) - return + var/atom/found + switch(controller.blackboard[special_targeting_key]) + if(MAGICARP_SPELL_CORPSES) + found = find_friendly_corpse(controller) + if(MAGICARP_SPELL_OBJECTS) + found = find_animatable(controller) + if(MAGICARP_SPELL_WALLS) + found = find_nearest_wall(controller) + else + found = find_nearest_enemy(controller) - switch(spell_targeting) - if (MAGICARP_SPELL_CORPSES) - controller.queue_behavior(/datum/ai_behavior/find_and_set/friendly_corpses, BB_MAGICARP_SPELL_TARGET, MAGICARP_SPELL_TARGET_SEEK_RANGE) - return - if (MAGICARP_SPELL_OBJECTS) - controller.queue_behavior(/datum/ai_behavior/find_and_set/animatable, BB_MAGICARP_SPELL_TARGET, MAGICARP_SPELL_TARGET_SEEK_RANGE) - return - if (MAGICARP_SPELL_WALLS) - controller.queue_behavior(/datum/ai_behavior/find_and_set/nearest_wall, BB_MAGICARP_SPELL_TARGET, MAGICARP_SPELL_TARGET_SEEK_RANGE) - return + if(isnull(found)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED -/// This subtype only exists because if you queue multiple of the same action with different arguments it deletes their stored arguments -/datum/ai_behavior/find_potential_targets/nearest/magicarp + controller.set_blackboard_key(target_key, found) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED -/datum/ai_behavior/find_potential_targets/nearest/magicarp/pick_final_target(datum/ai_controller/controller, list/enemies_list) - for(var/atom/atom as anything in enemies_list) - if(HAS_TRAIT(atom, TRAIT_SCARY_FISHERMAN)) - enemies_list -= atom - return ..() +/// Nearest valid combat target which isn't a scary fisherman (default spell targeting) +/datum/bt_node/ai_behavior/find_magicarp_spell_target/proc/find_nearest_enemy(datum/ai_controller/controller) + var/mob/living/living_pawn = controller.pawn + var/datum/targeting_strategy/strategy = GET_TARGETING_STRATEGY(controller.blackboard[targeting_strategy_key]) + if(!strategy) + return null + var/list/candidates = list() + for(var/mob/living/candidate in oview(MAGICARP_SPELL_ENEMY_SEEK_RANGE, living_pawn)) + if(HAS_TRAIT(candidate, TRAIT_SCARY_FISHERMAN)) + continue + if(!strategy.is_valid_target(living_pawn, candidate, MAGICARP_SPELL_ENEMY_SEEK_RANGE, controller)) + continue + candidates += candidate + if(!length(candidates)) + return null + return get_closest_atom(/mob/living, candidates, living_pawn) -/// Then use it on that target -/datum/ai_planning_subtree/targeted_mob_ability/magicarp - ability_key = BB_MAGICARP_SPELL - target_key = BB_MAGICARP_SPELL_TARGET - use_ability_behaviour = /datum/ai_behavior/targeted_mob_ability/and_clear_target +/// An object or structure we could animate with a staff of change +/datum/bt_node/ai_behavior/find_magicarp_spell_target/proc/find_animatable(datum/ai_controller/controller) + var/mob/living/living_pawn = controller.pawn + var/list/nearby_items = list() + for(var/obj/new_friend in oview(MAGICARP_SPELL_TARGET_SEEK_RANGE, living_pawn)) + if(!isitem(new_friend) && !isstructure(new_friend)) + continue + if(is_type_in_list(new_friend, GLOB.animatable_blacklist)) + continue + if(living_pawn.see_invisible < new_friend.invisibility) + continue + nearby_items += new_friend + if(length(nearby_items)) + return pick(nearby_items) + return null + +/// The nearest wall which isn't invulnerable +/datum/bt_node/ai_behavior/find_magicarp_spell_target/proc/find_nearest_wall(datum/ai_controller/controller) + var/mob/living/living_pawn = controller.pawn + var/list/nearby_walls = list() + for(var/turf/closed/new_wall in oview(MAGICARP_SPELL_TARGET_SEEK_RANGE, living_pawn)) + if(isindestructiblewall(new_wall)) + continue + nearby_walls += new_wall + if(length(nearby_walls)) + return get_closest_atom(/turf/closed, nearby_walls, living_pawn) + return null + +/// A corpse who shares our faction, for resurrection spells +/datum/bt_node/ai_behavior/find_magicarp_spell_target/proc/find_friendly_corpse(datum/ai_controller/controller) + var/mob/living/living_pawn = controller.pawn + var/list/nearby_bodies = list() + for(var/mob/living/dead_pal in oview(MAGICARP_SPELL_TARGET_SEEK_RANGE, living_pawn)) + if(!isturf(dead_pal.loc)) + continue + if(!dead_pal.stat || dead_pal.health > 0) + continue + if(living_pawn.see_invisible < dead_pal.invisibility) + continue + if(!living_pawn.faction_check_atom(dead_pal)) + continue + nearby_bodies += dead_pal + if(length(nearby_bodies)) + return pick(nearby_bodies) + return null #undef MAGICARP_SPELL_TARGET_SEEK_RANGE +#undef MAGICARP_SPELL_ENEMY_SEEK_RANGE diff --git a/code/modules/mob/living/basic/space_fauna/carp/carp_ai_migration.dm b/code/modules/mob/living/basic/space_fauna/carp/carp_ai_migration.dm index 9458877af7f..b65456f16d7 100644 --- a/code/modules/mob/living/basic/space_fauna/carp/carp_ai_migration.dm +++ b/code/modules/mob/living/basic/space_fauna/carp/carp_ai_migration.dm @@ -1,46 +1,17 @@ /// How close you need to get to the destination in order to consider yourself there #define CARP_DESTINATION_SEARCH_RANGE 3 -/// If there's a portal this close to us we'll enter it just on the basis that the carp who made it probably knew where they were going -#define CARP_PORTAL_SEARCH_RANGE 2 - -/** - * # Carp Migration - * Will try to plan a path between a list of locations for carp to travel through - */ -/datum/ai_planning_subtree/carp_migration - -/datum/ai_planning_subtree/carp_migration/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - // If there's a rift nearby take a ride, then cancel everything else because it's not valid any more - for(var/obj/effect/temp_visual/lesser_carp_rift/entrance/rift in orange(controller.pawn, CARP_PORTAL_SEARCH_RANGE)) - controller.queue_behavior(/datum/ai_behavior/travel_towards_atom, get_turf(rift)) - return SUBTREE_RETURN_FINISH_PLANNING - - // We have a destination, try to approach it - var/turf/moving_to = controller.blackboard[BB_CARP_MIGRATION_TARGET] - if(!isnull(moving_to)) - var/turf/next_step = get_step_towards(controller.pawn, moving_to) - // Attempt to teleport around if we're blocked - if(next_step.is_blocked_turf(exclude_mobs = TRUE)) - controller.queue_behavior(/datum/ai_behavior/make_carp_rift/towards/unvalidated, BB_CARP_RIFT, BB_CARP_MIGRATION_TARGET) - controller.queue_behavior(/datum/ai_behavior/attack_obstructions/carp, BB_CARP_MIGRATION_TARGET) - controller.queue_behavior(/datum/ai_behavior/step_towards_turf, BB_CARP_MIGRATION_TARGET) - // We've gotten close enough to it, clear it so we can select a new point (or do nothing) - if(get_dist(controller.pawn, moving_to) <= CARP_DESTINATION_SEARCH_RANGE) - controller.clear_blackboard_key(BB_CARP_MIGRATION_TARGET) - return SUBTREE_RETURN_FINISH_PLANNING - - // We have a path to follow but no destination, select one - if(length(controller.blackboard[BB_CARP_MIGRATION_PATH])) - controller.queue_behavior(/datum/ai_behavior/find_next_carp_migration_step, BB_CARP_MIGRATION_PATH, BB_CARP_MIGRATION_TARGET) - return SUBTREE_RETURN_FINISH_PLANNING /** * # Find next carp migration step * Records the next turf we want to travel to into the blackboard for other actions */ -/datum/ai_behavior/find_next_carp_migration_step +/datum/bt_node/ai_behavior/find_next_carp_migration_step + /// Blackboard key holding the list of turfs to migrate between + var/path_key = BB_CARP_MIGRATION_PATH + /// Blackboard key in which we record our next destination + var/target_key = BB_CARP_MIGRATION_TARGET -/datum/ai_behavior/find_next_carp_migration_step/perform(seconds_per_tick, datum/ai_controller/controller, path_key, target_key) +/datum/bt_node/ai_behavior/find_next_carp_migration_step/perform(seconds_per_tick, datum/ai_controller/controller) var/list/blackboard_points = controller.blackboard[path_key] for(var/turf/migration_point as anything in blackboard_points) // By the end of this loop we will either have a valid migration point set, or an empty list in our blackboard @@ -50,5 +21,30 @@ return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED +/** + * # Clear arrived migration target + * Clears our migration target once we've gotten close enough to it, so a new one can be selected. + */ +/datum/bt_node/ai_behavior/clear_arrived_migration_target + /// Blackboard key holding our migration destination + var/target_key = BB_CARP_MIGRATION_TARGET + +/datum/bt_node/ai_behavior/clear_arrived_migration_target/perform(seconds_per_tick, datum/ai_controller/controller) + var/turf/moving_to = controller.blackboard[target_key] + if(QDELETED(moving_to) || get_dist(controller.pawn, moving_to) <= CARP_DESTINATION_SEARCH_RANGE) + controller.clear_blackboard_key(target_key) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED + +/// Returns TRUE if the next step towards the keyed turf is blocked, so we can smash or teleport through it. +/datum/bt_node/decorator/carp_path_blocked + /// Blackboard key holding the turf we're trying to reach + var/target_key = BB_CARP_MIGRATION_TARGET + +/datum/bt_node/decorator/carp_path_blocked/check_condition(datum/ai_controller/controller) + var/atom/target = controller.blackboard[target_key] + if(QDELETED(target)) + return FALSE + var/turf/next_step = get_step_towards(controller.pawn, target) + return next_step?.is_blocked_turf(exclude_mobs = TRUE) + #undef CARP_DESTINATION_SEARCH_RANGE -#undef CARP_PORTAL_SEARCH_RANGE diff --git a/code/modules/mob/living/basic/space_fauna/carp/carp_ai_rift_actions.dm b/code/modules/mob/living/basic/space_fauna/carp/carp_ai_rift_actions.dm index 69890c7bb2f..13f76f972aa 100644 --- a/code/modules/mob/living/basic/space_fauna/carp/carp_ai_rift_actions.dm +++ b/code/modules/mob/living/basic/space_fauna/carp/carp_ai_rift_actions.dm @@ -1,112 +1,77 @@ /** * # Make carp rift - * Plan a carp rift action, so basically teleport somewhere if the action is available + * Use the carp rift ability to teleport somewhere relative to a target. */ -/datum/ai_planning_subtree/make_carp_rift - /// Chiefly describes where we are placing this teleport - var/datum/ai_behavior/rift_behaviour - /// If true we finish planning after this - var/finish_planning = FALSE - /// Key to read for flee target - var/target_key = BB_BASIC_MOB_CURRENT_TARGET +/datum/bt_node/ai_behavior/make_carp_rift + /// Blackboard key holding the rift ability + var/ability_key = BB_CARP_RIFT + /// Blackboard key holding the atom we're teleporting relative to + var/target_key + /// Rift destination snapshotted in perform(), for perform_async() to read. + VAR_PRIVATE/turf/rift_destination -/datum/ai_planning_subtree/make_carp_rift/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if (!rift_behaviour) - CRASH("Forgot to specify rift behaviour for [src]") +/datum/bt_node/ai_behavior/make_carp_rift/setup(datum/ai_controller/controller) + . = ..() + if(!controller.blackboard[ability_key] || !controller.blackboard[target_key]) + return FALSE - if (!controller.blackboard_key_exists(target_key)) - return - var/datum/action/cooldown/using_action = controller.blackboard[BB_CARP_RIFT] - if (!using_action?.IsAvailable()) - return +/datum/bt_node/ai_behavior/make_carp_rift/perform(seconds_per_tick, datum/ai_controller/controller) + var/async_flags = handle_async() + if(async_flags) + return async_flags - controller.queue_behavior(rift_behaviour, BB_CARP_RIFT, target_key) - if (finish_planning) - return SUBTREE_RETURN_FINISH_PLANNING - -/** - * # Make carp rift (panic) - * Plan to teleport away from our target so they can't fuck us up - */ -/datum/ai_planning_subtree/make_carp_rift/panic_teleport - rift_behaviour = /datum/ai_behavior/make_carp_rift/away - finish_planning = TRUE - -/datum/ai_planning_subtree/make_carp_rift/panic_teleport/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/atom/movable/fleeing_from = controller.blackboard[target_key] - if(!QDELETED(fleeing_from) && controller.blackboard[BB_CARPS_FEAR_FISHERMAN] && HAS_TRAIT(fleeing_from, TRAIT_SCARY_FISHERMAN)) - return ..() - if (controller.blackboard[BB_BASIC_MOB_STOP_FLEEING]) - return - return ..() - -/datum/ai_planning_subtree/make_carp_rift/panic_teleport/flee_key - target_key = BB_BASIC_MOB_FLEE_TARGET - - -/** - * # Make carp rift (aggressive) - * Plan to teleport towards our target so we can fuck them up - */ -/datum/ai_planning_subtree/make_carp_rift/aggressive_teleport - rift_behaviour = /datum/ai_behavior/make_carp_rift/towards/aggressive - -/datum/ai_planning_subtree/make_carp_rift/aggressive_teleport/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/atom/movable/target = controller.blackboard[target_key] - if(!QDELETED(target) && controller.blackboard[BB_CARPS_FEAR_FISHERMAN] && HAS_TRAIT(target, TRAIT_SCARY_FISHERMAN)) - return - return ..() - -/** - * # Make carp rift - * Make a carp rift somewhere - */ -/datum/ai_behavior/make_carp_rift - -/datum/ai_behavior/make_carp_rift/setup(datum/ai_controller/controller, ability_key, target_key) - return controller.blackboard[ability_key] && controller.blackboard[target_key] - -/datum/ai_behavior/make_carp_rift/perform(seconds_per_tick, datum/ai_controller/controller, ability_key, target_key) var/datum/action/cooldown/mob_cooldown/lesser_carp_rift/ability = controller.blackboard[ability_key] var/atom/target = controller.blackboard[target_key] - if (!validate_target(controller, target, ability)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + // Fail INSTANT (not DELAY) so a selector falls through to melee/obstacles when we can't teleport, + // rather than latching on us while our cooldown ticks down. + if(!validate_target(controller, target, ability)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED var/turf/target_destination = find_target_turf(controller, target, ability) - if (!target_destination) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + if(!target_destination) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED - if(ability.InterceptClickOn(controller.pawn, null, target_destination)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + rift_destination = target_destination + return start_async() + +/datum/bt_node/ai_behavior/make_carp_rift/perform_async(datum/ai_controller/controller) + var/datum/action/cooldown/mob_cooldown/lesser_carp_rift/ability = controller.blackboard[ability_key] + var/result = ability.InterceptClickOn(controller.pawn, null, rift_destination) + if(!async_still_valid()) + return + finish_async(result ? AI_BEHAVIOR_SUCCEEDED : AI_BEHAVIOR_FAILED) + +/datum/bt_node/ai_behavior/make_carp_rift/finish_action(datum/ai_controller/controller, succeeded) + . = ..() + rift_destination = null /// Return true if your target is valid for the action -/datum/ai_behavior/make_carp_rift/proc/validate_target(datum/ai_controller/controller, atom/target, datum/action/cooldown/mob_cooldown/lesser_carp_rift/ability) - if (!ability) +/datum/bt_node/ai_behavior/make_carp_rift/proc/validate_target(datum/ai_controller/controller, atom/target, datum/action/cooldown/mob_cooldown/lesser_carp_rift/ability) + if(!ability) return FALSE - if (!target) + if(!target) return FALSE return TRUE /// Return the turf to teleport to, implement this or the behaviour won't do anything -/datum/ai_behavior/make_carp_rift/proc/find_target_turf(datum/ai_controller/controller, atom/target, datum/action/cooldown/mob_cooldown/lesser_carp_rift/ability) +/datum/bt_node/ai_behavior/make_carp_rift/proc/find_target_turf(datum/ai_controller/controller, atom/target, datum/action/cooldown/mob_cooldown/lesser_carp_rift/ability) CRASH("Called unimplemented target finding proc on carp rift behaviour") /** * # Make carp rift away * Make a rift bringing you further away from your target */ -/datum/ai_behavior/make_carp_rift/away +/datum/bt_node/ai_behavior/make_carp_rift/away -/datum/ai_behavior/make_carp_rift/away/find_target_turf(datum/ai_controller/controller, atom/target, datum/action/cooldown/mob_cooldown/lesser_carp_rift/ability) +/datum/bt_node/ai_behavior/make_carp_rift/away/find_target_turf(datum/ai_controller/controller, atom/target, datum/action/cooldown/mob_cooldown/lesser_carp_rift/ability) return get_ranged_target_turf_direct(controller.pawn, target, range = ability.max_range, offset = 180) /** * # Make carp rift forwards * Make a rift bringing you closer to your target */ -/datum/ai_behavior/make_carp_rift/towards +/datum/bt_node/ai_behavior/make_carp_rift/towards /// Drop rift at least this many tiles away from target var/teleport_buffer_distance = 0 /// Teleport simply if you are far away @@ -114,44 +79,44 @@ /// Teleport if the turf in front of you is blocked var/teleport_if_blocked = TRUE -/datum/ai_behavior/make_carp_rift/towards/validate_target(datum/ai_controller/controller, atom/target, datum/action/cooldown/mob_cooldown/lesser_carp_rift/ability) +/datum/bt_node/ai_behavior/make_carp_rift/towards/validate_target(datum/ai_controller/controller, atom/target, datum/action/cooldown/mob_cooldown/lesser_carp_rift/ability) . = ..() - if (!.) + if(!.) return FALSE - if (teleport_if_far) + if(teleport_if_far) var/distance = get_dist(get_turf(controller.pawn), get_turf(target)) - if (distance >= ability.max_range + teleport_buffer_distance) // Perform if we are far away + if(distance >= ability.max_range + teleport_buffer_distance) // Perform if we are far away return TRUE - if (teleport_if_blocked) + if(teleport_if_blocked) var/turf/next_move = get_step_towards(controller.pawn, target) - if (next_move.is_blocked_turf(exclude_mobs = TRUE)) // Perform if target is behind cover + if(next_move.is_blocked_turf(exclude_mobs = TRUE)) // Perform if target is behind cover return TRUE return FALSE -/datum/ai_behavior/make_carp_rift/towards/find_target_turf(datum/ai_controller/controller, atom/target, datum/action/cooldown/mob_cooldown/lesser_carp_rift/ability) +/datum/bt_node/ai_behavior/make_carp_rift/towards/find_target_turf(datum/ai_controller/controller, atom/target, datum/action/cooldown/mob_cooldown/lesser_carp_rift/ability) var/turf/target_turf = get_turf(target) var/distance = get_dist(get_turf(controller.pawn), target_turf) var/turf/chosen_turf - if (distance <= ability.max_range) + if(distance <= ability.max_range) chosen_turf = target_turf else var/run_direction = get_dir(controller.pawn, get_step_towards(controller.pawn, target_turf)) - chosen_turf = get_ranged_target_turf(controller.pawn, run_direction, ability.max_range) + chosen_turf = get_ranged_target_turf(controller.pawn, run_direction, ability.max_range) - if (!chosen_turf) + if(!chosen_turf) return // Subtract some distance so we don't drop carp directly on top of someone var/rift_to_target_distance = get_dist(target_turf, chosen_turf) - if (rift_to_target_distance < teleport_buffer_distance) + if(rift_to_target_distance < teleport_buffer_distance) var/away_direction = get_dir(controller.pawn, get_step_away(controller.pawn, target_turf)) - var/turf/backed_away_turf = get_ranged_target_turf(controller.pawn, away_direction, teleport_buffer_distance - rift_to_target_distance) - if (distance > get_dist(backed_away_turf, target_turf)) + var/turf/backed_away_turf = get_ranged_target_turf(controller.pawn, away_direction, teleport_buffer_distance - rift_to_target_distance) + if(distance > get_dist(backed_away_turf, target_turf)) chosen_turf = backed_away_turf // Avoid edge case pointless teleports from being up against a wall return chosen_turf @@ -160,48 +125,68 @@ * # Make carp rift forwards (aggressive) * Make a rift towards your target if you are blocked from moving or if it is far away */ -/datum/ai_behavior/make_carp_rift/towards/aggressive +/datum/bt_node/ai_behavior/make_carp_rift/towards/aggressive teleport_buffer_distance = 1 // Don't aggressively drop carps directly on top of a target mob /** * # Make carp rift forwards (unvalidated) - * Skip validation checks because we already did them in the controller + * Skip validation checks because we already did them elsewhere */ -/datum/ai_behavior/make_carp_rift/towards/unvalidated +/datum/bt_node/ai_behavior/make_carp_rift/towards/unvalidated -/datum/ai_behavior/make_carp_rift/towards/unvalidated/validate_target(datum/ai_controller/controller, atom/target, datum/action/cooldown/mob_cooldown/lesser_carp_rift/ability) +/datum/bt_node/ai_behavior/make_carp_rift/towards/unvalidated/validate_target(datum/ai_controller/controller, atom/target, datum/action/cooldown/mob_cooldown/lesser_carp_rift/ability) return TRUE -/datum/ai_behavior/make_carp_rift/towards/unvalidated/finish_action(datum/ai_controller/controller, succeeded, ...) +/datum/bt_node/ai_behavior/make_carp_rift/towards/unvalidated/finish_action(datum/ai_controller/controller, succeeded) . = ..() - if (succeeded) - controller.CancelActions() + if(succeeded) + controller.cancel_current_plan() /** - * # Shortcut to target through carp rift - * If there's a carp rift heading your way, plan to ride it to your target + * # Find carp rift shortcut + * If there's a carp rift heading towards our target, record its turf so we can ride it there. */ -/datum/ai_planning_subtree/shortcut_to_target_through_carp_rift +/datum/bt_node/ai_behavior/find_carp_rift_shortcut + /// Blackboard key holding our current target + var/target_key = BB_CURRENT_TARGET + /// Blackboard key in which we store the rift turf to travel to + var/destination_key = BB_CARP_RIFT_DESTINATION /// How far away do we look for rifts? var/search_distance = 3 - /// Minimum distance we should be from the target before we bother performing this action + /// Minimum distance we should be from the target before we bother var/minimum_distance = 2 -/datum/ai_planning_subtree/shortcut_to_target_through_carp_rift/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/mob/living/target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] - if (QDELETED(target) || (controller.blackboard[BB_CARPS_FEAR_FISHERMAN] && HAS_TRAIT(target, TRAIT_SCARY_FISHERMAN))) - return +/datum/bt_node/ai_behavior/find_carp_rift_shortcut/perform(seconds_per_tick, datum/ai_controller/controller) + var/mob/living/target = controller.blackboard[target_key] + if(QDELETED(target) || (controller.blackboard[BB_CARPS_FEAR_FISHERMAN] && HAS_TRAIT(target, TRAIT_SCARY_FISHERMAN))) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED var/distance_to_target = get_dist(controller.pawn, target) - if (distance_to_target <= minimum_distance) - return + if(distance_to_target <= minimum_distance) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED - for (var/obj/effect/temp_visual/lesser_carp_rift/entrance/rift in orange(controller.pawn, search_distance)) + for(var/obj/effect/temp_visual/lesser_carp_rift/entrance/rift in orange(controller.pawn, search_distance)) var/exit_count = length(rift.exit_locs) - if (!exit_count) + if(!exit_count) continue var/turf/rift_exit = rift.exit_locs[exit_count] - if ((get_dist(rift_exit, target) + get_dist(rift, target)) >= distance_to_target) + if((get_dist(rift_exit, target) + get_dist(rift, target)) >= distance_to_target) continue - controller.queue_behavior(/datum/ai_behavior/travel_towards_atom, rift) - return SUBTREE_RETURN_FINISH_PLANNING + controller.set_blackboard_key(destination_key, get_turf(rift)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED + + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED + +/** + * # Find nearby carp rift + * Record the turf of any nearby rift entrance so we can ride it; the carp who made it probably knew where they were going. + */ +/datum/bt_node/ai_behavior/find_carp_rift_shortcut/nearby + search_distance = 2 + minimum_distance = 0 + +/datum/bt_node/ai_behavior/find_carp_rift_shortcut/nearby/perform(seconds_per_tick, datum/ai_controller/controller) + for(var/obj/effect/temp_visual/lesser_carp_rift/entrance/rift in orange(controller.pawn, search_distance)) + controller.set_blackboard_key(destination_key, get_turf(rift)) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_SUCCEEDED + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED diff --git a/code/modules/mob/living/basic/space_fauna/carp/carp_combat.bt.json b/code/modules/mob/living/basic/space_fauna/carp/carp_combat.bt.json new file mode 100644 index 00000000000..08a60964214 --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/carp/carp_combat.bt.json @@ -0,0 +1,85 @@ +{ + "dm_type": "/datum/bt_node/subtree/carp_combat", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_MAGICARP_SPELL_TARGET" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/ability_available", + "vars": { + "ability_key": "BB_MAGICARP_SPELL" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability/and_clear_target", + "vars": { + "ability_key": "BB_MAGICARP_SPELL", + "target_key": "BB_MAGICARP_SPELL_TARGET" + } + } + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/attack_obstructions", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "time_between_perform": "1.5 SECONDS" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/ability_available", + "vars": { + "ability_key": "BB_CARP_RIFT" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/make_carp_rift/towards/aggressive", + "vars": { + "ability_key": "BB_CARP_RIFT", + "target_key": "BB_CURRENT_TARGET" + } + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] +} diff --git a/code/modules/mob/living/basic/space_fauna/carp/carp_controllers.dm b/code/modules/mob/living/basic/space_fauna/carp/carp_controllers.dm index 35a9e6d7f51..a99ca6e1e49 100644 --- a/code/modules/mob/living/basic/space_fauna/carp/carp_controllers.dm +++ b/code/modules/mob/living/basic/space_fauna/carp/carp_controllers.dm @@ -1,11 +1,9 @@ /** * AI controller for carp * Expected flow is: - * * If health is low, mark that we want to run away. - * * If we want to run away, find nearest target and run out of view of it. - * * Look for anything we want to eat in the area and target it. - * * If we don't have a target already, find something to attack. - * * Go and attack our target (which might be food, or might be a mob). + * * If we want to run away (injured, or a scary fisherman is near), flee or panic-teleport from our target. + * * Otherwise hunt for something to attack, prioritising scary fishermen, and go bite it. + * * When idle, migrate between destinations or wander. */ /datum/ai_controller/basic_controller/carp blackboard = list( @@ -17,21 +15,7 @@ ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/pet_planning, - /datum/ai_planning_subtree/simple_find_nearest_target_to_flee, - /datum/ai_planning_subtree/find_target_prioritize_traits, - /datum/ai_planning_subtree/make_carp_rift/panic_teleport, - /datum/ai_planning_subtree/flee_target/from_fisherman, - /datum/ai_planning_subtree/attack_obstacle_in_path/carp, - /datum/ai_planning_subtree/shortcut_to_target_through_carp_rift, - /datum/ai_planning_subtree/make_carp_rift/aggressive_teleport, - /datum/ai_planning_subtree/basic_melee_attack_subtree/no_fisherman, - /datum/ai_planning_subtree/find_food, - /datum/ai_planning_subtree/carp_migration, - ) + behavior_tree_json = "code/modules/mob/living/basic/space_fauna/carp/carp.bt.json" ///Megacarps. The only difference is that they don't flee from scary fishermen and prioritize them. /datum/ai_controller/basic_controller/carp/mega @@ -42,20 +26,7 @@ BB_TARGET_PRIORITY_TRAIT = TRAIT_SCARY_FISHERMAN, BB_CARPS_FEAR_FISHERMAN = FALSE, ) - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/pet_planning, - /datum/ai_planning_subtree/simple_find_nearest_target_to_flee, - /datum/ai_planning_subtree/make_carp_rift/panic_teleport, - /datum/ai_planning_subtree/flee_target, - /datum/ai_planning_subtree/find_target_prioritize_traits, - /datum/ai_planning_subtree/attack_obstacle_in_path/carp, - /datum/ai_planning_subtree/shortcut_to_target_through_carp_rift, - /datum/ai_planning_subtree/make_carp_rift/aggressive_teleport, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/find_food, - /datum/ai_planning_subtree/carp_migration, - ) + /** * Carp which bites back, but doesn't look for targets. * 'Not hunting targets' includes food (and can rings), because they have been well trained. @@ -69,39 +40,14 @@ BB_CARPS_FEAR_FISHERMAN = TRUE, ) ai_traits = PASSIVE_AI_FLAGS - planning_subtrees = list( - /datum/ai_planning_subtree/pet_planning, - /datum/ai_planning_subtree/find_nearest_thing_which_attacked_me_to_flee, - /datum/ai_planning_subtree/make_carp_rift/panic_teleport, - /datum/ai_planning_subtree/flee_target/from_fisherman, - /datum/ai_planning_subtree/target_retaliate, - /datum/ai_planning_subtree/attack_obstacle_in_path/carp, - /datum/ai_planning_subtree/shortcut_to_target_through_carp_rift, - /datum/ai_planning_subtree/make_carp_rift/aggressive_teleport, - /datum/ai_planning_subtree/basic_melee_attack_subtree/no_fisherman, - ) + behavior_tree_json = "code/modules/mob/living/basic/space_fauna/carp/carp_pet.bt.json" /** * AI for carp with a spell. * Flow is basically the same as regular carp, except it will try and cast a spell at its target whenever possible and not fleeing. */ /datum/ai_controller/basic_controller/carp/ranged - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/pet_planning, - /datum/ai_planning_subtree/simple_find_nearest_target_to_flee, - /datum/ai_planning_subtree/find_target_prioritize_traits, - /datum/ai_planning_subtree/make_carp_rift/panic_teleport, - /datum/ai_planning_subtree/flee_target/from_fisherman, - /datum/ai_planning_subtree/find_nearest_magicarp_spell_target, - /datum/ai_planning_subtree/targeted_mob_ability/magicarp, - /datum/ai_planning_subtree/attack_obstacle_in_path/carp, - /datum/ai_planning_subtree/shortcut_to_target_through_carp_rift, - /datum/ai_planning_subtree/make_carp_rift/aggressive_teleport, - /datum/ai_planning_subtree/basic_melee_attack_subtree/magicarp, - /datum/ai_planning_subtree/find_food, - /datum/ai_planning_subtree/carp_migration, - ) + behavior_tree_json = "code/modules/mob/living/basic/space_fauna/carp/carp_ranged.bt.json" /** * Carp which bites back, but doesn't look for targets and doesnt do as much damage @@ -116,14 +62,33 @@ BB_TARGET_ONLY_WITH_TRAITS = list(TRAIT_SCARY_FISHERMAN), ) ai_traits = PASSIVE_AI_FLAGS - planning_subtrees = list( - /datum/ai_planning_subtree/pet_planning, - /datum/ai_planning_subtree/simple_find_target/to_flee, // This should only find master fishermen because of the targeting strategy - /datum/ai_planning_subtree/find_nearest_thing_which_attacked_me_to_flee/from_flee_key, - /datum/ai_planning_subtree/make_carp_rift/panic_teleport/flee_key, - /datum/ai_planning_subtree/flee_target/from_flee_key, - /datum/ai_planning_subtree/find_food, - /datum/ai_planning_subtree/make_carp_rift/aggressive_teleport, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/carp_migration, - ) + behavior_tree_json = "code/modules/mob/living/basic/space_fauna/carp/carp_passive.bt.json" + + +/// Shared carp skeleton: escape -> pet command -> (flee / combat / migrate-or-idle) with a target-finding secondary. +/datum/bt_node/subtree/basic_carp_tree + behavior_tree_json = "code/modules/mob/living/basic/space_fauna/carp/basic_carp_tree.bt.json" + +/// Flee or panic-teleport away from a keyed target. +/datum/bt_node/subtree/carp_flee + behavior_tree_json = "code/modules/mob/living/basic/space_fauna/carp/carp_flee.bt.json" + +/// Attack our current target: cast a spell, teleport in, smash obstacles or bite. +/datum/bt_node/subtree/carp_combat + behavior_tree_json = "code/modules/mob/living/basic/space_fauna/carp/carp_combat.bt.json" + +/// Travel a migration path, riding or punching through rifts and walls along the way. +/datum/bt_node/subtree/carp_migration + behavior_tree_json = "code/modules/mob/living/basic/space_fauna/carp/carp_migration.bt.json" + +/// Hunting target finder: flee the nearest threat when injured, otherwise hunt prioritising scary fishermen. +/datum/bt_node/subtree/carp_target_selection + behavior_tree_json = "code/modules/mob/living/basic/space_fauna/carp/carp_target_selection.bt.json" + +/// Bite-back target finder: target whoever has attacked us. +/datum/bt_node/subtree/carp_retaliate_selection + behavior_tree_json = "code/modules/mob/living/basic/space_fauna/carp/carp_retaliate_selection.bt.json" + +/// Passive flee finder: flag scary fishermen and attackers as things to run away from. +/datum/bt_node/subtree/carp_passive_selection + behavior_tree_json = "code/modules/mob/living/basic/space_fauna/carp/carp_passive_selection.bt.json" diff --git a/code/modules/mob/living/basic/space_fauna/carp/carp_flee.bt.json b/code/modules/mob/living/basic/space_fauna/carp/carp_flee.bt.json new file mode 100644 index 00000000000..3f8d758f941 --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/carp/carp_flee.bt.json @@ -0,0 +1,63 @@ +{ + "dm_type": "/datum/bt_node/subtree/carp_flee", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/carp_should_flee", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "target_key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/ability_available", + "vars": { + "ability_key": "BB_CARP_RIFT" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/make_carp_rift/away", + "vars": { + "ability_key": "BB_CARP_RIFT", + "target_key": "BB_CURRENT_TARGET" + } + } + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/find_flee_location", + "vars": { + "target_key": "$bf3k9q2p", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION", + "destination_key": "BB_FLEE_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_FLEE_LOCATION", + "required_dist": 0, + "finish_on_arrival": true + } + } + ] + } + ] + } +} diff --git a/code/modules/mob/living/basic/space_fauna/carp/carp_migration.bt.json b/code/modules/mob/living/basic/space_fauna/carp/carp_migration.bt.json new file mode 100644 index 00000000000..0135ad46a74 --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/carp/carp_migration.bt.json @@ -0,0 +1,114 @@ +{ + "dm_type": "/datum/bt_node/subtree/carp_migration", + "type": "selector", + "children": [ + { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/find_carp_rift_shortcut/nearby", + "vars": { + "destination_key": "BB_CARP_RIFT_DESTINATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CARP_RIFT_DESTINATION", + "required_dist": 0, + "finish_on_arrival": true + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/clear_key", + "vars": { + "key": "BB_CARP_RIFT_DESTINATION" + } + } + ] + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CARP_MIGRATION_TARGET" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/carp_path_blocked", + "vars": { + "target_key": "BB_CARP_MIGRATION_TARGET" + }, + "child": { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/ability_available", + "vars": { + "ability_key": "BB_CARP_RIFT" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/make_carp_rift/towards/unvalidated", + "vars": { + "ability_key": "BB_CARP_RIFT", + "target_key": "BB_CARP_MIGRATION_TARGET" + } + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/attack_obstructions", + "vars": { + "target_key": "BB_CARP_MIGRATION_TARGET", + "time_between_perform": "1.5 SECONDS" + } + } + ] + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/succeed" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CARP_MIGRATION_TARGET", + "required_dist": 3, + "finish_on_arrival": true + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/clear_arrived_migration_target", + "vars": { + "target_key": "BB_CARP_MIGRATION_TARGET" + } + } + ] + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/find_next_carp_migration_step", + "vars": { + "path_key": "BB_CARP_MIGRATION_PATH", + "target_key": "BB_CARP_MIGRATION_TARGET" + } + } + ] +} diff --git a/code/modules/mob/living/basic/space_fauna/carp/carp_passive.bt.json b/code/modules/mob/living/basic/space_fauna/carp/carp_passive.bt.json new file mode 100644 index 00000000000..c4b2c347082 --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/carp/carp_passive.bt.json @@ -0,0 +1,9 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/carp/passive", + "type": "subtree", + "subtype": "/datum/bt_node/subtree/basic_carp_tree", + "bindings": { + "bf1nd7r2": "/datum/bt_node/subtree/carp_passive_selection", + "b7cm2s3z": "BB_BASIC_MOB_FLEE_TARGET" + } +} diff --git a/code/modules/mob/living/basic/space_fauna/carp/carp_passive_selection.bt.json b/code/modules/mob/living/basic/space_fauna/carp/carp_passive_selection.bt.json new file mode 100644 index 00000000000..77bd25864eb --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/carp/carp_passive_selection.bt.json @@ -0,0 +1,25 @@ +{ + "dm_type": "/datum/bt_node/subtree/carp_passive_selection", + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_BASIC_MOB_FLEE_TARGET_HIDING_LOCATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list/nearest", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_FLEE_TARGETING_STRATEGY", + "hiding_location_key": "BB_BASIC_MOB_FLEE_TARGET_HIDING_LOCATION", + "check_faction": false + } + } + ] +} diff --git a/code/modules/mob/living/basic/space_fauna/carp/carp_pet.bt.json b/code/modules/mob/living/basic/space_fauna/carp/carp_pet.bt.json new file mode 100644 index 00000000000..7e84f7bc58f --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/carp/carp_pet.bt.json @@ -0,0 +1,10 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/carp/pet", + "type": "subtree", + "subtype": "/datum/bt_node/subtree/basic_carp_tree", + "bindings": { + "bf1nd7r2": "/datum/bt_node/subtree/carp_retaliate_selection", + "bc0mb4t9": "/datum/bt_node/subtree/carp_retaliate_selection", + "bid1e6k3": "/datum/bt_node/subtree" + } +} diff --git a/code/modules/mob/living/basic/space_fauna/carp/carp_ranged.bt.json b/code/modules/mob/living/basic/space_fauna/carp/carp_ranged.bt.json new file mode 100644 index 00000000000..04b187ecb50 --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/carp/carp_ranged.bt.json @@ -0,0 +1,8 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/carp/ranged", + "type": "subtree", + "subtype": "/datum/bt_node/subtree/basic_carp_tree", + "bindings": { + "be5c4p10": "/datum/bt_node/subtree/escape_captivity" + } +} diff --git a/code/modules/mob/living/basic/space_fauna/carp/carp_retaliate_selection.bt.json b/code/modules/mob/living/basic/space_fauna/carp/carp_retaliate_selection.bt.json new file mode 100644 index 00000000000..0497947d8ed --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/carp/carp_retaliate_selection.bt.json @@ -0,0 +1,11 @@ +{ + "dm_type": "/datum/bt_node/subtree/carp_retaliate_selection", + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION", + "check_faction": false + } +} diff --git a/code/modules/mob/living/basic/space_fauna/carp/carp_target_selection.bt.json b/code/modules/mob/living/basic/space_fauna/carp/carp_target_selection.bt.json new file mode 100644 index 00000000000..0d2446542f6 --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/carp/carp_target_selection.bt.json @@ -0,0 +1,32 @@ +{ + "dm_type": "/datum/bt_node/subtree/carp_target_selection", + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_true", + "vars": { + "key": "BB_BASIC_MOB_STOP_FLEEING", + "invert": true + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets/nearest", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets/prioritize_trait", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] +} diff --git a/code/modules/mob/living/basic/space_fauna/cat_butcherer.bt.json b/code/modules/mob/living/basic/space_fauna/cat_butcherer.bt.json new file mode 100644 index 00000000000..e2291f2dc3f --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/cat_butcherer.bt.json @@ -0,0 +1,5 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/cat_butcherer", + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_hostile_combat_with_retaliate" +} diff --git a/code/modules/mob/living/basic/space_fauna/cat_surgeon.dm b/code/modules/mob/living/basic/space_fauna/cat_surgeon.dm index cc77315ebeb..25586fcbfd4 100644 --- a/code/modules/mob/living/basic/space_fauna/cat_surgeon.dm +++ b/code/modules/mob/living/basic/space_fauna/cat_surgeon.dm @@ -63,17 +63,9 @@ tail.forceMove(drop_location()) /datum/ai_controller/basic_controller/cat_butcherer + behavior_tree_json = "code/modules/mob/living/basic/space_fauna/cat_butcherer.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/target_retaliate, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) diff --git a/code/modules/mob/living/basic/space_fauna/changeling/headslug.bt.json b/code/modules/mob/living/basic/space_fauna/changeling/headslug.bt.json new file mode 100644 index 00000000000..eccbfd80fd9 --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/changeling/headslug.bt.json @@ -0,0 +1,5 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/headslug", + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" +} diff --git a/code/modules/mob/living/basic/space_fauna/changeling/headslug.dm b/code/modules/mob/living/basic/space_fauna/changeling/headslug.dm index 1f9bd60db9d..1707d51ae18 100644 --- a/code/modules/mob/living/basic/space_fauna/changeling/headslug.dm +++ b/code/modules/mob/living/basic/space_fauna/changeling/headslug.dm @@ -95,8 +95,8 @@ /// This is a bit neutered since these aren't intended to exist outside of player control, but it's a bit weird to just have these guys be completely stationary. /// No attacking or anything like that, though. Just something so they seem alive. /datum/ai_controller/basic_controller/headslug + behavior_tree_json = "code/modules/mob/living/basic/space_fauna/changeling/headslug.bt.json" ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk /// Neutered version to prevent people from turning themselves into changelings with sentience potions or transformation /mob/living/basic/headslug/beakless diff --git a/code/modules/mob/living/basic/space_fauna/eyeball/eyeball.bt.json b/code/modules/mob/living/basic/space_fauna/eyeball/eyeball.bt.json new file mode 100644 index 00000000000..1237c1cc75d --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/eyeball/eyeball.bt.json @@ -0,0 +1,183 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/eyeball", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/ability_available", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "ability_key": "BB_GLARE_ABILITY" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/find_target_facing_turf", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "set_key": "BB_GLARE_POSITION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_GLARE_POSITION", + "required_dist": 0 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability", + "vars": { + "ability_key": "BB_GLARE_ABILITY", + "target_key": "BB_CURRENT_TARGET" + } + } + ] + } + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_BLIND_TARGET" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_BLIND_TARGET", + "required_dist": 1 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/heal_eye_damage", + "vars": { + "target_key": "BB_BLIND_TARGET" + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/move_to_and_hunt", + "bindings": { + "bqwjf4id": "2 SECONDS", + "b3cnse9r": "TRUE" + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_BLIND_TARGET", + "target_source": "/datum/target_source/oview_single_type/carbon_mob", + "targeting_strategy": "/datum/targeting_strategy/damaged_eyes", + "vision_range": 9 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_LOW_PRIORITY_HUNTING_TARGET", + "target_source": "/datum/target_source/oview_single_type/carrot", + "targeting_strategy": "/datum/targeting_strategy/anything", + "vision_range": 6 + } + } + ] + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/space_fauna/eyeball/eyeball_ai_behavior.dm b/code/modules/mob/living/basic/space_fauna/eyeball/eyeball_ai_behavior.dm deleted file mode 100644 index 27f637d3a87..00000000000 --- a/code/modules/mob/living/basic/space_fauna/eyeball/eyeball_ai_behavior.dm +++ /dev/null @@ -1,88 +0,0 @@ -/datum/ai_behavior/find_the_blind - -/datum/ai_behavior/find_the_blind/perform(seconds_per_tick, datum/ai_controller/controller, blind_key, threshold_key) - var/mob/living_pawn = controller.pawn - var/list/blind_list = list() - var/eye_damage_threshold = controller.blackboard[threshold_key] - if(!eye_damage_threshold) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - for(var/mob/living/carbon/blind in oview(9, living_pawn)) - var/obj/item/organ/eyes/eyes = blind.get_organ_slot(ORGAN_SLOT_EYES) - if(isnull(eyes)) - continue - if(eyes.damage < eye_damage_threshold) - continue - blind_list += blind - - if(!length(blind_list)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - controller.set_blackboard_key(blind_key, pick(blind_list)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -/datum/ai_behavior/heal_eye_damage - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH - -/datum/ai_behavior/heal_eye_damage/setup(datum/ai_controller/controller, target_key) - . = ..() - var/mob/living/carbon/target = controller.blackboard[target_key] - if(QDELETED(target)) - return FALSE - set_movement_target(controller, target) - -/datum/ai_behavior/heal_eye_damage/perform(seconds_per_tick, datum/ai_controller/controller, target_key) - var/mob/living/carbon/target = controller.blackboard[target_key] - var/mob/living/living_pawn = controller.pawn - - if(QDELETED(target)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - var/obj/item/organ/eyes/eyes = target.get_organ_slot(ORGAN_SLOT_EYES) - var/datum/callback/callback = CALLBACK(living_pawn, TYPE_PROC_REF(/mob/living/basic/eyeball, heal_eye_damage), target, eyes) - callback.Invoke() - - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -/datum/ai_behavior/heal_eye_damage/finish_action(datum/ai_controller/controller, succeeded, target_key) - . = ..() - controller.clear_blackboard_key(target_key) - -/datum/ai_behavior/targeted_mob_ability/glare_at_target - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT - required_distance = 0 - -/datum/ai_behavior/targeted_mob_ability/glare_at_target/setup(datum/ai_controller/controller, ability_key, target_key) - . = ..() - var/atom/target = controller.blackboard[target_key] - if (isnull(target)) - return FALSE - - var/turf/turf_to_move_towards = get_step(target, target.dir) - if(turf_to_move_towards.is_blocked_turf(ignore_atoms = list(controller.pawn))) - return FALSE - - if(isnull(turf_to_move_towards)) - return FALSE - - set_movement_target(controller, turf_to_move_towards) - -/datum/ai_behavior/targeted_mob_ability/glare_at_target/perform(seconds_per_tick, datum/ai_controller/controller, ability_key, target_key) - var/datum/action/cooldown/ability = controller.blackboard[ability_key] - var/mob/living/target = controller.blackboard[target_key] - - if(QDELETED(ability) || QDELETED(target)) - return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED - - var/direction_to_compare = get_dir(target, controller.pawn) - var/target_direction = target.dir - if(direction_to_compare != target_direction) - return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED - - var/result = ability.InterceptClickOn(controller.pawn, null, target) - if(result == TRUE) - return AI_BEHAVIOR_INSTANT - else - return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED - -/datum/ai_behavior/hunt_target/interact_with_target/carrot - hunt_cooldown = 2 SECONDS - always_reset_target = TRUE diff --git a/code/modules/mob/living/basic/space_fauna/eyeball/eyeball_ai_subtree.dm b/code/modules/mob/living/basic/space_fauna/eyeball/eyeball_ai_subtree.dm index e24abb62b5e..5a3a2ccacce 100644 --- a/code/modules/mob/living/basic/space_fauna/eyeball/eyeball_ai_subtree.dm +++ b/code/modules/mob/living/basic/space_fauna/eyeball/eyeball_ai_subtree.dm @@ -5,25 +5,9 @@ ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/targeted_mob_ability/glare, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/heal_the_blind, - /datum/ai_planning_subtree/find_and_hunt_target/carrot, - ) + behavior_tree_json = "code/modules/mob/living/basic/space_fauna/eyeball/eyeball.bt.json" -/datum/ai_planning_subtree/heal_the_blind - -/datum/ai_planning_subtree/heal_the_blind/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(controller.blackboard_key_exists(BB_BLIND_TARGET)) - controller.queue_behavior(/datum/ai_behavior/heal_eye_damage, BB_BLIND_TARGET) - return SUBTREE_RETURN_FINISH_PLANNING - controller.queue_behavior(/datum/ai_behavior/find_the_blind, BB_BLIND_TARGET, BB_EYE_DAMAGE_THRESHOLD) - -/datum/targeting_strategy/basic/eyeball/can_attack(mob/living/owner, atom/target, vision_range) +/datum/targeting_strategy/basic/eyeball/is_valid_target(mob/living/owner, atom/target, vision_range, datum/ai_controller/controller = null) . = ..() if(!.) return FALSE @@ -40,14 +24,3 @@ return FALSE return can_see(target, owner, 9) //if the target cant see us dont attack him - -/datum/ai_planning_subtree/targeted_mob_ability/glare - ability_key = BB_GLARE_ABILITY - use_ability_behaviour = /datum/ai_behavior/targeted_mob_ability/glare_at_target - finish_planning = TRUE - -/datum/ai_planning_subtree/find_and_hunt_target/carrot - target_key = BB_LOW_PRIORITY_HUNTING_TARGET - hunting_behavior = /datum/ai_behavior/hunt_target/interact_with_target/carrot - hunt_targets = list(/obj/item/food/grown/carrotlike/carrot) - hunt_range = 6 diff --git a/code/modules/mob/living/basic/space_fauna/faithless.bt.json b/code/modules/mob/living/basic/space_fauna/faithless.bt.json new file mode 100644 index 00000000000..ae18da6d4eb --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/faithless.bt.json @@ -0,0 +1,137 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/faithless", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/attack_obstructions", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_LOW_PRIORITY_HUNTING_TARGET" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_LOW_PRIORITY_HUNTING_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/hunt_target/interact_with_target", + "vars": { + "target_key": "BB_LOW_PRIORITY_HUNTING_TARGET", + "always_reset_target": true + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_LOW_PRIORITY_HUNTING_TARGET", + "target_source": "/datum/target_source/oview_typed/from_bb_key/hunt_target_list", + "targeting_strategy": "/datum/targeting_strategy/unbroken_light", + "vision_range": 7 + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop", + "bindings": { + "bqdqne64": "/datum/bt_node/ai_behavior/random_speech/faithless" + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/space_fauna/faithless.dm b/code/modules/mob/living/basic/space_fauna/faithless.dm index c4f195cc0a1..1cc1df54c3c 100644 --- a/code/modules/mob/living/basic/space_fauna/faithless.dm +++ b/code/modules/mob/living/basic/space_fauna/faithless.dm @@ -41,6 +41,7 @@ AddElement(/datum/element/door_pryer) AddElement(/datum/element/footstep, FOOTSTEP_MOB_SHOE) AddElement(/datum/element/mob_grabber, steal_from_others = FALSE) + ai_controller.set_blackboard_key(BB_HUNT_TARGET_LIST, typecacheof(list(/obj/machinery/light))) /mob/living/basic/faithless/melee_attack(atom/target, list/modifiers, ignore_cooldown) . = ..() @@ -54,20 +55,10 @@ span_userdanger("\The [src] knocks you down!")) /datum/ai_controller/basic_controller/faithless + behavior_tree_json = "code/modules/mob/living/basic/space_fauna/faithless.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_TARGET_MINIMUM_STAT = UNCONSCIOUS, - BB_LOW_PRIORITY_HUNTING_TARGET = null, // lights ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/attack_obstacle_in_path/low_priority_target, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/find_and_hunt_target/look_for_light_fixtures, - /datum/ai_planning_subtree/random_speech/faithless, - ) diff --git a/code/modules/mob/living/basic/space_fauna/garden_gnome.bt.json b/code/modules/mob/living/basic/space_fauna/garden_gnome.bt.json new file mode 100644 index 00000000000..f79314d9541 --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/garden_gnome.bt.json @@ -0,0 +1,114 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/garden_gnome", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/attack_obstructions", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION", + "check_faction": "$b2jvnm5d" + } + }, + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "loop_delay": "1 SECONDS", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/random_speech/garden_gnome" + } + ] + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/space_fauna/garden_gnome.dm b/code/modules/mob/living/basic/space_fauna/garden_gnome.dm index f6926b2ea51..c36a3ff85bb 100644 --- a/code/modules/mob/living/basic/space_fauna/garden_gnome.dm +++ b/code/modules/mob/living/basic/space_fauna/garden_gnome.dm @@ -124,16 +124,9 @@ potential_gnome.ai_controller.set_blackboard_key_assoc_lazylist(BB_BASIC_MOB_RETALIATE_LIST, attacker, world.time) /datum/ai_controller/basic_controller/garden_gnome + behavior_tree_json = "code/modules/mob/living/basic/space_fauna/garden_gnome.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk/less_walking - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/target_retaliate, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/random_speech/garden_gnome, - ) diff --git a/code/modules/mob/living/basic/space_fauna/ghost.bt.json b/code/modules/mob/living/basic/space_fauna/ghost.bt.json new file mode 100644 index 00000000000..7f79b254cf1 --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/ghost.bt.json @@ -0,0 +1,14 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/ghost", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_retaliate_combat" + } + ] +} diff --git a/code/modules/mob/living/basic/space_fauna/ghost.dm b/code/modules/mob/living/basic/space_fauna/ghost.dm index cfdf6840ff2..904857438f3 100644 --- a/code/modules/mob/living/basic/space_fauna/ghost.dm +++ b/code/modules/mob/living/basic/space_fauna/ghost.dm @@ -94,18 +94,12 @@ name = "ghost of [pick(GLOB.first_names_female)] [pick(GLOB.last_names)]" /datum/ai_controller/basic_controller/ghost + behavior_tree_json = "code/modules/mob/living/basic/space_fauna/ghost.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/target_retaliate, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) /// Weaker variant of ghosts. Meant to be summoned in swarms via the ectoplasmic anomaly and associated ghost portal. /mob/living/basic/ghost/swarm diff --git a/code/modules/mob/living/basic/space_fauna/hivebot/hivebot.bt.json b/code/modules/mob/living/basic/space_fauna/hivebot/hivebot.bt.json new file mode 100644 index 00000000000..d02a4edaaf9 --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/hivebot/hivebot.bt.json @@ -0,0 +1,119 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/hivebot", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/attack_obstructions", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/relay_to_hive_partner" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_SUCCEED_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_FAIL_ON_FAILURE", + "loop_delay": "1 SECONDS", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/random_chance", + "vars": { + "chance": 0.1 + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_HIVE_PARTNER", + "target_source": "/datum/target_source/oview_single_type/hivebot", + "targeting_strategy": "/datum/targeting_strategy/living_not_dead", + "vision_range": 10 + } + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/space_fauna/hivebot/hivebot_behavior.dm b/code/modules/mob/living/basic/space_fauna/hivebot/hivebot_behavior.dm index 54c3c13d139..f299d032b1d 100644 --- a/code/modules/mob/living/basic/space_fauna/hivebot/hivebot_behavior.dm +++ b/code/modules/mob/living/basic/space_fauna/hivebot/hivebot_behavior.dm @@ -1,35 +1,17 @@ -/datum/ai_behavior/find_and_set/hive_partner +/// Moves to the hive partner in BB_HIVE_PARTNER, relays a message, then clears the key. +/// The partner is found by a separate acquire_target leaf in the controller tree. +/datum/bt_node/subtree/relay_to_hive_partner + behavior_tree_json = "code/modules/mob/living/basic/space_fauna/hivebot/relay_to_hive_partner.bt.json" -/datum/ai_behavior/find_and_set/hive_partner/search_tactic(datum/ai_controller/controller, locate_path, search_range = 10) - var/mob/living/living_pawn = controller.pawn - var/list/hive_partners = list() - for(var/mob/living/target in oview(search_range, living_pawn)) - if(!istype(target, locate_path)) - continue - if(target.stat == DEAD) - continue - hive_partners += target - - if(length(hive_partners)) - return pick(hive_partners) - -/// behavior that allow us to go communicate with other hivebots -/datum/ai_behavior/relay_message - ///length of the message we will relay +/// Says a random binary string to a hive partner. Movement to the partner is handled +/// in the tree via a move_to_target leaf; the target key is cleared by a clear_key leaf. +/datum/bt_node/ai_behavior/relay_message + /// Blackboard key holding the hive partner to talk at. + var/target_key + /// Number of bits in the message we relay. var/length_of_message = 4 - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT| AI_BEHAVIOR_REQUIRE_REACH | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION - -/datum/ai_behavior/relay_message/setup(datum/ai_controller/controller, target_key) - . = ..() - var/mob/living/target = controller.blackboard[target_key] - // It stopped existing - if(QDELETED(target)) - return FALSE - set_movement_target(controller, target) - - -/datum/ai_behavior/relay_message/perform(seconds_per_tick, datum/ai_controller/controller, target_key) +/datum/bt_node/ai_behavior/relay_message/perform(seconds_per_tick, datum/ai_controller/controller) var/mob/living/target = controller.blackboard[target_key] var/mob/living/living_pawn = controller.pawn @@ -38,31 +20,13 @@ var/message_relayed = "" for(var/i in 1 to length_of_message) message_relayed += prob(50) ? "1" : "0" - living_pawn.say(message_relayed, forced = "AI Controller") + INVOKE_ASYNC(living_pawn, TYPE_PROC_REF(/atom/movable, say), message_relayed, forced = "AI Controller") return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED -/datum/ai_behavior/relay_message/finish_action(datum/ai_controller/controller, succeeded, target_key) - . = ..() - controller.clear_blackboard_key(target_key) - -/datum/ai_behavior/find_hunt_target/repair_machines - -/datum/ai_behavior/find_hunt_target/repair_machines/valid_dinner(mob/living/source, obj/machinery/repair_target, radius) - if(repair_target.get_integrity() >= repair_target.max_integrity) - return FALSE - - return can_see(source, repair_target, radius) - -/datum/ai_behavior/hunt_target/repair_machines +/// Repairs a damaged machine once in range. Finding the machine is a separate +/// acquire_target leaf; movement is a move_to_target leaf. +/datum/bt_node/ai_behavior/hunt_target/repair_machines always_reset_target = TRUE -/datum/ai_behavior/hunt_target/repair_machines/target_caught(mob/living/basic/hivebot/mechanic/hunter, obj/machinery/repair_target) +/datum/bt_node/ai_behavior/hunt_target/repair_machines/target_caught(mob/living/basic/hivebot/mechanic/hunter, obj/machinery/repair_target) hunter.repair_machine(repair_target) - -/datum/ai_behavior/basic_ranged_attack/hivebot - action_cooldown = 3 SECONDS - avoid_friendly_fire = TRUE - -/datum/ai_behavior/basic_ranged_attack/hivebot_rapid - action_cooldown = 1.5 SECONDS - avoid_friendly_fire = TRUE diff --git a/code/modules/mob/living/basic/space_fauna/hivebot/hivebot_mechanic.bt.json b/code/modules/mob/living/basic/space_fauna/hivebot/hivebot_mechanic.bt.json new file mode 100644 index 00000000000..444702cb7d9 --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/hivebot/hivebot_mechanic.bt.json @@ -0,0 +1,166 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/hivebot/mechanic", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/attack_obstructions", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_ranged_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "key": "BB_MACHINE_TARGET" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_MACHINE_TARGET", + "required_dist": 1 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/hunt_target/repair_machines", + "vars": { + "target_key": "BB_MACHINE_TARGET" + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/relay_to_hive_partner" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_MACHINE_TARGET", + "target_source": "/datum/target_source/oview_single_type/machine", + "targeting_strategy": "/datum/targeting_strategy/damaged_machine", + "vision_range": 10 + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/random_chance", + "vars": { + "chance": 0.1 + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_HIVE_PARTNER", + "invert": true + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_HIVE_PARTNER", + "target_source": "/datum/target_source/oview_single_type/hivebot", + "targeting_strategy": "/datum/targeting_strategy/living_not_dead", + "vision_range": 10 + } + } + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/space_fauna/hivebot/hivebot_ranged.bt.json b/code/modules/mob/living/basic/space_fauna/hivebot/hivebot_ranged.bt.json new file mode 100644 index 00000000000..b6009de5a22 --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/hivebot/hivebot_ranged.bt.json @@ -0,0 +1,130 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/hivebot/ranged", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/attack_obstructions", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_ranged_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION", + "time_between_perform": "3 SECONDS", + "avoid_friendly_fire": true + } + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/relay_to_hive_partner" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/random_chance", + "vars": { + "chance": 0.1 + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_HIVE_PARTNER", + "invert": true + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_HIVE_PARTNER", + "target_source": "/datum/target_source/oview_single_type/hivebot", + "targeting_strategy": "/datum/targeting_strategy/living_not_dead", + "vision_range": 10 + } + } + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/space_fauna/hivebot/hivebot_ranged_rapid.bt.json b/code/modules/mob/living/basic/space_fauna/hivebot/hivebot_ranged_rapid.bt.json new file mode 100644 index 00000000000..38703850ca1 --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/hivebot/hivebot_ranged_rapid.bt.json @@ -0,0 +1,130 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/hivebot/ranged/rapid", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/attack_obstructions", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_ranged_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION", + "time_between_perform": "1.5 SECONDS", + "avoid_friendly_fire": true + } + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/relay_to_hive_partner" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/random_chance", + "vars": { + "chance": 0.1 + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_HIVE_PARTNER", + "invert": true + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_HIVE_PARTNER", + "target_source": "/datum/target_source/oview_single_type/hivebot", + "targeting_strategy": "/datum/targeting_strategy/living_not_dead", + "vision_range": 10 + } + } + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/space_fauna/hivebot/hivebot_subtree.dm b/code/modules/mob/living/basic/space_fauna/hivebot/hivebot_subtree.dm index 34ace878929..affe6266151 100644 --- a/code/modules/mob/living/basic/space_fauna/hivebot/hivebot_subtree.dm +++ b/code/modules/mob/living/basic/space_fauna/hivebot/hivebot_subtree.dm @@ -4,68 +4,13 @@ ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/hive_communicate, - ) + behavior_tree_json = "code/modules/mob/living/basic/space_fauna/hivebot/hivebot.bt.json" /datum/ai_controller/basic_controller/hivebot/mechanic - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_ranged_attack_subtree, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/find_and_hunt_target/repair_machines, - /datum/ai_planning_subtree/hive_communicate, - ) + behavior_tree_json = "code/modules/mob/living/basic/space_fauna/hivebot/hivebot_mechanic.bt.json" /datum/ai_controller/basic_controller/hivebot/ranged - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_ranged_attack_subtree/hivebot, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/hive_communicate, - ) + behavior_tree_json = "code/modules/mob/living/basic/space_fauna/hivebot/hivebot_ranged.bt.json" /datum/ai_controller/basic_controller/hivebot/ranged/rapid - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_ranged_attack_subtree/hivebot_rapid, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/hive_communicate, - ) - - -/datum/ai_planning_subtree/basic_ranged_attack_subtree/hivebot_rapid - ranged_attack_behavior = /datum/ai_behavior/basic_ranged_attack/hivebot_rapid - - -/datum/ai_planning_subtree/basic_ranged_attack_subtree/hivebot - ranged_attack_behavior = /datum/ai_behavior/basic_ranged_attack/hivebot - -/datum/ai_planning_subtree/hive_communicate - ///chance to go and relay message - var/relay_chance = 10 - -/datum/ai_planning_subtree/hive_communicate/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - - if(!SPT_PROB(relay_chance, seconds_per_tick)) - return - - if (controller.blackboard_key_exists(BB_HIVE_PARTNER)) - controller.queue_behavior(/datum/ai_behavior/relay_message, BB_HIVE_PARTNER) - return SUBTREE_RETURN_FINISH_PLANNING - controller.queue_behavior(/datum/ai_behavior/find_and_set/hive_partner, BB_HIVE_PARTNER, /mob/living/basic/hivebot) - -/datum/ai_planning_subtree/find_and_hunt_target/repair_machines - target_key = BB_MACHINE_TARGET - hunting_behavior = /datum/ai_behavior/hunt_target/repair_machines - finding_behavior = /datum/ai_behavior/find_hunt_target/repair_machines - hunt_targets = list(/obj/machinery) - hunt_range = 10 - hunt_chance = 35 + behavior_tree_json = "code/modules/mob/living/basic/space_fauna/hivebot/hivebot_ranged_rapid.bt.json" diff --git a/code/modules/mob/living/basic/space_fauna/hivebot/relay_to_hive_partner.bt.json b/code/modules/mob/living/basic/space_fauna/hivebot/relay_to_hive_partner.bt.json new file mode 100644 index 00000000000..52ce2daa107 --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/hivebot/relay_to_hive_partner.bt.json @@ -0,0 +1,36 @@ +{ + "dm_type": "/datum/bt_node/subtree/relay_to_hive_partner", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "key": "BB_HIVE_PARTNER" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_HIVE_PARTNER", + "required_dist": 1 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/relay_message", + "vars": { + "target_key": "BB_HIVE_PARTNER" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/clear_key", + "vars": { + "key": "BB_HIVE_PARTNER" + } + } + ] + } +} diff --git a/code/modules/mob/living/basic/space_fauna/killer_tomato.bt.json b/code/modules/mob/living/basic/space_fauna/killer_tomato.bt.json new file mode 100644 index 00000000000..726caa4a98d --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/killer_tomato.bt.json @@ -0,0 +1,8 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/killer_tomato", + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_hostile_obstacles_combat", + "bindings": { + "bqul7l8t": "/datum/bt_node/subtree/random_speech_loop" + } +} diff --git a/code/modules/mob/living/basic/space_fauna/killer_tomato.dm b/code/modules/mob/living/basic/space_fauna/killer_tomato.dm index 152c14ff38e..00405bd1531 100644 --- a/code/modules/mob/living/basic/space_fauna/killer_tomato.dm +++ b/code/modules/mob/living/basic/space_fauna/killer_tomato.dm @@ -41,16 +41,14 @@ ADD_TRAIT(src, TRAIT_VENTCRAWLER_ALWAYS, INNATE_TRAIT) /datum/ai_controller/basic_controller/killer_tomato + behavior_tree_json = "code/modules/mob/living/basic/space_fauna/killer_tomato.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_BASIC_MOB_SPEAK_LINES = list( + BB_EMOTE_HEAR = list("gnashes.", "growls lowly.", "snarls."), + BB_EMOTE_SEE = list("salivates."), + BB_SPEAK_CHANCE = 3, + ), ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/random_speech/killer_tomato, - ) diff --git a/code/modules/mob/living/basic/space_fauna/lightgeist.bt.json b/code/modules/mob/living/basic/space_fauna/lightgeist.bt.json new file mode 100644 index 00000000000..ddbc437caf1 --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/lightgeist.bt.json @@ -0,0 +1,5 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/lightgeist", + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_hostile_combat" +} diff --git a/code/modules/mob/living/basic/space_fauna/lightgeist.dm b/code/modules/mob/living/basic/space_fauna/lightgeist.dm index b86d50e2efb..da6fff87715 100644 --- a/code/modules/mob/living/basic/space_fauna/lightgeist.dm +++ b/code/modules/mob/living/basic/space_fauna/lightgeist.dm @@ -71,18 +71,13 @@ death() /datum/ai_controller/basic_controller/lightgeist + behavior_tree_json = "code/modules/mob/living/basic/space_fauna/lightgeist.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/lightgeist, ) ai_traits = PASSIVE_AI_FLAGS ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk/less_walking - - planning_subtrees = list( - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_melee_attack_subtree, // We heal things by attacking them - ) /// Attack only mobs who have damage that we can heal, I think this is specific enough not to be a generic type /datum/targeting_strategy/lightgeist @@ -91,7 +86,7 @@ /// Type of limb we can heal var/required_bodytype = BODYTYPE_ORGANIC -/datum/targeting_strategy/lightgeist/can_attack(mob/living/living_mob, mob/living/target, vision_range) +/datum/targeting_strategy/lightgeist/is_valid_target(mob/living/living_mob, mob/living/target, vision_range, datum/ai_controller/controller = null) if (!isliving(target) || target.stat == DEAD) return FALSE if (!(heal_biotypes & target.mob_biotypes)) diff --git a/code/modules/mob/living/basic/space_fauna/meteor_heart/meteor_heart.bt.json b/code/modules/mob/living/basic/space_fauna/meteor_heart/meteor_heart.bt.json new file mode 100644 index 00000000000..2380cf189ca --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/meteor_heart/meteor_heart.bt.json @@ -0,0 +1,73 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/meteor_heart", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CURRENT_TARGET", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability", + "vars": { + "ability_key": "BB_METEOR_HEART_GROUND_SPIKES", + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/use_mob_ability", + "vars": { + "ability_key": "BB_METEOR_HEART_SPINE_TRAPS" + } + } + ] + } + ] + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/meteor_heart_deaggro" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/space_fauna/meteor_heart/meteor_heart.dm b/code/modules/mob/living/basic/space_fauna/meteor_heart/meteor_heart.dm index ccb86e7043c..4ca874df94f 100644 --- a/code/modules/mob/living/basic/space_fauna/meteor_heart/meteor_heart.dm +++ b/code/modules/mob/living/basic/space_fauna/meteor_heart/meteor_heart.dm @@ -43,7 +43,7 @@ /datum/action/cooldown/mob_cooldown/spine_traps = BB_METEOR_HEART_SPINE_TRAPS, ) grant_actions_by_list(innate_actions) - ai_controller.set_ai_status(AI_STATUS_OFF) + ai_controller.force_ai_off() RegisterSignal(src, COMSIG_MOB_ABILITY_FINISHED, PROC_REF(used_ability)) RegisterSignal(src, COMSIG_ATOM_WAS_ATTACKED, PROC_REF(aggro)) @@ -62,7 +62,7 @@ /mob/living/basic/meteor_heart/proc/aggro() if (ai_controller.ai_status == AI_STATUS_ON) return - ai_controller.reset_ai_status() + ai_controller.clear_forced_off() if (!ai_controller.ai_status == AI_STATUS_ON) return icon_state = "heart_aggro" @@ -70,7 +70,7 @@ /// Called when we stop being mad /mob/living/basic/meteor_heart/proc/deaggro() - ai_controller.set_ai_status(AI_STATUS_OFF) + ai_controller.force_ai_off() icon_state = "heart" soundloop.set_mid_length(HEARTBEAT_NORMAL) diff --git a/code/modules/mob/living/basic/space_fauna/meteor_heart/meteor_heart_ai.dm b/code/modules/mob/living/basic/space_fauna/meteor_heart/meteor_heart_ai.dm index 92f1d0a9889..3e3a801d083 100644 --- a/code/modules/mob/living/basic/space_fauna/meteor_heart/meteor_heart_ai.dm +++ b/code/modules/mob/living/basic/space_fauna/meteor_heart/meteor_heart_ai.dm @@ -1,33 +1,29 @@ /// A spellcasting AI which does not move /datum/ai_controller/basic_controller/meteor_heart + behavior_tree_json = "code/modules/mob/living/basic/space_fauna/meteor_heart/meteor_heart.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, - BB_TARGETLESS_TIME = 0, + BB_CURRENT_TARGET_HIDING_LOCATION = null, ) - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/targeted_mob_ability/ground_spikes, - /datum/ai_planning_subtree/use_mob_ability/spine_traps, - /datum/ai_planning_subtree/sleep_with_no_target/meteor_heart, - ) +/// After enough time with no target, calls deaggro() on the meteor heart to shut down the AI and reset visuals. +/datum/bt_node/ai_behavior/meteor_heart_deaggro + var/deaggro_delay = 10 SECONDS + VAR_PRIVATE/timerid -/datum/ai_planning_subtree/targeted_mob_ability/ground_spikes - ability_key = BB_METEOR_HEART_GROUND_SPIKES - finish_planning = FALSE +/datum/bt_node/ai_behavior/meteor_heart_deaggro/setup(datum/ai_controller/controller) + . = ..() + timerid = addtimer(CALLBACK(src, PROC_REF(finish_action), controller, TRUE), deaggro_delay, TIMER_UNIQUE | TIMER_STOPPABLE) -/datum/ai_planning_subtree/use_mob_ability/spine_traps - ability_key = BB_METEOR_HEART_SPINE_TRAPS +/datum/bt_node/ai_behavior/meteor_heart_deaggro/perform(seconds_per_tick, datum/ai_controller/controller) + return AI_BEHAVIOR_DELAY -/// After enough time with no target, deaggro and change animation state -/datum/ai_planning_subtree/sleep_with_no_target/meteor_heart - sleep_behaviour = /datum/ai_behavior/sleep_after_targetless_time/meteor_heart - -/datum/ai_behavior/sleep_after_targetless_time/meteor_heart - -/datum/ai_behavior/sleep_after_targetless_time/meteor_heart/enter_sleep(datum/ai_controller/controller) +/datum/bt_node/ai_behavior/meteor_heart_deaggro/finish_action(datum/ai_controller/controller, succeeded) + . = ..() + deltimer(timerid) + timerid = null + if(!succeeded) + return var/mob/living/basic/meteor_heart/heart = controller.pawn - if (!istype(heart)) - return ..() - heart.deaggro() + if(istype(heart)) + heart.deaggro() diff --git a/code/modules/mob/living/basic/space_fauna/morph.bt.json b/code/modules/mob/living/basic/space_fauna/morph.bt.json new file mode 100644 index 00000000000..d9508fc1eaa --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/morph.bt.json @@ -0,0 +1,5 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/morph", + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_hostile_combat_with_retaliate" +} diff --git a/code/modules/mob/living/basic/space_fauna/morph.dm b/code/modules/mob/living/basic/space_fauna/morph.dm index a76704e40fe..c0e92d20bbe 100644 --- a/code/modules/mob/living/basic/space_fauna/morph.dm +++ b/code/modules/mob/living/basic/space_fauna/morph.dm @@ -200,16 +200,10 @@ /// No fleshed out AI implementation, just something that make these fellers seem lively if they're just dropped into a station. /// Only real human-powered intelligence is capable of playing prop hunt in SS13 (until further notice). /datum/ai_controller/basic_controller/morph + behavior_tree_json = "code/modules/mob/living/basic/space_fauna/morph.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/target_retaliate, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) diff --git a/code/modules/mob/living/basic/space_fauna/mushroom.bt.json b/code/modules/mob/living/basic/space_fauna/mushroom.bt.json new file mode 100644 index 00000000000..6f6b21c905a --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/mushroom.bt.json @@ -0,0 +1,103 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/mushroom", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/move_to_and_hunt", + "bindings": { + "bvtz06kb": "BB_LOW_PRIORITY_HUNTING_TARGET", + "b3y599q4": "BB_LOW_PRIORITY_HUNTING_TARGET", + "brrasnah": "BB_LOW_PRIORITY_HUNTING_TARGET", + "bd1towgc": "TRUE", + "b3cnse9r": "TRUE" + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_LOW_PRIORITY_HUNTING_TARGET", + "invert": true + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_LOW_PRIORITY_HUNTING_TARGET", + "targeting_strategy": "/datum/targeting_strategy/pickup_item", + "target_source": "/datum/target_source/oview_single_type/mushroom_food", + "vision_range": 6 + } + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/space_fauna/mushroom.dm b/code/modules/mob/living/basic/space_fauna/mushroom.dm index e6195ba9d9c..90f87c30d10 100644 --- a/code/modules/mob/living/basic/space_fauna/mushroom.dm +++ b/code/modules/mob/living/basic/space_fauna/mushroom.dm @@ -55,32 +55,21 @@ ADD_TRAIT(src, TRAIT_VENTCRAWLER_ALWAYS, INNATE_TRAIT) /datum/ai_controller/basic_controller/mushroom + behavior_tree_json = "code/modules/mob/living/basic/space_fauna/mushroom.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/mushroom, BB_TARGET_MINIMUM_STAT = DEAD, ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/find_and_hunt_target/mushroom_food, - ) - /datum/targeting_strategy/basic/mushroom + custom_faction_check = TRUE ///we only attacked another mushrooms /datum/targeting_strategy/basic/mushroom/faction_check(datum/ai_controller/controller, mob/living/living_mob, mob/living/the_target) return !living_mob.faction_check_atom(the_target, exact_match = check_factions_exactly) -/datum/ai_planning_subtree/find_and_hunt_target/mushroom_food - target_key = BB_LOW_PRIORITY_HUNTING_TARGET - hunting_behavior = /datum/ai_behavior/hunt_target/interact_with_target/reset_target - hunt_targets = list(/obj/item/food/grown/mushroom) - hunt_range = 6 - /mob/living/basic/mushroom/UnarmedAttack(atom/attack_target, proximity_flag, list/modifiers) . = ..() if(!.) diff --git a/code/modules/mob/living/basic/space_fauna/paper_wizard/paper_wizard.bt.json b/code/modules/mob/living/basic/space_fauna/paper_wizard/paper_wizard.bt.json new file mode 100644 index 00000000000..2ba0d9b479a --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/paper_wizard/paper_wizard.bt.json @@ -0,0 +1,121 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/paper_wizard", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/attack_obstructions", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability", + "vars": { + "ability_key": "BB_WIZARD_MIMICS", + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/use_mob_ability", + "vars": { + "ability_key": "BB_WIZARD_SUMMON_MINIONS" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/find_paper_and_write" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_FOUND_PAPER", + "target_source": "/datum/target_source/oview_single_type/paper", + "targeting_strategy": "/datum/targeting_strategy/empty_paper", + "time_between_perform": "10 SECONDS" + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/space_fauna/paper_wizard/paper_wizard.dm b/code/modules/mob/living/basic/space_fauna/paper_wizard/paper_wizard.dm index 24980cc0110..47ea7c39c47 100644 --- a/code/modules/mob/living/basic/space_fauna/paper_wizard/paper_wizard.dm +++ b/code/modules/mob/living/basic/space_fauna/paper_wizard/paper_wizard.dm @@ -47,6 +47,7 @@ AddElement(/datum/element/death_drops, /obj/effect/temp_visual/paperwiz_dying) /datum/ai_controller/basic_controller/paper_wizard + behavior_tree_json = "code/modules/mob/living/basic/space_fauna/paper_wizard/paper_wizard.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_WRITING_LIST = list( @@ -57,46 +58,6 @@ ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk/less_walking - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/targeted_mob_ability/wizard_mimic, - /datum/ai_planning_subtree/use_mob_ability/wizard_summon_minions, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/attack_obstacle_in_path/paper_wizard, - /datum/ai_planning_subtree/find_paper_and_write, - ) - -/datum/ai_planning_subtree/attack_obstacle_in_path/paper_wizard - target_key = BB_FOUND_PAPER - attack_behaviour = /datum/ai_behavior/attack_obstructions/paper_wizard - -/datum/ai_behavior/attack_obstructions/paper_wizard - action_cooldown = 0.4 SECONDS - can_attack_turfs = TRUE - can_attack_dense_objects = TRUE - -/datum/ai_planning_subtree/targeted_mob_ability/wizard_mimic - ability_key = BB_WIZARD_MIMICS - finish_planning = FALSE - -/datum/ai_planning_subtree/use_mob_ability/wizard_summon_minions - ability_key = BB_WIZARD_SUMMON_MINIONS - finish_planning = FALSE - -/datum/ai_behavior/find_and_set/empty_paper - action_cooldown = 10 SECONDS - -/datum/ai_behavior/find_and_set/empty_paper/search_tactic(datum/ai_controller/controller, locate_path, search_range = SEARCH_TACTIC_DEFAULT_RANGE) - var/list/empty_papers = list() - - for(var/obj/item/paper/target_paper in oview(search_range, controller.pawn)) - if(target_paper.is_empty()) - empty_papers += target_paper - - if(empty_papers.len) - return pick(empty_papers) /mob/living/basic/paper_wizard/copy desc = "'Tis a ruse!" diff --git a/code/modules/mob/living/basic/space_fauna/regal_rat/regal_rat.bt.json b/code/modules/mob/living/basic/space_fauna/regal_rat/regal_rat.bt.json new file mode 100644 index 00000000000..8b9cf685188 --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/regal_rat/regal_rat.bt.json @@ -0,0 +1,98 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/regal_rat", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_BASIC_MOB_FLEE_TARGET", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/run_away_from_target", + "bindings": { + "byk9gqj4": "BB_BASIC_MOB_FLEE_TARGET" + } + }, + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability", + "vars": { + "ability_key": "BB_RAISE_HORDE_ABILITY", + "target_key": "BB_BASIC_MOB_FLEE_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/attack_obstructions", + "vars": { + "target_key": "BB_BASIC_MOB_FLEE_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_BASIC_MOB_FLEE_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_BASIC_MOB_FLEE_TARGET_HIDING_LOCATION" + } + } + ] + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list", + "vars": { + "target_key": "BB_BASIC_MOB_FLEE_TARGET", + "targeting_strategy": "BB_FLEE_TARGETING_STRATEGY", + "hiding_location_key": "BB_BASIC_MOB_FLEE_TARGET_HIDING_LOCATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/use_mob_ability/domain", + "vars": { + "ability_key": "BB_DOMAIN_ABILITY" + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/space_fauna/regal_rat/regal_rat_actions.dm b/code/modules/mob/living/basic/space_fauna/regal_rat/regal_rat_actions.dm index b51871c7f9d..83c3c18397b 100644 --- a/code/modules/mob/living/basic/space_fauna/regal_rat/regal_rat_actions.dm +++ b/code/modules/mob/living/basic/space_fauna/regal_rat/regal_rat_actions.dm @@ -125,7 +125,6 @@ * * Convert nearby mice into aggressive rats. * * Convert nearby roaches into aggressive roaches. * * Convert nearby frogs into aggressive frogs. - * * Spawn a single mouse if below the mouse cap. */ /datum/action/cooldown/mob_cooldown/riot/proc/riot() playsound(owner, 'sound/mobs/non-humanoids/mouse/mousesqueek.ogg', vol = 150, frequency = 10000) @@ -144,15 +143,14 @@ command_feedback = "squeak!" // Frogs and roaches can squeak too it's fine pointed_reaction = "and squeaks aggressively" refuse_reaction = "quivers" - attack_behaviour = /datum/ai_behavior/basic_melee_attack -// Command you can give to a mouse to make it kill someone +// Command you can give to a glockroach to make it shoot someone /datum/pet_command/attack/glockroach speech_commands = list("attack", "sic", "kill", "cheese em") command_feedback = "squeak!" pointed_reaction = "and cocks its gun" refuse_reaction = "quivers" - attack_behaviour = /datum/ai_behavior/basic_ranged_attack/glockroach + attack_subtree = /datum/bt_node/subtree/pet_command/attack/ranged/glockroach /** *Spittle; harmless reagent that is added by rat king, and makes you disgusted. @@ -193,4 +191,4 @@ affected_mob.vomit(VOMIT_CATEGORY_DEFAULT) /datum/pet_command/protect_owner/glockroach - protect_behavior = /datum/ai_behavior/basic_ranged_attack/glockroach + protect_owner_subtree = /datum/bt_node/subtree/pet_command/protect_owner/ranged/glockroach diff --git a/code/modules/mob/living/basic/space_fauna/regal_rat/regal_rat_ai.dm b/code/modules/mob/living/basic/space_fauna/regal_rat/regal_rat_ai.dm index 27f1eb47785..e39fc380f24 100644 --- a/code/modules/mob/living/basic/space_fauna/regal_rat/regal_rat_ai.dm +++ b/code/modules/mob/living/basic/space_fauna/regal_rat/regal_rat_ai.dm @@ -1,34 +1,17 @@ /datum/ai_controller/basic_controller/regal_rat + behavior_tree_json = "code/modules/mob/living/basic/space_fauna/regal_rat/regal_rat.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_FLEE_TARGETING_STRATEGY = /datum/targeting_strategy/basic, ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - // we pretty much do cheesy things (make the station worse) and don't deal with peasants (crew) unless they start to get in the way - // summon the horde if we get into a fight and then let the horde take care of it while we skedaddle - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/target_retaliate/to_flee, - /datum/ai_planning_subtree/targeted_mob_ability/riot, - /datum/ai_planning_subtree/flee_target/from_flee_key, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/use_mob_ability/domain, - ) +/// Only activate the domain when it isn't already running. +/datum/bt_node/ai_behavior/use_mob_ability/domain -/datum/ai_planning_subtree/targeted_mob_ability/riot - target_key = BB_BASIC_MOB_FLEE_TARGET // we only want to trigger this when provoked, manpower is low nowadays - ability_key = BB_RAISE_HORDE_ABILITY - finish_planning = FALSE - -/datum/ai_planning_subtree/use_mob_ability/domain - ability_key = BB_DOMAIN_ABILITY - -/datum/ai_planning_subtree/use_mob_ability/domain/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) +/datum/bt_node/ai_behavior/use_mob_ability/domain/perform(seconds_per_tick, datum/ai_controller/controller) var/datum/action/cooldown/mob_cooldown/domain/domain = controller.blackboard[ability_key] - if (!istype(domain) || domain.is_active) - return + if(!istype(domain) || domain.is_active) + return AI_BEHAVIOR_INSTANT | AI_BEHAVIOR_FAILED return ..() diff --git a/code/modules/mob/living/basic/space_fauna/roro.bt.json b/code/modules/mob/living/basic/space_fauna/roro.bt.json new file mode 100644 index 00000000000..cddeb4b8f1d --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/roro.bt.json @@ -0,0 +1,5 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/roro", + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_retaliate_combat" +} diff --git a/code/modules/mob/living/basic/space_fauna/roro.dm b/code/modules/mob/living/basic/space_fauna/roro.dm index b9c4c9e759c..43bdd118140 100644 --- a/code/modules/mob/living/basic/space_fauna/roro.dm +++ b/code/modules/mob/living/basic/space_fauna/roro.dm @@ -55,15 +55,10 @@ AddElement(/datum/element/ai_retaliate) /datum/ai_controller/basic_controller/roro + behavior_tree_json = "code/modules/mob/living/basic/space_fauna/roro.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, ) ai_traits = DEFAULT_AI_FLAGS | STOP_MOVING_WHEN_PULLED ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - - planning_subtrees = list( - /datum/ai_planning_subtree/target_retaliate, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) diff --git a/code/modules/mob/living/basic/space_fauna/snake/banded.bt.json b/code/modules/mob/living/basic/space_fauna/snake/banded.bt.json new file mode 100644 index 00000000000..a05207e90ee --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/snake/banded.bt.json @@ -0,0 +1,92 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/snake/banded", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/move_to_and_eat" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop" + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/find_food" + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/space_fauna/snake/banded_snake.dm b/code/modules/mob/living/basic/space_fauna/snake/banded_snake.dm index f6420803543..811f092251b 100644 --- a/code/modules/mob/living/basic/space_fauna/snake/banded_snake.dm +++ b/code/modules/mob/living/basic/space_fauna/snake/banded_snake.dm @@ -42,20 +42,18 @@ return . /datum/ai_controller/basic_controller/snake/banded + behavior_tree_json = "code/modules/mob/living/basic/space_fauna/snake/banded.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_BASIC_MOB_SPEAK_LINES = list( + BB_EMOTE_SAY = list("hsssss", "sssSSsssss...", "hiisssss"), + BB_EMOTE_HEAR = list("hisses."), + BB_EMOTE_SEE = list("slithers around.", "glances.", "stares."), + BB_EMOTE_SOUND = list('sound/mobs/non-humanoids/snake/snake_hissing1.ogg', 'sound/mobs/non-humanoids/snake/snake_hissing2.ogg'), + BB_SPEAK_CHANCE = 5, + ), ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/find_food, - /datum/ai_planning_subtree/random_speech/snake, - ) - /mob/living/basic/snake/banded/harmless venom_dose = 0.4 diff --git a/code/modules/mob/living/basic/space_fauna/snake/snake.bt.json b/code/modules/mob/living/basic/space_fauna/snake/snake.bt.json new file mode 100644 index 00000000000..6748ce55079 --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/snake/snake.bt.json @@ -0,0 +1,92 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/snake", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/move_to_and_eat" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop" + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/find_food" + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/space_fauna/snake/snake.dm b/code/modules/mob/living/basic/space_fauna/snake/snake.dm index aecfca8062a..985f72a4ef8 100644 --- a/code/modules/mob/living/basic/space_fauna/snake/snake.dm +++ b/code/modules/mob/living/basic/space_fauna/snake/snake.dm @@ -74,18 +74,17 @@ /// Snakes are primarily concerned with getting those tasty, tasty mice, but aren't afraid to strike back at those who attack them /datum/ai_controller/basic_controller/snake + behavior_tree_json = "code/modules/mob/living/basic/space_fauna/snake/snake.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/not_friends, + BB_BASIC_MOB_SPEAK_LINES = list( + BB_EMOTE_SAY = list("hsssss", "sssSSsssss...", "hiisssss"), + BB_EMOTE_HEAR = list("hisses."), + BB_EMOTE_SEE = list("slithers around.", "glances.", "stares."), + BB_EMOTE_SOUND = list('sound/mobs/non-humanoids/snake/snake_hissing1.ogg', 'sound/mobs/non-humanoids/snake/snake_hissing2.ogg'), + BB_SPEAK_CHANCE = 5, + ), ) ai_traits = DEFAULT_AI_FLAGS | STOP_MOVING_WHEN_PULLED ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/target_retaliate, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/find_food, - /datum/ai_planning_subtree/random_speech/snake, - ) diff --git a/code/modules/mob/living/basic/space_fauna/snake/snake_ai.dm b/code/modules/mob/living/basic/space_fauna/snake/snake_ai.dm deleted file mode 100644 index 189a7fb2eb0..00000000000 --- a/code/modules/mob/living/basic/space_fauna/snake/snake_ai.dm +++ /dev/null @@ -1,6 +0,0 @@ -/datum/ai_planning_subtree/random_speech/snake - speech_chance = 5 - speak = list("hsssss","sssSSsssss...","hiisssss") - sound = list('sound/mobs/non-humanoids/snake/snake_hissing1.ogg', 'sound/mobs/non-humanoids/snake/snake_hissing2.ogg') - emote_hear = list("hisses.") - emote_see = list("slithers around.", "glances.", "stares.") diff --git a/code/modules/mob/living/basic/space_fauna/spaceman.bt.json b/code/modules/mob/living/basic/space_fauna/spaceman.bt.json new file mode 100644 index 00000000000..c198789ac24 --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/spaceman.bt.json @@ -0,0 +1,14 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/spaceman", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity/pacifist" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_retaliate_combat" + } + ] +} diff --git a/code/modules/mob/living/basic/space_fauna/spaceman.dm b/code/modules/mob/living/basic/space_fauna/spaceman.dm index ecb413bb81d..6453a15719d 100644 --- a/code/modules/mob/living/basic/space_fauna/spaceman.dm +++ b/code/modules/mob/living/basic/space_fauna/spaceman.dm @@ -31,15 +31,9 @@ AddElement(/datum/element/ai_retaliate) /datum/ai_controller/basic_controller/spaceman + behavior_tree_json = "code/modules/mob/living/basic/space_fauna/spaceman.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity/pacifist, - /datum/ai_planning_subtree/target_retaliate, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) diff --git a/code/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider.bt.json b/code/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider.bt.json new file mode 100644 index 00000000000..b8697161b74 --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider.bt.json @@ -0,0 +1,133 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/giant_spider", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/attack_obstructions", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_SPIDER_WEB_TARGET" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_SPIDER_WEB_TARGET", + "required_dist": 0 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/spin_web", + "vars": { + "action_key": "BB_SPIDER_WEB_ACTION", + "target_key": "BB_SPIDER_WEB_TARGET" + } + } + ] + } + } + ] + }, + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/find_unwebbed_turf", + "vars": { + "target_key": "BB_SPIDER_WEB_TARGET" + } + } + ] + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop" + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider_ai.dm b/code/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider_ai.dm index ae11657338b..02fb234525e 100644 --- a/code/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider_ai.dm +++ b/code/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider_ai.dm @@ -1,63 +1,35 @@ /// Attacks people it can see, spins webs if it can't see anything to attack. /datum/ai_controller/basic_controller/giant_spider + behavior_tree_json = "code/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_BASIC_MOB_SPEAK_LINES = list( + BB_EMOTE_HEAR = list("chitters."), // Space spiders are taxonomically insects not arachnids, don't DM me + BB_EMOTE_SOUND = list('sound/mobs/non-humanoids/insect/chitter.ogg'), + BB_SPEAK_CHANCE = 5, + ), ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk/less_walking - - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/random_speech/insect, // Space spiders are taxonomically insects not arachnids, don't DM me - /datum/ai_planning_subtree/find_unwebbed_turf, - /datum/ai_planning_subtree/spin_web, - ) /// Giant spider which won't attack structures /datum/ai_controller/basic_controller/giant_spider/weak - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity/pacifist, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/random_speech/insect, - /datum/ai_planning_subtree/find_unwebbed_turf, - /datum/ai_planning_subtree/spin_web, - ) + behavior_tree_json = "code/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider_weak.bt.json" /// Used by Araneus, who only attacks those who attack first. He is house-trained and will not web up the HoS office. /datum/ai_controller/basic_controller/giant_spider/retaliate - blackboard = list( - BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, - ) - - planning_subtrees = list( - /datum/ai_planning_subtree/target_retaliate, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/random_speech/insect, - ) + behavior_tree_json = "code/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider_retaliate.bt.json" /// Retaliates, hunts other maintenance creatures, runs away from larger attackers, and spins webs. /datum/ai_controller/basic_controller/giant_spider/pest + behavior_tree_json = "code/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider_pest.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/of_size/ours_or_smaller, // Hunt mobs our size BB_PET_TARGETING_STRATEGY = /datum/targeting_strategy/basic/not_friends, BB_FLEE_TARGETING_STRATEGY = /datum/targeting_strategy/basic/of_size/larger, // Run away from mobs bigger than we are - ) - idle_behavior = /datum/idle_behavior/idle_random_walk - - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity/pacifist, - /datum/ai_planning_subtree/pet_planning, - /datum/ai_planning_subtree/target_retaliate/to_flee, - /datum/ai_planning_subtree/flee_target/from_flee_key, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/random_speech/insect, - /datum/ai_planning_subtree/find_unwebbed_turf, - /datum/ai_planning_subtree/spin_web, + BB_BASIC_MOB_SPEAK_LINES = list( + BB_EMOTE_HEAR = list("chitters."), + BB_EMOTE_SOUND = list('sound/mobs/non-humanoids/insect/chitter.ogg'), + BB_SPEAK_CHANCE = 5, + ), ) diff --git a/code/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider_pest.bt.json b/code/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider_pest.bt.json new file mode 100644 index 00000000000..5e856565757 --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider_pest.bt.json @@ -0,0 +1,150 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/giant_spider/pest", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity/pacifist" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree", + "override_id": "SUBPLAN_ID_PET_COMMAND" + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "key": "BB_BASIC_MOB_FLEE_TARGET" + }, + "child": { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/run_away_from_target", + "bindings": { + "byk9gqj4": "BB_BASIC_MOB_FLEE_TARGET" + } + } + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_SPIDER_WEB_TARGET" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_SPIDER_WEB_TARGET", + "required_dist": 0 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/spin_web", + "vars": { + "action_key": "BB_SPIDER_WEB_ACTION", + "target_key": "BB_SPIDER_WEB_TARGET" + } + } + ] + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list", + "vars": { + "target_key": "BB_BASIC_MOB_FLEE_TARGET", + "targeting_strategy": "BB_FLEE_TARGETING_STRATEGY", + "hiding_location_key": "BB_BASIC_MOB_FLEE_TARGET_HIDING_LOCATION" + } + }, + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/find_unwebbed_turf", + "vars": { + "target_key": "BB_SPIDER_WEB_TARGET" + } + } + ] + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop" + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider_retaliate.bt.json b/code/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider_retaliate.bt.json new file mode 100644 index 00000000000..970784d7bf1 --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider_retaliate.bt.json @@ -0,0 +1,78 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/giant_spider/retaliate", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/attack_obstructions", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list/nearest", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop" + } + ] +} diff --git a/code/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider_subtrees.dm b/code/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider_subtrees.dm deleted file mode 100644 index 20f6ce4baf0..00000000000 --- a/code/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider_subtrees.dm +++ /dev/null @@ -1,89 +0,0 @@ -/// Search for a nearby location to put webs on -/datum/ai_planning_subtree/find_unwebbed_turf - -/datum/ai_planning_subtree/find_unwebbed_turf/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - controller.queue_behavior(/datum/ai_behavior/find_unwebbed_turf) - -/// Find an unwebbed nearby turf and store it -/datum/ai_behavior/find_unwebbed_turf - action_cooldown = 5 SECONDS - /// Where do we store the target data - var/target_key = BB_SPIDER_WEB_TARGET - /// How far do we look for unwebbed turfs? - var/scan_range = 3 - -/datum/ai_behavior/find_unwebbed_turf/perform(seconds_per_tick, datum/ai_controller/controller) - var/mob/living/spider = controller.pawn - var/atom/current_target = controller.blackboard[target_key] - if (current_target && !(locate(/obj/structure/spider/stickyweb) in current_target)) - // Already got a target - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - controller.clear_blackboard_key(target_key) - var/turf/our_turf = get_turf(spider) - if (is_valid_web_turf(our_turf, spider)) - controller.set_blackboard_key(target_key, our_turf) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - - var/list/turfs_by_range = list() - for (var/i in 1 to scan_range) - turfs_by_range["[i]"] = list() - for (var/turf/turf_in_view in oview(scan_range, our_turf)) - if (!is_valid_web_turf(turf_in_view, spider)) - continue - turfs_by_range["[get_dist(our_turf, turf_in_view)]"] += turf_in_view - - var/list/final_turfs - for (var/list/turf_list as anything in turfs_by_range) - if (length(turfs_by_range[turf_list])) - final_turfs = turfs_by_range[turf_list] - break - if (!length(final_turfs)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - - controller.set_blackboard_key(target_key, pick(final_turfs)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -/datum/ai_behavior/find_unwebbed_turf/proc/is_valid_web_turf(turf/target_turf, mob/living/spider) - if (locate(/obj/structure/spider/stickyweb) in target_turf) - return FALSE - if (HAS_TRAIT(target_turf, TRAIT_SPINNING_WEB_TURF)) - return FALSE - return !target_turf.is_blocked_turf(source_atom = spider) - -/// Run the spin web behaviour if we have an ability to use for it -/datum/ai_planning_subtree/spin_web - /// Key where the web spinning action is stored - var/action_key = BB_SPIDER_WEB_ACTION - /// Key where the target turf is stored - var/target_key = BB_SPIDER_WEB_TARGET - -/datum/ai_planning_subtree/spin_web/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if (controller.blackboard_key_exists(action_key) && controller.blackboard_key_exists(target_key)) - controller.queue_behavior(/datum/ai_behavior/spin_web, action_key, target_key) - return SUBTREE_RETURN_FINISH_PLANNING - -/// Move to an unwebbed nearby turf and web it up -/datum/ai_behavior/spin_web - action_cooldown = 15 SECONDS // We don't want them doing this too quickly - required_distance = 0 - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION - -/datum/ai_behavior/spin_web/setup(datum/ai_controller/controller, action_key, target_key) - var/datum/action/cooldown/web_action = controller.blackboard[action_key] - var/turf/target_turf = controller.blackboard[target_key] - if (!web_action || !target_turf) - return FALSE - - set_movement_target(controller, target_turf) - return ..() - -/datum/ai_behavior/spin_web/perform(seconds_per_tick, datum/ai_controller/controller, action_key, target_key) - var/datum/action/cooldown/web_action = controller.blackboard[action_key] - if(web_action?.Trigger()) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - -/datum/ai_behavior/spin_web/finish_action(datum/ai_controller/controller, succeeded, action_key, target_key) - controller.clear_blackboard_key(target_key) - return ..() diff --git a/code/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider_weak.bt.json b/code/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider_weak.bt.json new file mode 100644 index 00000000000..42b2f434962 --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/spider/giant_spider/giant_spider_weak.bt.json @@ -0,0 +1,121 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/giant_spider/weak", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity/pacifist" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_SPIDER_WEB_TARGET" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_SPIDER_WEB_TARGET", + "required_dist": 0 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/spin_web", + "vars": { + "action_key": "BB_SPIDER_WEB_ACTION", + "target_key": "BB_SPIDER_WEB_TARGET" + } + } + ] + } + } + ] + }, + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/find_unwebbed_turf", + "vars": { + "target_key": "BB_SPIDER_WEB_TARGET" + } + } + ] + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop" + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/space_fauna/spider/spiderlings/spiderling.bt.json b/code/modules/mob/living/basic/space_fauna/spider/spiderlings/spiderling.bt.json new file mode 100644 index 00000000000..0d0f9795a7c --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/spider/spiderlings/spiderling.bt.json @@ -0,0 +1,69 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/spiderling", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity/pacifist" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/consider_venting" + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "invert": false, + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/run_away_from_target" + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop" + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_ENTRY_VENT_TARGET", + "target_source": "/datum/target_source/oview_single_type/vent_pump", + "targeting_strategy": "/datum/targeting_strategy/anything", + "vision_range": 7 + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/space_fauna/spider/spiderlings/spiderling.dm b/code/modules/mob/living/basic/space_fauna/spider/spiderlings/spiderling.dm index e7120ba34cd..130d22134c1 100644 --- a/code/modules/mob/living/basic/space_fauna/spider/spiderlings/spiderling.dm +++ b/code/modules/mob/living/basic/space_fauna/spider/spiderlings/spiderling.dm @@ -60,21 +60,17 @@ /// Opportunistically hops in and out of vents, if it can find one. We aren't interested in attacking due to how weak we are, we gotta be quick and hidey. /datum/ai_controller/basic_controller/spiderling + behavior_tree_json = "code/modules/mob/living/basic/space_fauna/spider/spiderlings/spiderling.bt.json" blackboard = list( - BB_FLEE_TARGETING_STRATEGY = /datum/targeting_strategy/basic/of_size/larger, // Run away from mobs bigger than we are + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/of_size/larger, // Run away from mobs bigger than we are BB_VENTCRAWL_COOLDOWN = 20 SECONDS, // enough time to get splatted while we're out in the open. BB_TIME_TO_GIVE_UP_ON_VENT_PATHING = 30 SECONDS, + BB_BASIC_MOB_SPEAK_LINES = list( + BB_EMOTE_HEAR = list("chitters."), + BB_EMOTE_SOUND = list('sound/mobs/non-humanoids/insect/chitter.ogg'), + BB_SPEAK_CHANCE = 5, + ), ) ai_traits = PASSIVE_AI_FLAGS ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk/less_walking - - // We understand that vents are nice little hidey holes through epigenetic inheritance, so we'll use them. - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity/pacifist, - /datum/ai_planning_subtree/target_retaliate/to_flee, - /datum/ai_planning_subtree/flee_target/from_flee_key, - /datum/ai_planning_subtree/opportunistic_ventcrawler, - /datum/ai_planning_subtree/random_speech/insect, - ) diff --git a/code/modules/mob/living/basic/space_fauna/spider/young_spider/young_spider.bt.json b/code/modules/mob/living/basic/space_fauna/spider/young_spider/young_spider.bt.json new file mode 100644 index 00000000000..f042cc5d946 --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/spider/young_spider/young_spider.bt.json @@ -0,0 +1,149 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/young_spider", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity/pacifist" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "selector", + "children": [ + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/attack_obstructions", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + } + ] + } + ] + }, + { + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/run_away_from_target", + "bindings": { + "byk9gqj4": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1 + } + } + ] + } + ] + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_SPIDER_WEB_TARGET" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_SPIDER_WEB_TARGET", + "required_dist": 0 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/spin_web", + "vars": { + "action_key": "BB_SPIDER_WEB_ACTION", + "target_key": "BB_SPIDER_WEB_TARGET" + } + } + ] + } + } + ] + }, + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list/nearest", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/find_unwebbed_turf", + "vars": { + "target_key": "BB_SPIDER_WEB_TARGET" + } + } + ] + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop" + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/space_fauna/spider/young_spider/young_spider.dm b/code/modules/mob/living/basic/space_fauna/spider/young_spider/young_spider.dm index 436602b71ba..a739ef2e9ec 100644 --- a/code/modules/mob/living/basic/space_fauna/spider/young_spider/young_spider.dm +++ b/code/modules/mob/living/basic/space_fauna/spider/young_spider/young_spider.dm @@ -28,26 +28,19 @@ /// Used by all young spiders if they ever appear. /datum/ai_controller/basic_controller/young_spider + behavior_tree_json = "code/modules/mob/living/basic/space_fauna/spider/young_spider/young_spider.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_BASIC_MOB_FLEE_DISTANCE = 6, + BB_BASIC_MOB_SPEAK_LINES = list( + BB_EMOTE_HEAR = list("chitters."), + BB_EMOTE_SOUND = list('sound/mobs/non-humanoids/insect/chitter.ogg'), + BB_SPEAK_CHANCE = 5, + ), ) ai_traits = PASSIVE_AI_FLAGS ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity/pacifist, - /datum/ai_planning_subtree/find_nearest_thing_which_attacked_me_to_flee, - /datum/ai_planning_subtree/flee_target, - /datum/ai_planning_subtree/target_retaliate, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/random_speech/insect, - /datum/ai_planning_subtree/find_unwebbed_turf, - /datum/ai_planning_subtree/spin_web, - ) /mob/living/basic/spider/growing/young/start_pulling(atom/movable/pulled_atom, state, force = move_force, supress_message = FALSE) // we're TOO FUCKING WEAK return diff --git a/code/modules/mob/living/basic/space_fauna/statue/mannequin.dm b/code/modules/mob/living/basic/space_fauna/statue/mannequin.dm index 03067edbc45..967cd492265 100644 --- a/code/modules/mob/living/basic/space_fauna/statue/mannequin.dm +++ b/code/modules/mob/living/basic/space_fauna/statue/mannequin.dm @@ -30,42 +30,13 @@ . += mutable_appearance(hat::worn_icon, hat::worn_icon_state || hat::post_init_icon_state || hat::icon_state) /datum/ai_controller/basic_controller/stares_at_people + behavior_tree_json = "code/modules/mob/living/basic/space_fauna/statue/stares_at_people.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_AGGRO_RANGE = 6, ) ai_movement = /datum/ai_movement/dumb - idle_behavior = null - planning_subtrees = list( - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/face_target_or_face_initial, // we be creepy and all - ) - -/datum/ai_planning_subtree/face_target_or_face_initial - -/datum/ai_planning_subtree/face_target_or_face_initial/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(isnull(controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET])) - return - var/mob/living/we = controller.pawn - controller.blackboard[BB_STARTING_DIRECTION] = we.dir - controller.queue_behavior(/datum/ai_behavior/face_target_or_face_initial, BB_BASIC_MOB_CURRENT_TARGET) - -/datum/ai_behavior/face_target_or_face_initial - -/datum/ai_behavior/face_target_or_face_initial/setup(datum/ai_controller/controller, target_key) - . = ..() - var/atom/movable/target = controller.blackboard[target_key] - return ismovable(target) && isturf(target.loc) && ismob(controller.pawn) - -/datum/ai_behavior/face_target_or_face_initial/perform(seconds_per_tick, datum/ai_controller/controller, target_key) - var/atom/movable/target = controller.blackboard[target_key] - var/mob/living/we = controller.pawn - if(isnull(target) || get_dist(we, target) > 8) - we.dir = controller.blackboard[BB_STARTING_DIRECTION] - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - we.face_atom(target) - return AI_BEHAVIOR_DELAY /mob/living/basic/statue/mannequin/suspicious name = "mannequin?" @@ -75,17 +46,9 @@ ai_controller = /datum/ai_controller/basic_controller/suspicious_mannequin /datum/ai_controller/basic_controller/suspicious_mannequin + behavior_tree_json = "code/modules/mob/living/basic/space_fauna/statue/suspicious_mannequin.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, - BB_AGGRO_RANGE = 14, - BB_EMOTE_KEY = "scream", //spooky ) - ai_movement = /datum/ai_movement/jps //threat - idle_behavior = null - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity/pacifist, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/run_emote, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) + ai_movement = /datum/ai_movement/basic_avoidance diff --git a/code/modules/mob/living/basic/space_fauna/statue/stares_at_people.bt.json b/code/modules/mob/living/basic/space_fauna/statue/stares_at_people.bt.json new file mode 100644 index 00000000000..b77a35e0108 --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/statue/stares_at_people.bt.json @@ -0,0 +1,44 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/stares_at_people", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/face_target_or_face_initial", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/wait" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] +} diff --git a/code/modules/mob/living/basic/space_fauna/statue/statue.bt.json b/code/modules/mob/living/basic/space_fauna/statue/statue.bt.json new file mode 100644 index 00000000000..b999f3c6eed --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/statue/statue.bt.json @@ -0,0 +1,125 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/statue", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_LOW_PRIORITY_HUNTING_TARGET" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_LOW_PRIORITY_HUNTING_TARGET", + "movement_failed": false + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/hunt_target/interact_with_target", + "vars": { + "target_key": "BB_LOW_PRIORITY_HUNTING_TARGET", + "hunt_cooldown": "10 SECONDS", + "always_reset_target": true + } + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "invert": true, + "key": "BB_LOW_PRIORITY_HUNTING_TARGET" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_LOW_PRIORITY_HUNTING_TARGET", + "target_source": "/datum/target_source/oview_typed/from_bb_key/hunt_target_list", + "targeting_strategy": "/datum/targeting_strategy/unbroken_light", + "vision_range": 7 + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/space_fauna/statue/statue.dm b/code/modules/mob/living/basic/space_fauna/statue/statue.dm index b4bb0467363..bb62acade2b 100644 --- a/code/modules/mob/living/basic/space_fauna/statue/statue.dm +++ b/code/modules/mob/living/basic/space_fauna/statue/statue.dm @@ -64,6 +64,7 @@ /datum/action/cooldown/spell/aoe/flicker_lights, ) grant_actions_by_list(innate_actions) + ai_controller.set_blackboard_key(BB_HUNT_TARGET_LIST, typecacheof(list(/obj/machinery/light))) /mob/living/basic/statue/med_hud_set_health() return //we're a statue we're invincible @@ -130,17 +131,12 @@ victim.adjust_temp_blindness(8 SECONDS) /datum/ai_controller/basic_controller/statue + behavior_tree_json = "code/modules/mob/living/basic/space_fauna/statue/statue.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, ) - ai_movement = /datum/ai_movement/basic_avoidance - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/find_and_hunt_target/look_for_light_fixtures, - ) + ai_movement = /datum/ai_movement/jps /mob/living/basic/statue/frosty name = "Frosty" diff --git a/code/modules/mob/living/basic/space_fauna/statue/suspicious_mannequin.bt.json b/code/modules/mob/living/basic/space_fauna/statue/suspicious_mannequin.bt.json new file mode 100644 index 00000000000..f9143d9986c --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/statue/suspicious_mannequin.bt.json @@ -0,0 +1,114 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/suspicious_mannequin", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity/pacifist" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/wait" + }, + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + }, + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/wait" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION", + "vision_range": 14 + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_NONE", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/random_chance", + "vars": { + "chance": 0.05 + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/perform_emote", + "vars": { + "emote": "\"scream\"" + } + } + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/space_fauna/supermatter_spider.bt.json b/code/modules/mob/living/basic/space_fauna/supermatter_spider.bt.json new file mode 100644 index 00000000000..49c3e9f075f --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/supermatter_spider.bt.json @@ -0,0 +1,8 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/supermatter_spider", + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_hostile_obstacles_combat", + "bindings": { + "b95z0f0c": "/datum/bt_node/subtree/random_speech_loop" + } +} diff --git a/code/modules/mob/living/basic/space_fauna/supermatter_spider.dm b/code/modules/mob/living/basic/space_fauna/supermatter_spider.dm index 44d7585faff..fdc0194999c 100644 --- a/code/modules/mob/living/basic/space_fauna/supermatter_spider.dm +++ b/code/modules/mob/living/basic/space_fauna/supermatter_spider.dm @@ -83,22 +83,14 @@ single_use = FALSE /datum/ai_controller/basic_controller/supermatter_spider + behavior_tree_json = "code/modules/mob/living/basic/space_fauna/supermatter_spider.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_BASIC_MOB_SPEAK_LINES = list( + BB_EMOTE_HEAR = list("clinks.", "clanks."), + BB_EMOTE_SEE = list("vibrates."), + BB_SPEAK_CHANCE = 7, + ), ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - - planning_subtrees = list( - /datum/ai_planning_subtree/target_retaliate, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/random_speech/supermatter_spider, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) - -/datum/ai_planning_subtree/random_speech/supermatter_spider - speech_chance = 7 - emote_hear = list("clinks", "clanks") - emote_see = list("vibrates") diff --git a/code/modules/mob/living/basic/space_fauna/wumborian_fugu/inflation.dm b/code/modules/mob/living/basic/space_fauna/wumborian_fugu/inflation.dm index 651cd848458..288333add5e 100644 --- a/code/modules/mob/living/basic/space_fauna/wumborian_fugu/inflation.dm +++ b/code/modules/mob/living/basic/space_fauna/wumborian_fugu/inflation.dm @@ -66,7 +66,7 @@ fugu.melee_damage_upper = 20 fugu.obj_damage = 60 fugu.ai_controller.set_blackboard_key(BB_BASIC_MOB_STOP_FLEEING, TRUE) - fugu.ai_controller.CancelActions() + fugu.ai_controller.cancel_current_plan() /datum/status_effect/inflated/on_remove() . = ..() @@ -84,7 +84,7 @@ fugu.icon_state = "Fugu0" fugu.obj_damage = 0 fugu.ai_controller.set_blackboard_key(BB_BASIC_MOB_STOP_FLEEING, FALSE) - fugu.ai_controller.CancelActions() + fugu.ai_controller.cancel_current_plan() /// Remove status effect if we die /datum/status_effect/inflated/proc/check_death(mob/living/source, new_stat) diff --git a/code/modules/mob/living/basic/space_fauna/wumborian_fugu/wumborian_ai.dm b/code/modules/mob/living/basic/space_fauna/wumborian_fugu/wumborian_ai.dm index 9664ad5d96f..ab7deaab7d7 100644 --- a/code/modules/mob/living/basic/space_fauna/wumborian_fugu/wumborian_ai.dm +++ b/code/modules/mob/living/basic/space_fauna/wumborian_fugu/wumborian_ai.dm @@ -1,27 +1,6 @@ /// Cowardly when small, aggressive when big. Tries to transform whenever possible. /datum/ai_controller/basic_controller/wumborian_fugu + behavior_tree_json = "code/modules/mob/living/basic/space_fauna/wumborian_fugu/wumborian_fugu.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, ) - - ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/targeted_mob_ability/inflate, - /datum/ai_planning_subtree/flee_target, - /datum/ai_planning_subtree/attack_obstacle_in_path/wumborian_fugu, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) - -/datum/ai_planning_subtree/attack_obstacle_in_path/wumborian_fugu - attack_behaviour = /datum/ai_behavior/attack_obstructions/wumborian_fugu - -/datum/ai_behavior/attack_obstructions/wumborian_fugu - can_attack_turfs = TRUE - action_cooldown = 2.5 SECONDS - -/datum/ai_planning_subtree/targeted_mob_ability/inflate - ability_key = BB_FUGU_INFLATE diff --git a/code/modules/mob/living/basic/space_fauna/wumborian_fugu/wumborian_fugu.bt.json b/code/modules/mob/living/basic/space_fauna/wumborian_fugu/wumborian_fugu.bt.json new file mode 100644 index 00000000000..bcff1218080 --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/wumborian_fugu/wumborian_fugu.bt.json @@ -0,0 +1,86 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/wumborian_fugu", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/targeted_mob_ability", + "vars": { + "ability_key": "BB_FUGU_INFLATE", + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/attack_obstructions/attack_turfs", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + } + ] + } + ] + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/run_away_from_target" + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/stoats/stoat.bt.json b/code/modules/mob/living/basic/stoats/stoat.bt.json new file mode 100644 index 00000000000..9c8d273c743 --- /dev/null +++ b/code/modules/mob/living/basic/stoats/stoat.bt.json @@ -0,0 +1,123 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/stoat", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree", + "override_id": "SUBPLAN_ID_PET_COMMAND" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CURRENT_TARGET", + "observer_abort": "BT_ABORT_SELF" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_SUCCEED_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/move_to_and_eat" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/make_babies" + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/is_dragging", + "vars": { + "observer_abort": "BT_ABORT_SELF" + }, + "child": { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/run_away_from_target", + "bindings": { + "byk9gqj4": "BB_LAST_STOLEN_ITEM" + } + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/steal_and_flee" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/find_stealable_object" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/find_food" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/find_partner" + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/stoats/stoat_ai.dm b/code/modules/mob/living/basic/stoats/stoat_ai.dm index ecefb0fd93a..181baf573dc 100644 --- a/code/modules/mob/living/basic/stoats/stoat_ai.dm +++ b/code/modules/mob/living/basic/stoats/stoat_ai.dm @@ -1,23 +1,15 @@ /datum/ai_controller/basic_controller/stoat + behavior_tree_json = "code/modules/mob/living/basic/stoats/stoat.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/of_size/smaller, BB_PET_TARGETING_STRATEGY = /datum/targeting_strategy/basic/not_friends, BB_GUILTY_CONSCIOUS_CHANCE = 5, - BB_STEAL_CHANCE = 2, + BB_STEAL_CHANCE = 25, BB_BABIES_PARTNER_TYPES = list(/mob/living/basic/stoat), BB_BABIES_CHILD_TYPES = list(/mob/living/basic/stoat/kit), + BB_FUCKS = TRUE ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/pet_planning, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/random_speech/blackboard, - /datum/ai_planning_subtree/steal_items, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/find_food, - /datum/ai_planning_subtree/make_babies, - ) /datum/ai_controller/basic_controller/stoat/kit blackboard = list( @@ -25,14 +17,6 @@ BB_PET_TARGETING_STRATEGY = /datum/targeting_strategy/basic/not_friends, BB_GUILTY_CONSCIOUS_CHANCE = 5, BB_STEAL_CHANCE = 2, + BB_FUCKS = FALSE ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/pet_planning, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/random_speech/blackboard, - /datum/ai_planning_subtree/steal_items, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/find_food, - ) diff --git a/code/modules/mob/living/basic/trader/trader.bt.json b/code/modules/mob/living/basic/trader/trader.bt.json new file mode 100644 index 00000000000..771bcabbaf2 --- /dev/null +++ b/code/modules/mob/living/basic/trader/trader.bt.json @@ -0,0 +1,154 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/trader", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity/pacifist" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_ranged_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION", + "avoid_friendly_fire": true, + "time_between_perform": "3 SECONDS" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_NONE", + "invert": true, + "key": "BB_SHOP_SPOT" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "key": "BB_FIRST_CUSTOMER" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_equals", + "vars": { + "key": "BB_TRADER_RUSH_TO_SELL", + "value": true + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_FIRST_CUSTOMER", + "required_dist": 1 + } + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/succeed" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/setup_shop" + } + ] + } + } + }, + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_FAIL_ON_FAILURE", + "loop_delay": "IDLE_BEHAVIOR_RATE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/idle_random_walk/not_while_on_target", + "vars": { + "target_key": "BB_SHOP_SPOT" + } + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target", + "vars": { + "target_key": "BB_FIRST_CUSTOMER", + "target_source": "/datum/target_source/oview", + "targeting_strategy": "/datum/targeting_strategy/conscious_human", + "revalidation_mode": "TARGET_REVALIDATE" + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/trader/trader.dm b/code/modules/mob/living/basic/trader/trader.dm index c23726a562b..72515931df1 100644 --- a/code/modules/mob/living/basic/trader/trader.dm +++ b/code/modules/mob/living/basic/trader/trader.dm @@ -49,7 +49,7 @@ AddComponent(/datum/component/trader, trader_data = trader_data) AddComponent(/datum/component/ranged_attacks, casing_type = ranged_attack_casing, projectile_sound = ranged_attack_sound, cooldown_time = 3 SECONDS) AddElement(/datum/element/ai_retaliate) - AddElement(/datum/element/ai_swap_combat_mode, BB_BASIC_MOB_CURRENT_TARGET, string_list(trader_data.say_phrases[TRADER_BATTLE_START_PHRASE]), string_list(trader_data.say_phrases[TRADER_BATTLE_END_PHRASE])) + AddElement(/datum/element/ai_swap_combat_mode, BB_CURRENT_TARGET, string_list(trader_data.say_phrases[TRADER_BATTLE_START_PHRASE]), string_list(trader_data.say_phrases[TRADER_BATTLE_END_PHRASE])) if(LAZYLEN(loot)) loot = string_list(loot) AddElement(/datum/element/death_drops, loot) diff --git a/code/modules/mob/living/basic/trader/trader_ai.dm b/code/modules/mob/living/basic/trader/trader_ai.dm index f26ec242ed1..68700498ccc 100644 --- a/code/modules/mob/living/basic/trader/trader_ai.dm +++ b/code/modules/mob/living/basic/trader/trader_ai.dm @@ -1,93 +1,25 @@ /datum/ai_controller/basic_controller/trader + behavior_tree_json = "code/modules/mob/living/basic/trader/trader.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_TRADER_RUSH_TO_SELL = FALSE ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk/not_while_on_target/trader - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity/pacifist, - /datum/ai_planning_subtree/target_retaliate, - /datum/ai_planning_subtree/basic_ranged_attack_subtree/trader, - /datum/ai_planning_subtree/prepare_travel_to_destination/trader, - /datum/ai_planning_subtree/travel_to_point/and_clear_target, - /datum/ai_planning_subtree/setup_shop, - ) /datum/ai_controller/basic_controller/trader/jumpscare - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity/pacifist, - /datum/ai_planning_subtree/target_retaliate, - /datum/ai_planning_subtree/basic_ranged_attack_subtree/trader, - /datum/ai_planning_subtree/prepare_travel_to_destination/trader, - /datum/ai_planning_subtree/travel_to_point/and_clear_target, - /datum/ai_planning_subtree/setup_shop/jumpscare, + blackboard = list( + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_TRADER_RUSH_TO_SELL = TRUE ) -/datum/ai_planning_subtree/basic_ranged_attack_subtree/trader - ranged_attack_behavior = /datum/ai_behavior/basic_ranged_attack/trader - -/datum/ai_behavior/basic_ranged_attack/trader - action_cooldown = 3 SECONDS - avoid_friendly_fire = TRUE - -///Subtree to find our very first customer and set up our shop after walking right into their face -/datum/ai_planning_subtree/setup_shop - ///What do we do in order to offer our deals? - var/datum/ai_behavior/setup_shop/setup_shop_behavior = /datum/ai_behavior/setup_shop - -/datum/ai_planning_subtree/setup_shop/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - - //If we don't have our ability, return - if(!controller.blackboard_key_exists(BB_SETUP_SHOP)) - return - - //If we already have a shop spot, return - if(controller.blackboard_key_exists(BB_SHOP_SPOT)) - return - - //If we don't have a costurmer to greet, look for one - if(!controller.blackboard_key_exists(BB_FIRST_CUSTOMER)) - controller.queue_behavior(/datum/ai_behavior/find_and_set/conscious_person, BB_FIRST_CUSTOMER, /mob/living/carbon/human) - return - - //We have our first customer, time to tell them about incredible deals - controller.queue_behavior(setup_shop_behavior, BB_FIRST_CUSTOMER) - return SUBTREE_RETURN_FINISH_PLANNING - -///The ai will create a shop the moment they see a potential costumer -/datum/ai_behavior/setup_shop - -/datum/ai_behavior/setup_shop/setup(datum/ai_controller/controller, target_key) - var/obj/target = controller.blackboard[target_key] - return !QDELETED(target) - -/datum/ai_behavior/setup_shop/perform(seconds_per_tick, datum/ai_controller/controller, target_key) - //We lost track of our costumer or our ability, abort - if(!controller.blackboard_key_exists(target_key) || !controller.blackboard_key_exists(BB_SETUP_SHOP)) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED +///Triggers the shop setup action and clears the first customer key +/datum/bt_node/ai_behavior/setup_shop +/datum/bt_node/ai_behavior/setup_shop/perform(seconds_per_tick, datum/ai_controller/controller) var/datum/action/setup_shop/shop = controller.blackboard[BB_SETUP_SHOP] + if(!shop || !controller.blackboard_key_exists(BB_FIRST_CUSTOMER)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED shop.Trigger() - controller.clear_blackboard_key(BB_FIRST_CUSTOMER) return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -/datum/idle_behavior/idle_random_walk/not_while_on_target/trader - target_key = BB_SHOP_SPOT - -///Version of setup show where the trader will run at you to assault you with incredible deals -/datum/ai_planning_subtree/setup_shop/jumpscare - setup_shop_behavior = /datum/ai_behavior/setup_shop/jumpscare - -/datum/ai_behavior/setup_shop/jumpscare - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH - -/datum/ai_behavior/setup_shop/jumpscare/setup(datum/ai_controller/controller, target_key) - . = ..() - if(.) - set_movement_target(controller, controller.blackboard[target_key]) - -/datum/ai_behavior/setup_shop/finish_action(datum/ai_controller/controller, succeeded, target_key) - . = ..() - controller.clear_blackboard_key(target_key) diff --git a/code/modules/mob/living/basic/tree.bt.json b/code/modules/mob/living/basic/tree.bt.json new file mode 100644 index 00000000000..ea3f7573b11 --- /dev/null +++ b/code/modules/mob/living/basic/tree.bt.json @@ -0,0 +1,8 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/tree", + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_hostile_obstacles_combat", + "bindings": { + "b95z0f0c": "/datum/bt_node/subtree/random_speech_loop" + } +} diff --git a/code/modules/mob/living/basic/tree.dm b/code/modules/mob/living/basic/tree.dm index 5cf9c1bf27a..c9714103a8c 100644 --- a/code/modules/mob/living/basic/tree.dm +++ b/code/modules/mob/living/basic/tree.dm @@ -100,15 +100,13 @@ ) /datum/ai_controller/basic_controller/tree + behavior_tree_json = "code/modules/mob/living/basic/tree.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_BASIC_MOB_SPEAK_LINES = list( + BB_EMOTE_SEE = list("photosynthesizes angrily."), + BB_SPEAK_CHANCE = 3, + ), ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk/less_walking - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/random_speech/tree, - ) diff --git a/code/modules/mob/living/basic/trooper/burst.bt.json b/code/modules/mob/living/basic/trooper/burst.bt.json new file mode 100644 index 00000000000..665988d1dc7 --- /dev/null +++ b/code/modules/mob/living/basic/trooper/burst.bt.json @@ -0,0 +1,9 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/trooper/ranged/burst", + "type": "subtree", + "subtype": "/datum/bt_node/subtree/trooper_ranged", + "bindings": { + "b4kar3zl": "3 SECONDS", + "bnr1aazo": "TRUE" + } +} diff --git a/code/modules/mob/living/basic/trooper/nanotrasen.dm b/code/modules/mob/living/basic/trooper/nanotrasen.dm index 8798467969b..a2494a5aad4 100644 --- a/code/modules/mob/living/basic/trooper/nanotrasen.dm +++ b/code/modules/mob/living/basic/trooper/nanotrasen.dm @@ -87,7 +87,7 @@ /mob/living/basic/trooper/nanotrasen/ranged/smg/peaceful desc = "An officer of Nanotrasen's private security force." - ai_controller = /datum/ai_controller/basic_controller/trooper/ranged/burst/peaceful + ai_controller = /datum/ai_controller/basic_controller/trooper/ranged/burst/peaceful_burst /mob/living/basic/trooper/nanotrasen/ranged/smg/peaceful/Initialize(mapload) . = ..() diff --git a/code/modules/mob/living/basic/trooper/peaceful.bt.json b/code/modules/mob/living/basic/trooper/peaceful.bt.json new file mode 100644 index 00000000000..17262a5b37a --- /dev/null +++ b/code/modules/mob/living/basic/trooper/peaceful.bt.json @@ -0,0 +1,138 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/trooper/peaceful", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "key": "BB_CALL_REINFORCEMENTS_TARGET" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_equals", + "vars": { + "key": "BB_CALLS_REINFORCEMENTS", + "value": true + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/cooldown", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "cooldown_key": "BB_BASIC_MOB_REINFORCEMENTS_COOLDOWN", + "cooldown_duration": "30 SECONDS", + "lock_on_succeed": true + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/call_reinforcements", + "vars": { + "target_key": "BB_CALL_REINFORCEMENTS_TARGET" + } + } + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/attack_obstructions", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/move_to_reinforce" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CALL_REINFORCEMENTS_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/trooper/peaceful_burst.bt.json b/code/modules/mob/living/basic/trooper/peaceful_burst.bt.json new file mode 100644 index 00000000000..b77b8705642 --- /dev/null +++ b/code/modules/mob/living/basic/trooper/peaceful_burst.bt.json @@ -0,0 +1,140 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/trooper/ranged/burst/peaceful_burst", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "key": "BB_CALL_REINFORCEMENTS_TARGET" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_equals", + "vars": { + "key": "BB_CALLS_REINFORCEMENTS", + "value": true + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/cooldown", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "cooldown_key": "BB_BASIC_MOB_REINFORCEMENTS_COOLDOWN", + "cooldown_duration": "30 SECONDS", + "lock_on_succeed": true + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/call_reinforcements", + "vars": { + "target_key": "BB_CALL_REINFORCEMENTS_TARGET" + } + } + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/attack_obstructions", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_ranged_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION", + "time_between_perform": "3 SECONDS", + "avoid_friendly_fire": true + } + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/move_to_reinforce" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CALL_REINFORCEMENTS_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/trooper/ranged.bt.json b/code/modules/mob/living/basic/trooper/ranged.bt.json new file mode 100644 index 00000000000..e28e6625b22 --- /dev/null +++ b/code/modules/mob/living/basic/trooper/ranged.bt.json @@ -0,0 +1,10 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/trooper/ranged", + "type": "subtree", + "subtype": "/datum/bt_node/subtree/trooper_ranged", + "bindings": { + "b4kar3zl": "1 SECONDS", + "beslksyc": "5", + "bnr1aazo": "TRUE" + } +} diff --git a/code/modules/mob/living/basic/trooper/shotgunner.bt.json b/code/modules/mob/living/basic/trooper/shotgunner.bt.json new file mode 100644 index 00000000000..3c2368d6afc --- /dev/null +++ b/code/modules/mob/living/basic/trooper/shotgunner.bt.json @@ -0,0 +1,10 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/trooper/ranged/shotgunner", + "type": "subtree", + "subtype": "/datum/bt_node/subtree/trooper_ranged", + "bindings": { + "b4kar3zl": "3 SECONDS", + "beslksyc": "3", + "bnr1aazo": "TRUE" + } +} diff --git a/code/modules/mob/living/basic/trooper/trooper.bt.json b/code/modules/mob/living/basic/trooper/trooper.bt.json new file mode 100644 index 00000000000..60f355e3cbf --- /dev/null +++ b/code/modules/mob/living/basic/trooper/trooper.bt.json @@ -0,0 +1,118 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/trooper", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_equals", + "vars": { + "key": "BB_CALLS_REINFORCEMENTS", + "value": true + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/cooldown", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "cooldown_key": "BB_BASIC_MOB_REINFORCEMENTS_COOLDOWN", + "cooldown_duration": "30 SECONDS", + "lock_on_succeed": true + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/call_reinforcements" + } + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/attack_obstructions", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/move_to_reinforce" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/trooper/trooper_ai.dm b/code/modules/mob/living/basic/trooper/trooper_ai.dm index b4cd1d52f6d..6976cfaf62c 100644 --- a/code/modules/mob/living/basic/trooper/trooper_ai.dm +++ b/code/modules/mob/living/basic/trooper/trooper_ai.dm @@ -1,4 +1,5 @@ /datum/ai_controller/basic_controller/trooper + behavior_tree_json = "code/modules/mob/living/basic/trooper/trooper.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_TARGET_MINIMUM_STAT = HARD_CRIT, @@ -6,99 +7,71 @@ ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/attack_obstacle_in_path/trooper, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/travel_to_point/and_clear_target/reinforce, - ) - -/datum/ai_planning_subtree/basic_melee_attack_subtree/trooper - melee_attack_behavior = /datum/ai_behavior/basic_melee_attack - -/datum/ai_planning_subtree/attack_obstacle_in_path/trooper - attack_behaviour = /datum/ai_behavior/attack_obstructions/trooper - -/datum/ai_behavior/attack_obstructions/trooper - action_cooldown = 1.2 SECONDS /datum/ai_controller/basic_controller/trooper/calls_reinforcements - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/call_reinforcements, - /datum/ai_planning_subtree/attack_obstacle_in_path/trooper, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/travel_to_point/and_clear_target/reinforce, + blackboard = list( + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_TARGET_MINIMUM_STAT = HARD_CRIT, + BB_REINFORCEMENTS_SAY = "411 in progress, requesting backup!", + BB_CALLS_REINFORCEMENTS = TRUE, ) /datum/ai_controller/basic_controller/trooper/peaceful - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/target_retaliate, - /datum/ai_planning_subtree/call_reinforcements, - /datum/ai_planning_subtree/attack_obstacle_in_path/trooper, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/travel_to_point/and_clear_target/reinforce, + behavior_tree_json = "code/modules/mob/living/basic/trooper/peaceful.bt.json" + blackboard = list( + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_TARGET_MINIMUM_STAT = HARD_CRIT, + BB_REINFORCEMENTS_SAY = "411 in progress, requesting backup!", + BB_CALLS_REINFORCEMENTS = TRUE ) +/datum/bt_node/subtree/trooper_ranged + behavior_tree_json = "code/modules/mob/living/basic/trooper/trooper_ranged.bt.json" + + /datum/ai_controller/basic_controller/trooper/ranged - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_ranged_attack_subtree/trooper, - /datum/ai_planning_subtree/travel_to_point/and_clear_target/reinforce, + behavior_tree_json = "code/modules/mob/living/basic/trooper/ranged.bt.json" + blackboard = list( + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_TARGET_MINIMUM_STAT = HARD_CRIT, + BB_REINFORCEMENTS_SAY = "411 in progress, requesting backup!", + BB_RANGED_SKIRMISH_MIN_DISTANCE = 3, + BB_RANGED_SKIRMISH_MAX_DISTANCE = 4 ) -/datum/ai_planning_subtree/basic_ranged_attack_subtree/trooper - ranged_attack_behavior = /datum/ai_behavior/basic_ranged_attack/trooper -/datum/ai_behavior/basic_ranged_attack/trooper - action_cooldown = 1 SECONDS - required_distance = 5 - avoid_friendly_fire = TRUE /datum/ai_controller/basic_controller/trooper/ranged/burst - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_ranged_attack_subtree/trooper_burst, - /datum/ai_planning_subtree/travel_to_point/and_clear_target/reinforce, + behavior_tree_json = "code/modules/mob/living/basic/trooper/burst.bt.json" + blackboard = list( + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_TARGET_MINIMUM_STAT = HARD_CRIT, + BB_REINFORCEMENTS_SAY = "411 in progress, requesting backup!", + BB_RANGED_SKIRMISH_MIN_DISTANCE = 2, + BB_RANGED_SKIRMISH_MAX_DISTANCE = 3 ) -/datum/ai_planning_subtree/basic_ranged_attack_subtree/trooper_burst - ranged_attack_behavior = /datum/ai_behavior/basic_ranged_attack/trooper_burst - -/datum/ai_behavior/basic_ranged_attack/trooper_burst - action_cooldown = 3 SECONDS - avoid_friendly_fire = TRUE - -/datum/ai_controller/basic_controller/trooper/ranged/burst/peaceful - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/target_retaliate, - /datum/ai_planning_subtree/call_reinforcements, - /datum/ai_planning_subtree/basic_ranged_attack_subtree/trooper_burst, - /datum/ai_planning_subtree/travel_to_point/and_clear_target/reinforce, +//lol my parser cant deal with two subtypes with the same name so this is just a peaceful burst :DDDD ebin +/datum/ai_controller/basic_controller/trooper/ranged/burst/peaceful_burst + behavior_tree_json = "code/modules/mob/living/basic/trooper/peaceful_burst.bt.json" + blackboard = list( + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_TARGET_MINIMUM_STAT = HARD_CRIT, + BB_REINFORCEMENTS_SAY = "411 in progress, requesting backup!", + BB_CALLS_REINFORCEMENTS = TRUE ) /datum/ai_controller/basic_controller/trooper/ranged/shotgunner - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_ranged_attack_subtree/trooper_shotgun, - /datum/ai_planning_subtree/travel_to_point/and_clear_target/reinforce, + behavior_tree_json = "code/modules/mob/living/basic/trooper/shotgunner.bt.json" + blackboard = list( + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_TARGET_MINIMUM_STAT = HARD_CRIT, + BB_REINFORCEMENTS_SAY = "411 in progress, requesting backup!", + BB_RANGED_SKIRMISH_MIN_DISTANCE = 2, + BB_RANGED_SKIRMISH_MAX_DISTANCE = 3 ) -/datum/ai_planning_subtree/basic_ranged_attack_subtree/trooper_shotgun - ranged_attack_behavior = /datum/ai_behavior/basic_ranged_attack/trooper_shotgun -/datum/ai_behavior/basic_ranged_attack/trooper_shotgun - action_cooldown = 3 SECONDS - required_distance = 3 - avoid_friendly_fire = TRUE /datum/ai_controller/basic_controller/trooper/viscerator blackboard = list( diff --git a/code/modules/mob/living/basic/trooper/trooper_ranged.bt.json b/code/modules/mob/living/basic/trooper/trooper_ranged.bt.json new file mode 100644 index 00000000000..4aa2adfbafb --- /dev/null +++ b/code/modules/mob/living/basic/trooper/trooper_ranged.bt.json @@ -0,0 +1,137 @@ +{ + "dm_type": "/datum/bt_node/subtree/trooper_ranged", + "bindings": { + "b4kar3zl": { + "label": "time_between_perform", + "default": "0" + }, + "beslksyc": { + "label": "max_range", + "default": "3" + }, + "bnr1aazo": { + "label": "avoid_friendly_fire", + "default": "FALSE" + } + }, + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_equals", + "vars": { + "key": "BB_CALLS_REINFORCEMENTS", + "value": true + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/cooldown", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "cooldown_key": "BB_BASIC_MOB_REINFORCEMENTS_COOLDOWN", + "cooldown_duration": "30 SECONDS", + "lock_on_succeed": true + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/call_reinforcements" + } + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/attack_obstructions", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_ranged_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION", + "time_between_perform": "$b4kar3zl", + "max_range": "$beslksyc", + "avoid_friendly_fire": "$bnr1aazo" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/succeed" + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/maintain_distance", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/move_to_reinforce" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/turtle/turtle.bt.json b/code/modules/mob/living/basic/turtle/turtle.bt.json new file mode 100644 index 00000000000..5a912fb917f --- /dev/null +++ b/code/modules/mob/living/basic/turtle/turtle.bt.json @@ -0,0 +1,169 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/turtle", + "type": "selector", + "children": [ + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/find_food" + }, + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/ability_available", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "ability_key": "BB_GENERIC_ACTION" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/use_mob_ability", + "vars": { + "ability_key": "BB_GENERIC_ACTION" + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "key": "BB_TURTLE_HEADBUTT_VICTIM" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_TURTLE_HEADBUTT_VICTIM", + "required_dist": 1 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/set_bb_cooldown", + "vars": { + "cooldown_key": "BB_TURTLE_HEADBUTT_COOLDOWN", + "cooldown_duration": "1 MINUTES" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/hunt_target/headbutt_leg", + "vars": { + "target_key": "BB_TURTLE_HEADBUTT_VICTIM", + "always_reset_target": true + } + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "key": "BB_TURTLE_HEADBUTT_VICTIM" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_TURTLE_FLORA_TARGET", + "required_dist": 1 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/set_bb_cooldown", + "vars": { + "cooldown_key": "BB_TURTLE_FLORA_COOLDOWN", + "cooldown_duration": "1 MINUTES" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/hunt_target/sniff_flora", + "vars": { + "target_key": "BB_TURTLE_FLORA_TARGET", + "always_reset_target": true + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/move_to_and_eat" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk", + "bindings": { + "bf07i8ep": "10" + } + } + ] + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/key_off_cooldown", + "vars": { + "cooldown_key": "BB_TURTLE_HEADBUTT_COOLDOWN" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_TURTLE_HEADBUTT_VICTIM", + "target_source": "/datum/target_source/oview_typed/from_bb_key/turtle_headbutt_types", + "targeting_strategy": "/datum/targeting_strategy/legged_conscious_human" + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/key_off_cooldown", + "vars": { + "cooldown_key": "BB_TURTLE_FLORA_COOLDOWN" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_TURTLE_FLORA_TARGET", + "target_source": "/datum/target_source/oview_typed/from_bb_key/turtle_flora_types", + "targeting_strategy": "/datum/targeting_strategy/sniffable_hydro" + } + } + }, + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "loop_delay": "1 SECONDS", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/express_happiness" + } + ] + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/turtle/turtle.dm b/code/modules/mob/living/basic/turtle/turtle.dm index 9f0ab89b07a..d78450573dc 100644 --- a/code/modules/mob/living/basic/turtle/turtle.dm +++ b/code/modules/mob/living/basic/turtle/turtle.dm @@ -53,6 +53,14 @@ var/static/list/indigestible_seeds = typecacheof(list( /obj/item/seeds/random, )) + var/static/list/headbutt_targets = list( + /mob/living/carbon/human, + ) + var/static/list/flora_to_smell = list( + /obj/machinery/hydroponics, + /obj/item/kirbyplants, + ) + /mob/living/basic/turtle/Initialize(mapload) . = ..() @@ -67,6 +75,8 @@ add_traits(list(TRAIT_NODROWN, TRAIT_SWIMMER), INNATE_TRAIT) var/static/list/eatable_food = list(/obj/item/seeds) ai_controller.set_blackboard_key(BB_BASIC_FOODS, typecacheof(eatable_food)) + ai_controller.set_blackboard_key(BB_TURTLE_HEADBUTT_TYPES, typecacheof(headbutt_targets)) + ai_controller.set_blackboard_key(BB_TURTLE_FLORA_TYPES, typecacheof(flora_to_smell)) AddElement(/datum/element/basic_eating, food_types = eatable_food) AddComponent(/datum/component/happiness) RegisterSignal(src, COMSIG_MOB_PRE_EAT, PROC_REF(pre_eat_food)) @@ -164,7 +174,7 @@ developed_path = evolution_path var/datum/action/cooldown/tree_ability = new new_ability_path(src) tree_ability?.Grant(src) - ai_controller?.set_blackboard_key(BB_TURTLE_TREE_ABILITY, tree_ability) + ai_controller?.set_blackboard_key(BB_GENERIC_ACTION, tree_ability) STOP_PROCESSING(SSprocessing, src) update_appearance() diff --git a/code/modules/mob/living/basic/turtle/turtle_ai.dm b/code/modules/mob/living/basic/turtle/turtle_ai.dm index 1af7c3111f7..6bda4053c3e 100644 --- a/code/modules/mob/living/basic/turtle/turtle_ai.dm +++ b/code/modules/mob/living/basic/turtle/turtle_ai.dm @@ -1,4 +1,5 @@ /datum/ai_controller/basic_controller/turtle + behavior_tree_json = "code/modules/mob/living/basic/turtle/turtle.bt.json" blackboard = list( BB_HAPPY_EMOTIONS = list( "wiggles its tree in excitement!", @@ -15,78 +16,3 @@ ), ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk/less_walking - planning_subtrees = list( - /datum/ai_planning_subtree/find_food, - /datum/ai_planning_subtree/express_happiness, - /datum/ai_planning_subtree/use_mob_ability/turtle_tree, - /datum/ai_planning_subtree/find_and_hunt_target/headbutt_people, //playfully headbutt people's legs - /datum/ai_planning_subtree/find_and_hunt_target/sniff_flora, //mmm the aroma - ) - -/datum/ai_planning_subtree/use_mob_ability/turtle_tree - ability_key = BB_TURTLE_TREE_ABILITY - -/datum/ai_planning_subtree/use_mob_ability/turtle_tree/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/happiness_count = controller.blackboard[BB_BASIC_HAPPINESS] * 100 - if(happiness_count > 75) - return ..() - if(!SPT_PROB(happiness_count / 50, seconds_per_tick)) - return - return ..() - -/datum/ai_planning_subtree/find_and_hunt_target/sniff_flora - target_key = BB_TURTLE_FLORA_TARGET - finding_behavior = /datum/ai_behavior/find_hunt_target/sniff_flora - hunting_behavior = /datum/ai_behavior/hunt_target/sniff_flora - hunt_targets = list( - /obj/machinery/hydroponics, - /obj/item/kirbyplants, - ) - hunt_range = 5 - hunt_chance = 45 - -/datum/ai_behavior/find_hunt_target/sniff_flora - action_cooldown = 1 MINUTES - behavior_flags = AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION - -/datum/ai_behavior/find_hunt_target/sniff_flora/valid_dinner(mob/living/source, obj/machinery/hydroponics/dinner, radius, datum/ai_controller/controller, seconds_per_tick) - if(!istype(dinner)) - return TRUE - if(isnull(dinner.myseed)) - return FALSE - if(dinner.weedlevel > 5 || dinner.pestlevel > 5) //too smelly - return FALSE - return can_see(source, dinner, radius) - -/datum/ai_behavior/hunt_target/sniff_flora - always_reset_target = TRUE - -/datum/ai_behavior/hunt_target/sniff_flora/target_caught(mob/living/hunter, atom/hunted) - hunter.manual_emote("Enjoys the sweet scent eminating from [hunted::name]!") - -/datum/ai_planning_subtree/find_and_hunt_target/headbutt_people - target_key = BB_TURTLE_HEADBUTT_VICTIM - finding_behavior = /datum/ai_behavior/find_hunt_target/human_to_headbutt - hunting_behavior = /datum/ai_behavior/hunt_target/headbutt_leg - hunt_targets = list(/mob/living/carbon/human) - hunt_range = 4 - hunt_chance = 45 - -/datum/ai_behavior/find_hunt_target/human_to_headbutt - action_cooldown = 2 MINUTES - behavior_flags = AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION - -/datum/ai_behavior/find_hunt_target/human_to_headbutt/valid_dinner(mob/living/source, mob/living/carbon/human/dinner, radius, datum/ai_controller/controller, seconds_per_tick) - if(dinner.stat != CONSCIOUS) - return FALSE - if(isnull(dinner.get_bodypart(BODY_ZONE_R_LEG)) && isnull(dinner.get_bodypart(BODY_ZONE_L_LEG))) //no legs to headbutt! - return FALSE - return can_see(source, dinner, radius) - -/datum/ai_behavior/hunt_target/headbutt_leg - always_reset_target = TRUE - -/datum/ai_behavior/hunt_target/headbutt_leg/target_caught(mob/living/hunter, atom/hunted) - hunter.manual_emote("playfully headbutts [hunted]'s legs!") - diff --git a/code/modules/mob/living/basic/vermin/axolotl.bt.json b/code/modules/mob/living/basic/vermin/axolotl.bt.json new file mode 100644 index 00000000000..20dd0de5e09 --- /dev/null +++ b/code/modules/mob/living/basic/vermin/axolotl.bt.json @@ -0,0 +1,5 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/axolotl", + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" +} diff --git a/code/modules/mob/living/basic/vermin/axolotl.dm b/code/modules/mob/living/basic/vermin/axolotl.dm index 730b22fb16d..b6ad8b5c121 100644 --- a/code/modules/mob/living/basic/vermin/axolotl.dm +++ b/code/modules/mob/living/basic/vermin/axolotl.dm @@ -37,6 +37,6 @@ AddElement(/datum/element/can_be_held) /datum/ai_controller/basic_controller/axolotl + behavior_tree_json = "code/modules/mob/living/basic/vermin/axolotl.bt.json" ai_traits = PASSIVE_AI_FLAGS ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk diff --git a/code/modules/mob/living/basic/vermin/butterfly.bt.json b/code/modules/mob/living/basic/vermin/butterfly.bt.json new file mode 100644 index 00000000000..439ec88c758 --- /dev/null +++ b/code/modules/mob/living/basic/vermin/butterfly.bt.json @@ -0,0 +1,5 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/butterfly", + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" +} diff --git a/code/modules/mob/living/basic/vermin/butterfly.dm b/code/modules/mob/living/basic/vermin/butterfly.dm index cbc847aa9e8..9edb092d965 100644 --- a/code/modules/mob/living/basic/vermin/butterfly.dm +++ b/code/modules/mob/living/basic/vermin/butterfly.dm @@ -39,8 +39,7 @@ return TRUE //treaty signed at the Beeneeva convention /datum/ai_controller/basic_controller/butterfly - ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk + behavior_tree_json = "code/modules/mob/living/basic/vermin/butterfly.bt.json" /mob/living/basic/butterfly/lavaland unsuitable_atmos_damage = 0 diff --git a/code/modules/mob/living/basic/vermin/cockroach/cockroach.bt.json b/code/modules/mob/living/basic/vermin/cockroach/cockroach.bt.json new file mode 100644 index 00000000000..11d967e9330 --- /dev/null +++ b/code/modules/mob/living/basic/vermin/cockroach/cockroach.bt.json @@ -0,0 +1,80 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/cockroach", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_LOW_PRIORITY_HUNTING_TARGET", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_LOW_PRIORITY_HUNTING_TARGET", + "required_dist": 1 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/hunt_target", + "vars": { + "target_key": "BB_LOW_PRIORITY_HUNTING_TARGET", + "cooldown_key": "BB_ROACH_HUNT_COOLDOWN", + "hunt_cooldown": "5 SECONDS", + "always_reset_target": true + } + } + ] + } + }, + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_FAIL_ON_FAILURE", + "loop_delay": "IDLE_BEHAVIOR_RATE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/idle_random_walk" + } + ] + } + ] + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/key_off_cooldown", + "vars": { + "cooldown_key": "BB_ROACH_HUNT_COOLDOWN" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_LOW_PRIORITY_HUNTING_TARGET", + "target_source": "/datum/target_source/oview_single_type/ants", + "targeting_strategy": "/datum/targeting_strategy/huntable", + "vision_range": 2 + } + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop" + } + ] +} diff --git a/code/modules/mob/living/basic/vermin/cockroach/cockroach_aggro.bt.json b/code/modules/mob/living/basic/vermin/cockroach/cockroach_aggro.bt.json new file mode 100644 index 00000000000..42333e89b35 --- /dev/null +++ b/code/modules/mob/living/basic/vermin/cockroach/cockroach_aggro.bt.json @@ -0,0 +1,145 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/cockroach/aggro", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree", + "override_id": "SUBPLAN_ID_PET_COMMAND" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CURRENT_TARGET", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_LOW_PRIORITY_HUNTING_TARGET", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_LOW_PRIORITY_HUNTING_TARGET", + "required_dist": 1 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/hunt_target", + "vars": { + "target_key": "BB_LOW_PRIORITY_HUNTING_TARGET", + "cooldown_key": "BB_ROACH_HUNT_COOLDOWN", + "hunt_cooldown": "5 SECONDS", + "always_reset_target": true + } + } + ] + } + }, + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_FAIL_ON_FAILURE", + "loop_delay": "IDLE_BEHAVIOR_RATE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/idle_random_walk" + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/key_off_cooldown", + "vars": { + "cooldown_key": "BB_ROACH_HUNT_COOLDOWN" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_LOW_PRIORITY_HUNTING_TARGET", + "target_source": "/datum/target_source/oview_single_type/ants", + "targeting_strategy": "/datum/targeting_strategy/huntable", + "vision_range": 2 + } + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop" + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/vermin/cockroach/cockroach_ai.dm b/code/modules/mob/living/basic/vermin/cockroach/cockroach_ai.dm index a6d42aa162f..bd5e14e94ee 100644 --- a/code/modules/mob/living/basic/vermin/cockroach/cockroach_ai.dm +++ b/code/modules/mob/living/basic/vermin/cockroach/cockroach_ai.dm @@ -1,64 +1,38 @@ /// AI controller for normal roach /datum/ai_controller/basic_controller/cockroach + behavior_tree_json = "code/modules/mob/living/basic/vermin/cockroach/cockroach.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_PET_TARGETING_STRATEGY = /datum/targeting_strategy/basic/not_friends, BB_OWNER_SELF_HARM_RESPONSES = list( "*me waves its antennae in disapproval.", "*me chitters sadly." - ) + ), + BB_BASIC_MOB_SPEAK_LINES = list( + BB_EMOTE_HEAR = list("chitters."), + BB_EMOTE_SOUND = list('sound/mobs/non-humanoids/insect/chitter.ogg'), + BB_SPEAK_CHANCE = 5, + ), ) ai_traits = PASSIVE_AI_FLAGS ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/random_speech/insect, - /datum/ai_planning_subtree/find_and_hunt_target/roach, - ) /// AI controller for aggressive roach /datum/ai_controller/basic_controller/cockroach/aggro - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/pet_planning, - /datum/ai_planning_subtree/random_speech/insect, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/find_and_hunt_target/roach, - ) + behavior_tree_json = "code/modules/mob/living/basic/vermin/cockroach/cockroach_aggro.bt.json" /// AI controller for roach who can shoot at you /datum/ai_controller/basic_controller/cockroach/glockroach - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/pet_planning, - /datum/ai_planning_subtree/random_speech/insect, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_ranged_attack_subtree/glockroach, //If we are attacking someone, this will prevent us from hunting - /datum/ai_planning_subtree/find_and_hunt_target/roach, - ) - -/datum/ai_planning_subtree/basic_ranged_attack_subtree/glockroach - ranged_attack_behavior = /datum/ai_behavior/basic_ranged_attack/glockroach - -/datum/ai_behavior/basic_ranged_attack/glockroach //Slightly slower, as this is being made in feature freeze ;) - action_cooldown = 1 SECONDS + behavior_tree_json = "code/modules/mob/living/basic/vermin/cockroach/cockroach_glockroach.bt.json" /// roach who shoots at you slightly slower /datum/ai_controller/basic_controller/cockroach/mobroach - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/pet_planning, - /datum/ai_planning_subtree/random_speech/insect, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_ranged_attack_subtree/mobroach, - /datum/ai_planning_subtree/find_and_hunt_target/roach, - ) + behavior_tree_json = "code/modules/mob/living/basic/vermin/cockroach/cockroach_mobroach.bt.json" -/datum/ai_planning_subtree/basic_ranged_attack_subtree/mobroach - ranged_attack_behavior = /datum/ai_behavior/basic_ranged_attack/mobroach +/datum/bt_node/ai_behavior/basic_ranged_attack/glockroach //Slightly slower, as this is being made in feature freeze ;) + time_between_perform = 1 SECONDS -/datum/ai_behavior/basic_ranged_attack/mobroach - action_cooldown = 2 SECONDS +/datum/bt_node/ai_behavior/basic_ranged_attack/mobroach + time_between_perform = 2 SECONDS diff --git a/code/modules/mob/living/basic/vermin/cockroach/cockroach_glockroach.bt.json b/code/modules/mob/living/basic/vermin/cockroach/cockroach_glockroach.bt.json new file mode 100644 index 00000000000..89b843a4538 --- /dev/null +++ b/code/modules/mob/living/basic/vermin/cockroach/cockroach_glockroach.bt.json @@ -0,0 +1,143 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/cockroach/glockroach", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree", + "override_id": "SUBPLAN_ID_PET_COMMAND" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CURRENT_TARGET", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_ranged_attack/glockroach", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/maintain_distance", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_LOW_PRIORITY_HUNTING_TARGET", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_LOW_PRIORITY_HUNTING_TARGET", + "required_dist": 1 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/hunt_target", + "vars": { + "target_key": "BB_LOW_PRIORITY_HUNTING_TARGET", + "cooldown_key": "BB_ROACH_HUNT_COOLDOWN", + "hunt_cooldown": "5 SECONDS", + "always_reset_target": true + } + } + ] + } + }, + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_FAIL_ON_FAILURE", + "loop_delay": "IDLE_BEHAVIOR_RATE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/idle_random_walk" + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/key_off_cooldown", + "vars": { + "cooldown_key": "BB_ROACH_HUNT_COOLDOWN" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_LOW_PRIORITY_HUNTING_TARGET", + "target_source": "/datum/target_source/oview_single_type/ants", + "targeting_strategy": "/datum/targeting_strategy/huntable", + "vision_range": 2 + } + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop" + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/vermin/cockroach/cockroach_mobroach.bt.json b/code/modules/mob/living/basic/vermin/cockroach/cockroach_mobroach.bt.json new file mode 100644 index 00000000000..ab18fc7ac40 --- /dev/null +++ b/code/modules/mob/living/basic/vermin/cockroach/cockroach_mobroach.bt.json @@ -0,0 +1,143 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/cockroach/mobroach", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree", + "override_id": "SUBPLAN_ID_PET_COMMAND" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CURRENT_TARGET", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_ranged_attack/mobroach", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/maintain_distance", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + } + ] + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_LOW_PRIORITY_HUNTING_TARGET", + "observer_abort": "BT_ABORT_BOTH" + }, + "child": { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_LOW_PRIORITY_HUNTING_TARGET", + "required_dist": 1 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/hunt_target", + "vars": { + "target_key": "BB_LOW_PRIORITY_HUNTING_TARGET", + "cooldown_key": "BB_ROACH_HUNT_COOLDOWN", + "hunt_cooldown": "5 SECONDS", + "always_reset_target": true + } + } + ] + } + }, + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_FAIL_ON_FAILURE", + "loop_delay": "IDLE_BEHAVIOR_RATE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/idle_random_walk" + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/key_off_cooldown", + "vars": { + "cooldown_key": "BB_ROACH_HUNT_COOLDOWN" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_LOW_PRIORITY_HUNTING_TARGET", + "target_source": "/datum/target_source/oview_single_type/ants", + "targeting_strategy": "/datum/targeting_strategy/huntable", + "vision_range": 2 + } + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop" + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/vermin/crab.bt.json b/code/modules/mob/living/basic/vermin/crab.bt.json new file mode 100644 index 00000000000..c8798d61275 --- /dev/null +++ b/code/modules/mob/living/basic/vermin/crab.bt.json @@ -0,0 +1,126 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/crab", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/run_away_from_target", + "bindings": { + "byk9gqj4": "BB_BASIC_MOB_FLEE_TARGET" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/go_for_swim" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_BASIC_MOB_STOP_FLEEING", + "invert": true + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list/nearest", + "vars": { + "target_key": "BB_BASIC_MOB_FLEE_TARGET", + "targeting_strategy": "BB_FLEE_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/key_off_cooldown", + "vars": { + "cooldown_key": "BB_KEY_SWIMMER_COOLDOWN" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_SWIM_ALTERNATE_TURF", + "targeting_strategy": "/datum/targeting_strategy/walkable_turf", + "target_source": "/datum/target_source/oview_water_turfs" + } + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop", + "bindings": { + "bqdqne64": "/datum/bt_node/ai_behavior/random_speech/crab" + } + } + ] +} diff --git a/code/modules/mob/living/basic/vermin/crab.dm b/code/modules/mob/living/basic/vermin/crab.dm index 8840bf3c153..78468e3855e 100644 --- a/code/modules/mob/living/basic/vermin/crab.dm +++ b/code/modules/mob/living/basic/vermin/crab.dm @@ -78,6 +78,7 @@ ///The basic ai controller for crabs /datum/ai_controller/basic_controller/crab + behavior_tree_json = "code/modules/mob/living/basic/vermin/crab.bt.json" blackboard = list( BB_ALWAYS_IGNORE_FACTION = TRUE, BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/of_size/smaller, @@ -86,13 +87,3 @@ ai_traits = DEFAULT_AI_FLAGS | STOP_MOVING_WHEN_PULLED ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/find_nearest_thing_which_attacked_me_to_flee/from_flee_key, - /datum/ai_planning_subtree/flee_target/from_flee_key, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/random_speech/crab, - /datum/ai_planning_subtree/go_for_swim, - ) diff --git a/code/modules/mob/living/basic/vermin/eat_cable.bt.json b/code/modules/mob/living/basic/vermin/eat_cable.bt.json new file mode 100644 index 00000000000..564fa4b28ec --- /dev/null +++ b/code/modules/mob/living/basic/vermin/eat_cable.bt.json @@ -0,0 +1,58 @@ +{ + "dm_type": "/datum/bt_node/subtree/eat_cable", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_LOW_PRIORITY_HUNTING_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_LOW_PRIORITY_HUNTING_TARGET", + "required_dist": 0 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/clear_key", + "vars": { + "key": "BB_MOUSE_WANTS_TO_EAT_CABLE" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/hunt_target/interact_with_target", + "vars": { + "target_key": "BB_LOW_PRIORITY_HUNTING_TARGET", + "cooldown_key": "BB_MOUSE_CABLE_HUNT_COOLDOWN", + "hunt_cooldown": "20 SECONDS" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_LOW_PRIORITY_HUNTING_TARGET", + "target_source": "/datum/target_source/oview_single_type/cable", + "targeting_strategy": "/datum/targeting_strategy/accessible_cable", + "vision_range": 0 + } + } + ] + } +} diff --git a/code/modules/mob/living/basic/vermin/eat_cheese.bt.json b/code/modules/mob/living/basic/vermin/eat_cheese.bt.json new file mode 100644 index 00000000000..ad71a43064e --- /dev/null +++ b/code/modules/mob/living/basic/vermin/eat_cheese.bt.json @@ -0,0 +1,51 @@ +{ + "dm_type": "/datum/bt_node/subtree/eat_cheese", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_HUNTING_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_HUNTING_TARGET", + "required_dist": 1 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/hunt_target/interact_with_target", + "vars": { + "target_key": "BB_CURRENT_HUNTING_TARGET", + "cooldown_key": "BB_MOUSE_CHEESE_HUNT_COOLDOWN", + "hunt_cooldown": "20 SECONDS" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_CURRENT_HUNTING_TARGET", + "targeting_strategy": "/datum/targeting_strategy/pickup_item", + "target_source": "/datum/target_source/oview_single_type/cheese", + "vision_range": 1 + } + } + ] + } +} diff --git a/code/modules/mob/living/basic/vermin/frog.bt.json b/code/modules/mob/living/basic/vermin/frog.bt.json new file mode 100644 index 00000000000..fe676a7bea3 --- /dev/null +++ b/code/modules/mob/living/basic/vermin/frog.bt.json @@ -0,0 +1,60 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/frog", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/frog_engage_target" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/go_for_swim" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/key_off_cooldown", + "vars": { + "cooldown_key": "BB_KEY_SWIMMER_COOLDOWN" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_SWIM_ALTERNATE_TURF", + "targeting_strategy": "/datum/targeting_strategy/walkable_turf", + "target_source": "/datum/target_source/oview_water_turfs" + } + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop", + "bindings": { + "bqdqne64": "/datum/bt_node/ai_behavior/random_speech/frog" + } + } + ] +} diff --git a/code/modules/mob/living/basic/vermin/frog.dm b/code/modules/mob/living/basic/vermin/frog.dm index d7dbe3848f8..5ffe3326de0 100644 --- a/code/modules/mob/living/basic/vermin/frog.dm +++ b/code/modules/mob/living/basic/vermin/frog.dm @@ -146,7 +146,12 @@ AddComponent(/datum/component/explode_on_attack, mob_type_dont_bomb = typecacheof(list(/mob/living/basic/frog, /mob/living/basic/leaper))) addtimer(CALLBACK(src, PROC_REF(death)), existence_period) +/// Engage our current target: flee from scary fishermen, otherwise beat them up. +/datum/bt_node/subtree/frog_engage_target + behavior_tree_json = "code/modules/mob/living/basic/vermin/frog_engage_target.bt.json" + /datum/ai_controller/basic_controller/frog + behavior_tree_json = "code/modules/mob/living/basic/vermin/frog.bt.json" blackboard = list( BB_BASIC_MOB_STOP_FLEEING = TRUE, //We only flee from scary fishermen. BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, @@ -158,33 +163,14 @@ ) ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/target_retaliate, - /datum/ai_planning_subtree/random_speech/frog, - /datum/ai_planning_subtree/basic_melee_attack_subtree/no_fisherman, - /datum/ai_planning_subtree/flee_target/from_fisherman, - /datum/ai_planning_subtree/go_for_swim, - ) /datum/ai_controller/basic_controller/frog/trash - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/pet_planning, - /datum/ai_planning_subtree/random_speech/frog, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/basic_melee_attack_subtree/no_fisherman, - /datum/ai_planning_subtree/flee_target/from_fisherman, - ) + behavior_tree_json = "code/modules/mob/living/basic/vermin/trash.bt.json" /datum/ai_controller/basic_controller/frog/suicide_frog + behavior_tree_json = "code/modules/mob/living/basic/vermin/suicide_frog.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_PET_TARGETING_STRATEGY = /datum/targeting_strategy/basic/not_friends, BB_TARGET_PRIORITY_TRAIT = TRAIT_SCARY_FISHERMAN, //No fear, only hatred. It has nothing to lose ) - - planning_subtrees = list( - /datum/ai_planning_subtree/find_target_prioritize_traits, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) diff --git a/code/modules/mob/living/basic/vermin/frog_engage_target.bt.json b/code/modules/mob/living/basic/vermin/frog_engage_target.bt.json new file mode 100644 index 00000000000..f8bd885f6bc --- /dev/null +++ b/code/modules/mob/living/basic/vermin/frog_engage_target.bt.json @@ -0,0 +1,90 @@ +{ + "dm_type": "/datum/bt_node/subtree/frog_engage_target", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/target_has_trait", + "vars": { + "key": "BB_CURRENT_TARGET", + "trait": "TRAIT_SCARY_FISHERMAN" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/find_flee_location", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION", + "destination_key": "BB_FLEE_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_FLEE_LOCATION", + "required_dist": 0, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": true + } + } + ] + } + ] + } +} diff --git a/code/modules/mob/living/basic/vermin/lizard.bt.json b/code/modules/mob/living/basic/vermin/lizard.bt.json new file mode 100644 index 00000000000..80e4cc2575e --- /dev/null +++ b/code/modules/mob/living/basic/vermin/lizard.bt.json @@ -0,0 +1,35 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/lizard", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/move_to_and_eat" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/find_food" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop", + "bindings": { + "bqdqne64": "/datum/bt_node/ai_behavior/random_speech/lizard" + } + } + ] +} diff --git a/code/modules/mob/living/basic/vermin/lizard.dm b/code/modules/mob/living/basic/vermin/lizard.dm index a61b1604c54..38311a4c73d 100644 --- a/code/modules/mob/living/basic/vermin/lizard.dm +++ b/code/modules/mob/living/basic/vermin/lizard.dm @@ -64,17 +64,13 @@ ai_controller.set_blackboard_key(BB_BASIC_FOODS, typecacheof(edibles)) /datum/ai_controller/basic_controller/lizard + behavior_tree_json = "code/modules/mob/living/basic/vermin/lizard.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, ) ai_traits = PASSIVE_AI_FLAGS ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/find_food, - /datum/ai_planning_subtree/random_speech/lizard, - ) //Subtypes of lizards follow. diff --git a/code/modules/mob/living/basic/vermin/mothroach/mothroach.bt.json b/code/modules/mob/living/basic/vermin/mothroach/mothroach.bt.json new file mode 100644 index 00000000000..44dcc0a750b --- /dev/null +++ b/code/modules/mob/living/basic/vermin/mothroach/mothroach.bt.json @@ -0,0 +1,81 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/mothroach", + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/override_id_set", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "override_id": "SUBPLAN_ID_PET_COMMAND" + }, + "child": { + "type": "subtree", + "subtype": "/datum/bt_node/subtree", + "override_id": "SUBPLAN_ID_PET_COMMAND" + } + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "1 SECONDS", + "finish_on_primary": true, + "children": [ + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "key": "BB_CURRENT_TARGET", + "observer_abort": "BT_ABORT_SELF" + }, + "child": { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/run_away_from_target" + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/move_to_and_eat" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/target_from_retaliate_list/nearest", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_FLEE_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/find_food" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/random_speech/mothroach" + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/vermin/mothroach/mothroach_ai.dm b/code/modules/mob/living/basic/vermin/mothroach/mothroach_ai.dm index c589152f1cd..cfddecf2495 100644 --- a/code/modules/mob/living/basic/vermin/mothroach/mothroach_ai.dm +++ b/code/modules/mob/living/basic/vermin/mothroach/mothroach_ai.dm @@ -1,4 +1,5 @@ /datum/ai_controller/basic_controller/mothroach + behavior_tree_json = "code/modules/mob/living/basic/vermin/mothroach/mothroach.bt.json" blackboard = list( BB_FLEE_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_PET_TARGETING_STRATEGY = /datum/targeting_strategy/basic/not_friends, @@ -8,23 +9,3 @@ ai_traits = PASSIVE_AI_FLAGS ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/pet_planning, - /datum/ai_planning_subtree/find_food/mothroach, - /datum/ai_planning_subtree/target_retaliate/to_flee, - /datum/ai_planning_subtree/flee_target/from_flee_key, - /datum/ai_planning_subtree/random_speech/mothroach, - ) - -/datum/ai_planning_subtree/find_food/mothroach - finding_behavior = /datum/ai_behavior/find_and_set/in_list/mothroach_food - -/datum/ai_behavior/find_and_set/in_list/mothroach_food - -/datum/ai_behavior/find_and_set/in_list/mothroach_food/search_tactic(datum/ai_controller/controller, locate_paths, search_range = SEARCH_TACTIC_DEFAULT_RANGE) - var/list/found = typecache_filter_list(oview(search_range, controller.pawn), locate_paths) - var/mob/living/living_pawn = controller.pawn - found -= living_pawn.loc - if(length(found)) - return pick(found) diff --git a/code/modules/mob/living/basic/vermin/mouse.bt.json b/code/modules/mob/living/basic/vermin/mouse.bt.json new file mode 100644 index 00000000000..ce8f8c1c1b2 --- /dev/null +++ b/code/modules/mob/living/basic/vermin/mouse.bt.json @@ -0,0 +1,189 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/mouse", + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/override_id_set", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "override_id": "SUBPLAN_ID_PET_COMMAND" + }, + "child": { + "type": "subtree", + "subtype": "/datum/bt_node/subtree", + "override_id": "SUBPLAN_ID_PET_COMMAND" + } + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "1 SECONDS", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/eat_cheese" + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/run_away_from_target" + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/key_off_cooldown", + "vars": { + "cooldown_key": "BB_MOUSE_CHEESE_HUNT_COOLDOWN" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_CURRENT_HUNTING_TARGET", + "target_source": "/datum/target_source/oview_single_type/cheese", + "targeting_strategy": "/datum/targeting_strategy/anything", + "vision_range": 1 + } + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets/nearest", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/play_instrument_on_floor" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/eat_cable" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/key_off_cooldown", + "vars": { + "cooldown_key": "BB_MOUSE_CHEESE_HUNT_COOLDOWN" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_CURRENT_HUNTING_TARGET", + "target_source": "/datum/target_source/oview_single_type/cheese", + "targeting_strategy": "/datum/targeting_strategy/anything", + "vision_range": 1 + } + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets/nearest", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_SONG_INSTRUMENT", + "target_source": "/datum/target_source/oview_single_type/piano_synth", + "targeting_strategy": "/datum/targeting_strategy/playable_synthesizer" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_true", + "vars": { + "key": "BB_MOUSE_WANTS_TO_EAT_CABLE" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_LOW_PRIORITY_HUNTING_TARGET", + "target_source": "/datum/target_source/oview_single_type/cable", + "targeting_strategy": "/datum/targeting_strategy/accessible_cable", + "vision_range": 0 + } + } + } + ] + } + ] + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop", + "bindings": { + "bqdqne64": "/datum/bt_node/ai_behavior/random_speech/mouse" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/key_off_cooldown", + "vars": { + "cooldown_key": "BB_MOUSE_CABLE_HUNT_COOLDOWN" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/random_chance", + "vars": { + "chance": 0.01 + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/set_bb_key", + "vars": { + "target_key": "BB_MOUSE_WANTS_TO_EAT_CABLE", + "value": true + } + } + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/vermin/mouse.dm b/code/modules/mob/living/basic/vermin/mouse.dm index 3e92f78c8af..efb2d4b19c0 100644 --- a/code/modules/mob/living/basic/vermin/mouse.dm +++ b/code/modules/mob/living/basic/vermin/mouse.dm @@ -199,7 +199,7 @@ new /obj/effect/temp_visual/heart(loc) add_faction(FACTION_NEUTRAL) try_consume_cheese(cheese) - ai_controller.CancelActions() // Interrupt any current fleeing + ai_controller.cancel_current_plan() // Interrupt any current fleeing /// Attempts to consume a piece of cheese, causing a few effects. /mob/living/basic/mouse/proc/try_consume_cheese(obj/item/food/cheese/cheese) @@ -422,6 +422,7 @@ /// The mouse AI controller /datum/ai_controller/basic_controller/mouse + behavior_tree_json = "code/modules/mob/living/basic/vermin/mouse.bt.json" blackboard = list( // Always cowardly BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, // Use this to find people to run away from BB_PET_TARGETING_STRATEGY = /datum/targeting_strategy/basic/not_friends, @@ -431,41 +432,14 @@ ai_traits = PASSIVE_AI_FLAGS ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - // Try to speak, because it's cute - /datum/ai_planning_subtree/random_speech/mouse, - // Follow the boss's orders - /datum/ai_planning_subtree/pet_planning, - // Look for and execute hunts for cheese even if someone is looking at us - /datum/ai_planning_subtree/find_and_hunt_target/look_for_cheese, - // Next priority is to try and appreoach a keyboard - /datum/ai_planning_subtree/approach_synthesizer, - // And play it if we are near it - /datum/ai_planning_subtree/generic_play_instrument/end_planning, - // Next priority is see if anyone is looking at us - /datum/ai_planning_subtree/simple_find_nearest_target_to_flee, - // Skedaddle - /datum/ai_planning_subtree/flee_target/mouse, - // Otherwise, look for and execute hunts for cabling - /datum/ai_planning_subtree/find_and_hunt_target/look_for_cables, - ) - -/// Don't look for anything to run away from if you are distracted by being adjacent to cheese -/datum/ai_planning_subtree/flee_target/mouse - -/datum/ai_planning_subtree/flee_target/mouse/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - var/atom/hunted_cheese = controller.blackboard[BB_CURRENT_HUNTING_TARGET] - if (!isnull(hunted_cheese)) - return // We see some cheese, which is more important than our life - return ..() /// AI controller for rats, slightly more complex than mice becuase they attack people /datum/ai_controller/basic_controller/mouse/rat + behavior_tree_json = "code/modules/mob/living/basic/vermin/mouse_rat.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, BB_PET_TARGETING_STRATEGY = /datum/targeting_strategy/basic/not_friends, - BB_BASIC_MOB_CURRENT_TARGET = null, // heathen + BB_CURRENT_TARGET = null, // heathen BB_CURRENT_HUNTING_TARGET = null, // cheese BB_LOW_PRIORITY_HUNTING_TARGET = null, // cable BB_OWNER_SELF_HARM_RESPONSES = list( @@ -477,14 +451,15 @@ ai_traits = DEFAULT_AI_FLAGS | STOP_MOVING_WHEN_PULLED ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk - planning_subtrees = list( - /datum/ai_planning_subtree/escape_captivity, - /datum/ai_planning_subtree/pet_planning, - /datum/ai_planning_subtree/simple_find_target, - /datum/ai_planning_subtree/attack_obstacle_in_path, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/find_and_hunt_target/look_for_cheese, - /datum/ai_planning_subtree/random_speech/mouse, - /datum/ai_planning_subtree/find_and_hunt_target/look_for_cables, - ) + + + + +/datum/bt_node/subtree/eat_cable + behavior_tree_json = "code/modules/mob/living/basic/vermin/eat_cable.bt.json" + +/datum/bt_node/subtree/eat_cheese + behavior_tree_json = "code/modules/mob/living/basic/vermin/eat_cheese.bt.json" + +/datum/bt_node/subtree/play_instrument_on_floor + behavior_tree_json = "code/modules/mob/living/basic/vermin/play_instrument_on_floor.bt.json" diff --git a/code/modules/mob/living/basic/vermin/mouse_rat.bt.json b/code/modules/mob/living/basic/vermin/mouse_rat.bt.json new file mode 100644 index 00000000000..2fa9b856d77 --- /dev/null +++ b/code/modules/mob/living/basic/vermin/mouse_rat.bt.json @@ -0,0 +1,217 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/mouse/rat", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/override_id_set", + "vars": { + "observer_abort": "BT_ABORT_LOWER_PRIORITY", + "override_id": "SUBPLAN_ID_PET_COMMAND" + }, + "child": { + "type": "subtree", + "subtype": "/datum/bt_node/subtree", + "override_id": "SUBPLAN_ID_PET_COMMAND" + } + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "1 SECONDS", + "finish_on_primary": true, + "children": [] + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_SUCCEED_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/attack_obstructions", + "vars": { + "target_key": "BB_CURRENT_TARGET" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/eat_cheese" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/eat_cable" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/key_off_cooldown", + "vars": { + "cooldown_key": "BB_MOUSE_CHEESE_HUNT_COOLDOWN" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_CURRENT_HUNTING_TARGET", + "target_source": "/datum/target_source/oview_single_type/cheese", + "targeting_strategy": "/datum/targeting_strategy/anything", + "vision_range": 1 + } + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_true", + "vars": { + "key": "BB_MOUSE_WANTS_TO_EAT_CABLE" + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_LOW_PRIORITY_HUNTING_TARGET", + "target_source": "/datum/target_source/oview_single_type/cable", + "targeting_strategy": "/datum/targeting_strategy/accessible_cable", + "vision_range": 0 + } + } + }, + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_SUCCEED_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_FAIL_ON_FAILURE", + "loop_delay": "1 SECONDS", + "children": [ + { + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_speech_loop", + "bindings": { + "bqdqne64": "/datum/bt_node/ai_behavior/random_speech/mouse" + } + }, + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/key_off_cooldown", + "vars": { + "cooldown_key": "BB_MOUSE_CABLE_HUNT_COOLDOWN" + }, + "child": { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/random_chance", + "vars": { + "chance": 0.01 + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/set_bb_key", + "vars": { + "target_key": "BB_MOUSE_WANTS_TO_EAT_CABLE", + "value": true + } + } + } + } + ] + } + ] + } + ] + } + ] +} diff --git a/code/modules/mob/living/basic/vermin/play_instrument_on_floor.bt.json b/code/modules/mob/living/basic/vermin/play_instrument_on_floor.bt.json new file mode 100644 index 00000000000..d6ea55eb32c --- /dev/null +++ b/code/modules/mob/living/basic/vermin/play_instrument_on_floor.bt.json @@ -0,0 +1,78 @@ +{ + "dm_type": "/datum/bt_node/subtree/play_instrument_on_floor", + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_SONG_INSTRUMENT" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/is_at_distance", + "vars": { + "observer_abort": "BT_ABORT_SELF", + "maximum_distance": 1, + "require_reach": true + }, + "child": { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/keep_playing_instrument", + "vars": { + "song_instrument_key": "BB_SONG_INSTRUMENT" + } + } + }, + { + "type": "sequence", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_SONG_INSTRUMENT", + "required_dist": 1 + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/setup_instrument", + "vars": { + "song_instrument_key": "BB_SONG_INSTRUMENT", + "song_lines_key": "BB_SONG_LINES" + } + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/play_instrument", + "vars": { + "song_instrument_key": "BB_SONG_INSTRUMENT", + "volume": 50 + } + } + ] + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_interaction_target", + "vars": { + "target_key": "BB_SONG_INSTRUMENT", + "target_source": "/datum/target_source/oview_single_type/piano_synth", + "targeting_strategy": "/datum/targeting_strategy/playable_synthesizer" + } + } + ] + } +} diff --git a/code/modules/mob/living/basic/vermin/space_bat.bt.json b/code/modules/mob/living/basic/vermin/space_bat.bt.json new file mode 100644 index 00000000000..cd360abe561 --- /dev/null +++ b/code/modules/mob/living/basic/vermin/space_bat.bt.json @@ -0,0 +1,5 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/space_bat", + "type": "subtree", + "subtype": "/datum/bt_node/subtree/simple_retaliate_combat" +} diff --git a/code/modules/mob/living/basic/vermin/space_bat.dm b/code/modules/mob/living/basic/vermin/space_bat.dm index 4ef58a45c3a..27f14f49672 100644 --- a/code/modules/mob/living/basic/vermin/space_bat.dm +++ b/code/modules/mob/living/basic/vermin/space_bat.dm @@ -41,15 +41,10 @@ ///Controller for space bats, has nothing unique, just retaliation. /datum/ai_controller/basic_controller/space_bat + behavior_tree_json = "code/modules/mob/living/basic/vermin/space_bat.bt.json" blackboard = list( BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, ) ai_traits = PASSIVE_AI_FLAGS ai_movement = /datum/ai_movement/basic_avoidance - idle_behavior = /datum/idle_behavior/idle_random_walk/less_walking - - planning_subtrees = list( - /datum/ai_planning_subtree/target_retaliate, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - ) diff --git a/code/modules/mob/living/basic/vermin/suicide_frog.bt.json b/code/modules/mob/living/basic/vermin/suicide_frog.bt.json new file mode 100644 index 00000000000..a3fa8cdce07 --- /dev/null +++ b/code/modules/mob/living/basic/vermin/suicide_frog.bt.json @@ -0,0 +1,71 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/frog/suicide_frog", + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "decorator", + "decorator": "/datum/bt_node/decorator/bb_key_set", + "vars": { + "observer_abort": "BT_ABORT_BOTH", + "key": "BB_CURRENT_TARGET" + }, + "child": { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_ANY", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "finish_on_primary": true, + "children": [ + { + "type": "subplan", + "success_policy": "BT_SUBPLAN_LOOP_ON_SUCCESS", + "failure_policy": "BT_SUBPLAN_LOOP_ON_FAILURE", + "children": [ + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/basic_melee_attack", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/move_to_target", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "required_dist": 1, + "finish_on_arrival": false + } + } + ] + } + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets/prioritize_trait", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] +} diff --git a/code/modules/mob/living/basic/vermin/trash.bt.json b/code/modules/mob/living/basic/vermin/trash.bt.json new file mode 100644 index 00000000000..a51cb3f8b0b --- /dev/null +++ b/code/modules/mob/living/basic/vermin/trash.bt.json @@ -0,0 +1,61 @@ +{ + "dm_type": "/datum/ai_controller/basic_controller/frog/trash", + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/escape_captivity" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree", + "override_id": "SUBPLAN_ID_PET_COMMAND" + }, + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "BASIC_MOB_FIND_TARGET_RATE", + "finish_on_primary": true, + "children": [ + { + "type": "parallel", + "failure_policy": "BT_PARALLEL_FAILURE_CHILD_ONE", + "success_policy": "BT_PARALLEL_SUCCESS_CHILD_ONE", + "repeat_secondary": true, + "repeat_secondary_delay": "1 SECONDS", + "finish_on_primary": true, + "children": [ + { + "type": "selector", + "children": [ + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/frog_engage_target" + }, + { + "type": "subtree", + "subtype": "/datum/bt_node/subtree/random_walk" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/random_speech/frog" + } + ] + }, + { + "type": "leaf", + "behavior": "/datum/bt_node/ai_behavior/acquire_target/update_combat_targets", + "vars": { + "target_key": "BB_CURRENT_TARGET", + "targeting_strategy": "BB_TARGETING_STRATEGY", + "hiding_location_key": "BB_CURRENT_TARGET_HIDING_LOCATION" + } + } + ] + } + ] +} diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index 11d5088d5b9..cb071c379c5 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -1083,7 +1083,8 @@ GAME_VERB_PROC(/mob/living, mob_sleep, "Sleep", null) if (!buckled.anchored) buckled.moving_from_pull = moving_from_pull . = buckled.Move(newloc, direct, glide_size) - buckled.moving_from_pull = null + if(buckled) //buckled can become unbuckled, apparently + buckled.moving_from_pull = null return var/old_direction = dir @@ -2036,10 +2037,10 @@ GLOBAL_LIST_EMPTY(fire_appearances) //Check the amount of clients exists on the Z level we're leaving from, //this excludes us because at this point we are not registered to any z level. var/old_level_new_clients = (registered_z ? SSmobs.clients_by_zlevel[registered_z].len : null) - //No one is left after we're gone, shut off inactive ones + //No one is left after we're gone, recalculate AI status so eligible ones shut off if(registered_z && old_level_new_clients == 0) - for(var/datum/ai_controller/controller as anything in GLOB.ai_controllers_by_zlevel[registered_z]) - controller.set_ai_status(AI_STATUS_OFF) + for(var/datum/ai_controller/controller as anything in SSai_controllers.ai_controllers_by_zlevel[registered_z]) + controller.set_ai_status(controller.get_expected_ai_status()) if(new_z) //Check the amount of clients exists on the Z level we're moving towards, excluding ourselves. @@ -2049,7 +2050,7 @@ GLOBAL_LIST_EMPTY(fire_appearances) SSmobs.clients_by_zlevel[new_z] += src if(new_level_old_clients == 0) //No one was here before, wake up all the AIs. - for (var/datum/ai_controller/controller as anything in GLOB.ai_controllers_by_zlevel[new_z]) + for (var/datum/ai_controller/controller as anything in SSai_controllers.ai_controllers_by_zlevel[new_z]) //We don't set them directly on, for instances like AIs acting while dead and other cases that may exist in the future. //This isn't a problem for AIs with a client since the client will prevent this from being called anyway. controller.set_ai_status(controller.get_expected_ai_status()) diff --git a/code/modules/mob/living/simple_animal/bot/bot_announcement.dm b/code/modules/mob/living/simple_animal/bot/bot_announcement.dm index 10161dddfd9..0ce079dbe64 100644 --- a/code/modules/mob/living/simple_animal/bot/bot_announcement.dm +++ b/code/modules/mob/living/simple_animal/bot/bot_announcement.dm @@ -153,7 +153,7 @@ if (!(bot_owner.bot_mode_flags & BOT_MODE_ON)) return - bot_owner.say(line) + INVOKE_ASYNC(bot_owner, TYPE_PROC_REF(/atom/movable, say), line) if (channel && bot_owner.internal_radio.channels[channel]) bot_owner.internal_radio.talk_into(bot_owner, message = line, channel = channel) diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/demonic_frost_miner.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/demonic_frost_miner.dm index 275ba78c4f0..2cd694ae187 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/demonic_frost_miner.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/demonic_frost_miner.dm @@ -81,7 +81,7 @@ Difficulty: Extremely Hard AddElement(/datum/element/knockback, 7, FALSE, TRUE) AddElement(/datum/element/lifesteal, 50) ADD_TRAIT(src, TRAIT_NO_FLOATING_ANIM, INNATE_TRAIT) - AddComponent(/datum/component/boss_music, 'sound/music/boss/bdm_boss.ogg', COMSIG_HOSTILE_FOUND_TARGET) // change to COMSIG_AI_BLACKBOARD_KEY_SET(BB_BASIC_MOB_CURRENT_TARGET) in basic conversion + AddComponent(/datum/component/boss_music, 'sound/music/boss/bdm_boss.ogg', COMSIG_HOSTILE_FOUND_TARGET) // change to COMSIG_AI_BLACKBOARD_KEY_SET(BB_CURRENT_TARGET) in basic conversion /mob/living/simple_animal/hostile/megafauna/demonic_frost_miner/Destroy() frost_orbs = null diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/hierophant.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/hierophant.dm index 5e992bced04..ea465bfcd88 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/hierophant.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/hierophant.dm @@ -102,7 +102,7 @@ Difficulty: Hard /mob/living/simple_animal/hostile/megafauna/hierophant/Initialize(mapload) . = ..() spawned_beacon_ref = WEAKREF(new /obj/effect/hierophant(loc)) - AddComponent(/datum/component/boss_music, 'sound/music/boss/hiero_boss.ogg', COMSIG_HOSTILE_FOUND_TARGET) // change to COMSIG_AI_BLACKBOARD_KEY_SET(BB_BASIC_MOB_CURRENT_TARGET) in basic conversion + AddComponent(/datum/component/boss_music, 'sound/music/boss/hiero_boss.ogg', COMSIG_HOSTILE_FOUND_TARGET) // change to COMSIG_AI_BLACKBOARD_KEY_SET(BB_CURRENT_TARGET) in basic conversion /mob/living/simple_animal/hostile/megafauna/hierophant/Destroy() QDEL_NULL(spawned_beacon_ref) diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm index d1a0b684f9b..ed5d5e73677 100644 --- a/code/modules/mob/mob.dm +++ b/code/modules/mob/mob.dm @@ -1440,9 +1440,6 @@ GAME_VERB_HIDDEN(/mob, DisDblClick, ".dblclick", argu = null as anything, sec = if(href_list[VV_HK_GIVE_AI]) return SSadmin_verbs.dynamic_invoke_verb(usr, /datum/admin_verb/give_ai_controller, src) - if(href_list[VV_HK_GIVE_AI_SPEECH]) - return SSadmin_verbs.dynamic_invoke_verb(usr, /datum/admin_verb/give_ai_speech, src) - if(href_list[VV_HK_GIVE_MOB_ACTION]) return SSadmin_verbs.dynamic_invoke_verb(usr, /datum/admin_verb/give_mob_action, src) diff --git a/code/modules/paperwork/paper.dm b/code/modules/paperwork/paper.dm index bdba1f80a84..9ef5866c1d9 100644 --- a/code/modules/paperwork/paper.dm +++ b/code/modules/paperwork/paper.dm @@ -509,6 +509,8 @@ GAME_VERB_SRC(/obj/item/paper, rename, usr, "Rename paper", null) ) /obj/item/paper/ui_interact(mob/user, datum/tgui/ui) + if(!user.client) //bro stop trying to open UI on AI man ur gonna drive me nuts man comeon man + return if(resistance_flags & ON_FIRE) return ui = SStgui.try_update_ui(user, src, ui) diff --git a/code/modules/projectiles/guns/magic/staff.dm b/code/modules/projectiles/guns/magic/staff.dm index c13cf723b62..b681e2a74b9 100644 --- a/code/modules/projectiles/guns/magic/staff.dm +++ b/code/modules/projectiles/guns/magic/staff.dm @@ -103,7 +103,7 @@ var/obj/item/my_thing = pop(my_shit) user.dropItemToGround(my_thing) var/mob/living/angry_thing = my_thing.animate_atom_living() - angry_thing.ai_controller?.set_blackboard_key(BB_BASIC_MOB_CURRENT_TARGET, user) + angry_thing.ai_controller?.set_blackboard_key(BB_CURRENT_TARGET, user) angry_thing.ai_controller?.set_blackboard_key(BB_TARGET_MINIMUM_STAT, HARD_CRIT) angry_thing.ai_controller?.ai_interact(user, combat_mode = TRUE) user.apply_damage(35, BRUTE, forced = TRUE) // Mimics are not actually very strong so we pretend that it just bit us so we die faster, at least 3 charges & worn items should do it diff --git a/code/modules/projectiles/guns/magic/wands/wand_rebel.dm b/code/modules/projectiles/guns/magic/wands/wand_rebel.dm index 74cabaf9d0f..93f164b1c54 100644 --- a/code/modules/projectiles/guns/magic/wands/wand_rebel.dm +++ b/code/modules/projectiles/guns/magic/wands/wand_rebel.dm @@ -71,5 +71,5 @@ var/mob/living/bully = animate_item.animate_atom_living(firer) if (!bully.ai_controller) return - bully.ai_controller.set_blackboard_key(BB_BASIC_MOB_CURRENT_TARGET, victim) + bully.ai_controller.set_blackboard_key(BB_CURRENT_TARGET, victim) bully.ai_controller.ai_interact(victim, combat_mode = TRUE) diff --git a/code/modules/research/xenobiology/crossbreeding/_mobs.dm b/code/modules/research/xenobiology/crossbreeding/_mobs.dm index 9b37be5d1a5..2d136602b3d 100644 --- a/code/modules/research/xenobiology/crossbreeding/_mobs.dm +++ b/code/modules/research/xenobiology/crossbreeding/_mobs.dm @@ -48,7 +48,7 @@ Slimecrossing Mobs gold_core_spawnable = NO_SPAWN speak_emote = list("blorbles", "bubbles", "borks") -/mob/living/basic/pet/dog/corgi/puppy/slime/update_dog_speech(datum/ai_planning_subtree/random_speech/speech) - speech.speak = string_list(list()) - speech.emote_hear = string_list(list("bubbles!", "splorts.", "splops!")) - speech.emote_see = string_list(list("gets goop everywhere.", "flops.", "jiggles!")) +/mob/living/basic/pet/dog/corgi/puppy/slime/update_dog_speech(list/speech_data) + speech_data[BB_EMOTE_SAY] = string_list(list()) + speech_data[BB_EMOTE_HEAR] = string_list(list("bubbles!", "splorts.", "splops!")) + speech_data[BB_EMOTE_SEE] = string_list(list("gets goop everywhere.", "flops.", "jiggles!")) diff --git a/code/modules/unit_tests/_unit_tests.dm b/code/modules/unit_tests/_unit_tests.dm index 33575aa8454..15644a3c4e4 100644 --- a/code/modules/unit_tests/_unit_tests.dm +++ b/code/modules/unit_tests/_unit_tests.dm @@ -179,7 +179,6 @@ #include "embedding.dm" #include "emoting.dm" #include "emp_flashlight.dm" -#include "ensure_subtree_operational_datum.dm" #include "ethereal_revival.dm" #include "explosion_action.dm" #include "firedoor_regions.dm" diff --git a/code/modules/unit_tests/ensure_subtree_operational_datum.dm b/code/modules/unit_tests/ensure_subtree_operational_datum.dm deleted file mode 100644 index 9ca78fbc674..00000000000 --- a/code/modules/unit_tests/ensure_subtree_operational_datum.dm +++ /dev/null @@ -1,64 +0,0 @@ -/// The subtree that requires the operational datum. -#define REQUIRED_SUBTREE "required_subtree" -/// The list of typepaths of applicable operational datums that would satisfy the requirement. -#define REQUIRED_OPERATIONAL_DATUMS "required_operational_datums" - -/// Unit Test that ensure that if we add a specific planning subtree to a basic mob's planning tree, that we also have the operational datum needed for it (component/element). -/// This can be extended to other "mandatory" operational datums for certain subtrees to work. -/datum/unit_test/ensure_subtree_operational_datum - /// Associated list of mobs that we need to test this on. Key is the typepath of the mob, value is a list of the planning subtree and the operational datums that are required for it. - var/list/testable_mobs = list() - -/datum/unit_test/ensure_subtree_operational_datum/Run() - gather_testable_mobs() - test_applicable_mobs() - -/// First, look for all mobs that have a planning subtree that requires an element, then add it to the list for stuff to test afterwards. Done like this to not have one mumbo proc that's hard to read. -/datum/unit_test/ensure_subtree_operational_datum/proc/gather_testable_mobs() - for(var/mob/living/basic/checkable_mob as anything in subtypesof(/mob/living/basic)) - var/datum/ai_controller/testable_controller = initial(checkable_mob.ai_controller) - if(isnull(testable_controller)) - continue - - // we can't do inital() memes on lists so it's allocation time - testable_controller = allocate(testable_controller) - var/list/ai_planning_subtrees = testable_controller.planning_subtrees // list of instantiated datums. easy money - if(!length(ai_planning_subtrees)) - continue - - for(var/datum/ai_planning_subtree/testable_subtree as anything in ai_planning_subtrees) - var/list/necessary_datums = testable_subtree.operational_datums - if(isnull(necessary_datums)) - continue - - testable_mobs[checkable_mob] = list( - REQUIRED_OPERATIONAL_DATUMS = necessary_datums, - REQUIRED_SUBTREE = testable_subtree.type, - ) - -/// Then, test the mobs that we've found -/datum/unit_test/ensure_subtree_operational_datum/proc/test_applicable_mobs() - for(var/mob/living/basic/checkable_mob as anything in testable_mobs) - var/list/checkable_mob_data = testable_mobs[checkable_mob] - checkable_mob = allocate(checkable_mob) - - var/datum/ai_planning_subtree/test_subtree = checkable_mob_data[REQUIRED_SUBTREE] - var/list/trait_sources = GET_TRAIT_SOURCES(checkable_mob, TRAIT_SUBTREE_REQUIRED_OPERATIONAL_DATUM) - if(!length(trait_sources)) // yes yes we could use `COUNT_TRAIT_SOURCES` but why invoke the same macro twice - TEST_FAIL("The mob [checkable_mob] ([checkable_mob.type]) does not have ANY instances of TRAIT_SUBTREE_REQUIRED_OPERATIONAL_DATUM, but has a planning subtree ([test_subtree]) that requires it!") - continue - - var/has_element = FALSE - var/list/testable_operational_datums = checkable_mob_data[REQUIRED_OPERATIONAL_DATUMS] - for(var/iterable in trait_sources) - if(iterable in testable_operational_datums) - has_element = TRUE - break - - if(!has_element) - var/list/message_list = list("The mob [checkable_mob] ([checkable_mob.type]) has a planning subtree ([test_subtree]) that requires a component/element, but does not have any!") - message_list += "Needs one of the following to satisfy the requirement: ([testable_operational_datums.Join(", ")])" - TEST_FAIL(message_list.Join(" ")) - -#undef REQUIRED_SUBTREE -#undef REQUIRED_OPERATIONAL_DATUMS diff --git a/code/modules/unit_tests/mouse_bite_cable.dm b/code/modules/unit_tests/mouse_bite_cable.dm index dc7fd04d24c..53a28bf1079 100644 --- a/code/modules/unit_tests/mouse_bite_cable.dm +++ b/code/modules/unit_tests/mouse_bite_cable.dm @@ -20,21 +20,19 @@ // Ai controlling processes expect a seconds_per_tick, supply a real-fake dt var/fake_dt = SSai_controllers.wait * 0.1 - // Set AI - AIs by default are off in z-levels with no client, we have to force it on. + // Force this fker to be on + biter.ai_controller.ai_traits |= RUN_WHILE_UNWATCHED biter.ai_controller.set_ai_status(AI_STATUS_ON) - biter.ai_controller.can_idle = FALSE - // Select behavior - this will queue finding the cable - biter.ai_controller.SelectBehaviors(fake_dt) - // Process behavior - this will execute the "locate the cable" behavior - biter.ai_controller.process(fake_dt) - // Check that the cable was found - TEST_ASSERT(biter.ai_controller.blackboard[BB_LOW_PRIORITY_HUNTING_TARGET] == wire, "Mouse, after executing find, did not set the cable as a target.") - // Select behavior - this will queue hunting - biter.ai_controller.SelectBehaviors(fake_dt) - // Process behavior - this will execute the hunt for the cable and cause a bite (as we're in the min range) - biter.ai_controller.process(fake_dt) - // Check that the cable was removed, as it was hunted correctly - TEST_ASSERT_NULL(biter.ai_controller.blackboard[BB_LOW_PRIORITY_HUNTING_TARGET], "Mouse, after executing hunt, did not clear their target blackboard.") + + // Mouse eating is chance-based, so we set the hunting target directly, yes, this messed with the test from what it was before, but I'm not sure how to do it better :( + biter.ai_controller.set_blackboard_key(BB_LOW_PRIORITY_HUNTING_TARGET, wire) + + // Tick the tree until the hunt branch moves onto and bites the cable. Can we do this better in unit tests?? idk.. + for(var/i in 1 to 5) + if(QDELETED(biter)) + break + biter.ai_controller.SelectBehaviors(fake_dt) + biter.ai_controller.process(fake_dt) // Now check that the bite went through - remember we qdel mice on death TEST_ASSERT(QDELETED(biter), "Mouse, did not die after biting a powered cable.") @@ -47,12 +45,3 @@ /// Dummy mouse that is guaranteed to die when biting shocked cables. /mob/living/basic/mouse/cable_lover cable_zap_prob = 100 - ai_controller = /datum/ai_controller/basic_controller/mouse/guaranteed_to_bite - -/// Dummy mouse's ai controller that is guaranteed to find and bite a cable beneath it -/datum/ai_controller/basic_controller/mouse/guaranteed_to_bite - planning_subtrees = list(/datum/ai_planning_subtree/find_and_hunt_target/look_for_cables/guaranteed) - -/// Cable hunting subtree that's guarantee to hunt its target. -/datum/ai_planning_subtree/find_and_hunt_target/look_for_cables/guaranteed - hunt_chance = 100 diff --git a/tgstation.dme b/tgstation.dme index 006c9065de7..f2a42e3148b 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -288,6 +288,7 @@ #include "code\__DEFINES\zoom.dm" #include "code\__DEFINES\ai\ai.dm" #include "code\__DEFINES\ai\ai_blackboard.dm" +#include "code\__DEFINES\ai\behavior_trees.dm" #include "code\__DEFINES\ai\bot_keys.dm" #include "code\__DEFINES\ai\carp.dm" #include "code\__DEFINES\ai\haunted.dm" @@ -731,7 +732,6 @@ #include "code\_globalvars\time_vars.dm" #include "code\_globalvars\lists\achievements.dm" #include "code\_globalvars\lists\ambience.dm" -#include "code\_globalvars\lists\basic_ai.dm" #include "code\_globalvars\lists\canisters.dm" #include "code\_globalvars\lists\cargo.dm" #include "code\_globalvars\lists\client.dm" @@ -854,7 +854,7 @@ #include "code\controllers\subsystem\achievements.dm" #include "code\controllers\subsystem\admin_verbs.dm" #include "code\controllers\subsystem\ai_controllers.dm" -#include "code\controllers\subsystem\ai_idle_controllers.dm" +#include "code\controllers\subsystem\ai_controllers_low_priority.dm" #include "code\controllers\subsystem\air.dm" #include "code\controllers\subsystem\ambience.dm" #include "code\controllers\subsystem\area_contents.dm" @@ -938,8 +938,6 @@ #include "code\controllers\subsystem\transport.dm" #include "code\controllers\subsystem\tts.dm" #include "code\controllers\subsystem\tutorials.dm" -#include "code\controllers\subsystem\unplanned_ai_idle_controllers.dm" -#include "code\controllers\subsystem\unplanned_controllers.dm" #include "code\controllers\subsystem\verb_manager.dm" #include "code\controllers\subsystem\verbs.dm" #include "code\controllers\subsystem\vis_overlays.dm" @@ -983,8 +981,6 @@ #include "code\controllers\subsystem\persistence\trophy_fishes.dm" #include "code\controllers\subsystem\processing\acid.dm" #include "code\controllers\subsystem\processing\ai_basic_avoidance.dm" -#include "code\controllers\subsystem\processing\ai_behaviors.dm" -#include "code\controllers\subsystem\processing\ai_idle_behaviors.dm" #include "code\controllers\subsystem\processing\antag_hud.dm" #include "code\controllers\subsystem\processing\aura.dm" #include "code\controllers\subsystem\processing\clock_component.dm" @@ -1126,111 +1122,228 @@ #include "code\datums\actions\mobs\sequences\dash_attack.dm" #include "code\datums\actions\mobs\sequences\projectile.dm" #include "code\datums\ai\_ai_behavior.dm" +#include "code\datums\ai\_ai_bt_composites.dm" +#include "code\datums\ai\_ai_bt_decorators.dm" +#include "code\datums\ai\_ai_bt_node.dm" +#include "code\datums\ai\_ai_bt_subtree.dm" #include "code\datums\ai\_ai_controller.dm" -#include "code\datums\ai\_ai_planning_subtree.dm" #include "code\datums\ai\_item_behaviors.dm" +#include "code\datums\ai\bt_viewer.dm" #include "code\datums\ai\telegraph_effects.dm" #include "code\datums\ai\babies\babies_behaviors.dm" -#include "code\datums\ai\babies\babies_subtrees.dm" -#include "code\datums\ai\bane\bane_behaviors.dm" #include "code\datums\ai\bane\bane_controller.dm" -#include "code\datums\ai\bane\bane_subtrees.dm" #include "code\datums\ai\basic_mobs\admin_ai_templates.dm" #include "code\datums\ai\basic_mobs\base_basic_controller.dm" #include "code\datums\ai\basic_mobs\generic_controllers.dm" #include "code\datums\ai\basic_mobs\basic_ai_behaviors\basic_attacking.dm" #include "code\datums\ai\basic_mobs\basic_ai_behaviors\befriend_target.dm" -#include "code\datums\ai\basic_mobs\basic_ai_behaviors\clear_key.dm" -#include "code\datums\ai\basic_mobs\basic_ai_behaviors\climb_tree.dm" -#include "code\datums\ai\basic_mobs\basic_ai_behaviors\emote_with_target.dm" +#include "code\datums\ai\basic_mobs\basic_ai_behaviors\call_reinforcements.dm" +#include "code\datums\ai\basic_mobs\basic_ai_behaviors\find_flee_location.dm" #include "code\datums\ai\basic_mobs\basic_ai_behaviors\find_parent.dm" -#include "code\datums\ai\basic_mobs\basic_ai_behaviors\interact_with_target.dm" #include "code\datums\ai\basic_mobs\basic_ai_behaviors\nearest_targeting.dm" -#include "code\datums\ai\basic_mobs\basic_ai_behaviors\pick_up_item.dm" -#include "code\datums\ai\basic_mobs\basic_ai_behaviors\pull_target.dm" -#include "code\datums\ai\basic_mobs\basic_ai_behaviors\run_away_from_target.dm" -#include "code\datums\ai\basic_mobs\basic_ai_behaviors\set_travel_destination.dm" -#include "code\datums\ai\basic_mobs\basic_ai_behaviors\step_towards_turf.dm" +#include "code\datums\ai\basic_mobs\basic_ai_behaviors\play_dead.dm" #include "code\datums\ai\basic_mobs\basic_ai_behaviors\stop_and_stare.dm" #include "code\datums\ai\basic_mobs\basic_ai_behaviors\targeted_mob_ability.dm" #include "code\datums\ai\basic_mobs\basic_ai_behaviors\targeting.dm" #include "code\datums\ai\basic_mobs\basic_ai_behaviors\tipped_reaction.dm" -#include "code\datums\ai\basic_mobs\basic_ai_behaviors\travel_towards.dm" -#include "code\datums\ai\basic_mobs\basic_ai_behaviors\unbuckle_mob.dm" +#include "code\datums\ai\basic_mobs\basic_ai_behaviors\use_mob_ability.dm" #include "code\datums\ai\basic_mobs\basic_ai_behaviors\ventcrawling.dm" -#include "code\datums\ai\basic_mobs\basic_ai_behaviors\wounded_targeting.dm" #include "code\datums\ai\basic_mobs\basic_ai_behaviors\write_on_paper.dm" -#include "code\datums\ai\basic_mobs\basic_subtrees\attack_adjacent_target.dm" -#include "code\datums\ai\basic_mobs\basic_subtrees\attack_obstacle_in_path.dm" -#include "code\datums\ai\basic_mobs\basic_subtrees\call_reinforcements.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\capricious_retaliate.dm" -#include "code\datums\ai\basic_mobs\basic_subtrees\climb_tree.dm" -#include "code\datums\ai\basic_mobs\basic_subtrees\drag_items.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\enrage.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\escape_captivity.dm" -#include "code\datums\ai\basic_mobs\basic_subtrees\express_happiness.dm" -#include "code\datums\ai\basic_mobs\basic_subtrees\find_food.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\find_paper_and_write.dm" -#include "code\datums\ai\basic_mobs\basic_subtrees\find_parent.dm" -#include "code\datums\ai\basic_mobs\basic_subtrees\find_targets_prioritize_traits.dm" -#include "code\datums\ai\basic_mobs\basic_subtrees\fishing.dm" -#include "code\datums\ai\basic_mobs\basic_subtrees\flee_target.dm" +#include "code\datums\ai\basic_mobs\basic_subtrees\generic_hunger.dm" +#include "code\datums\ai\basic_mobs\basic_subtrees\generic_play_instrument.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\go_for_swim.dm" -#include "code\datums\ai\basic_mobs\basic_subtrees\maintain_distance.dm" -#include "code\datums\ai\basic_mobs\basic_subtrees\mine_walls.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\move_to_cardinal.dm" -#include "code\datums\ai\basic_mobs\basic_subtrees\opportunistic_ventcrawler.dm" -#include "code\datums\ai\basic_mobs\basic_subtrees\play_with_owners.dm" -#include "code\datums\ai\basic_mobs\basic_subtrees\prepare_travel_to_destination.dm" -#include "code\datums\ai\basic_mobs\basic_subtrees\ranged_skirmish.dm" -#include "code\datums\ai\basic_mobs\basic_subtrees\run_emote.dm" -#include "code\datums\ai\basic_mobs\basic_subtrees\shapechange_ambush.dm" -#include "code\datums\ai\basic_mobs\basic_subtrees\simple_attack_target.dm" -#include "code\datums\ai\basic_mobs\basic_subtrees\simple_find_nearest_target_to_flee.dm" -#include "code\datums\ai\basic_mobs\basic_subtrees\simple_find_target.dm" -#include "code\datums\ai\basic_mobs\basic_subtrees\simple_find_wounded_target.dm" -#include "code\datums\ai\basic_mobs\basic_subtrees\sleep_with_no_target.dm" -#include "code\datums\ai\basic_mobs\basic_subtrees\speech_subtree.dm" -#include "code\datums\ai\basic_mobs\basic_subtrees\stare_at_thing.dm" -#include "code\datums\ai\basic_mobs\basic_subtrees\target_retaliate.dm" -#include "code\datums\ai\basic_mobs\basic_subtrees\targeted_mob_ability.dm" -#include "code\datums\ai\basic_mobs\basic_subtrees\teleport_away_from_target.dm" -#include "code\datums\ai\basic_mobs\basic_subtrees\tipped_subtree.dm" -#include "code\datums\ai\basic_mobs\basic_subtrees\travel_to_point.dm" -#include "code\datums\ai\basic_mobs\basic_subtrees\use_mob_ability.dm" -#include "code\datums\ai\basic_mobs\pet_commands\fetch.dm" -#include "code\datums\ai\basic_mobs\pet_commands\pet_command_planning.dm" -#include "code\datums\ai\basic_mobs\pet_commands\pet_follow_friend.dm" +#include "code\datums\ai\basic_mobs\basic_subtrees\random_speech_loop.dm" +#include "code\datums\ai\basic_mobs\basic_subtrees\run_away_from_target.dm" +#include "code\datums\ai\basic_mobs\basic_subtrees\skittish_brawler_combat.dm" +#include "code\datums\ai\basic_mobs\pet_commands\pet_command_bt.dm" #include "code\datums\ai\basic_mobs\pet_commands\pet_use_targeted_ability.dm" -#include "code\datums\ai\basic_mobs\pet_commands\play_dead.dm" #include "code\datums\ai\basic_mobs\target_priority_strategies\_target_priority_strategy.dm" #include "code\datums\ai\basic_mobs\target_priority_strategies\mining_strategies.dm" +#include "code\datums\ai\basic_mobs\target_sources\_target_source.dm" +#include "code\datums\ai\basic_mobs\target_sources\held_items_then_oview.dm" +#include "code\datums\ai\basic_mobs\target_sources\held_items_typed.dm" +#include "code\datums\ai\basic_mobs\target_sources\mobs_in_oview.dm" +#include "code\datums\ai\basic_mobs\target_sources\near_village_humans.dm" +#include "code\datums\ai\basic_mobs\target_sources\oview_single_type.dm" +#include "code\datums\ai\basic_mobs\target_sources\oview_typed_from_bb_key.dm" +#include "code\datums\ai\basic_mobs\target_sources\range_turfs_typecache_visible.dm" +#include "code\datums\ai\basic_mobs\target_sources\slime_source.dm" +#include "code\datums\ai\basic_mobs\target_sources\turfs_in_oview.dm" #include "code\datums\ai\basic_mobs\targeting_strategies\_targeting_strategy.dm" +#include "code\datums\ai\basic_mobs\targeting_strategies\accessible_cable.dm" +#include "code\datums\ai\basic_mobs\targeting_strategies\ally_mob.dm" +#include "code\datums\ai\basic_mobs\targeting_strategies\baby_raptor.dm" #include "code\datums\ai\basic_mobs\targeting_strategies\basic_targeting_strategy.dm" +#include "code\datums\ai\basic_mobs\targeting_strategies\beamable_hydro.dm" +#include "code\datums\ai\basic_mobs\targeting_strategies\befriendable_cultist.dm" +#include "code\datums\ai\basic_mobs\targeting_strategies\cat_food.dm" +#include "code\datums\ai\basic_mobs\targeting_strategies\chargeable_apc.dm" +#include "code\datums\ai\basic_mobs\targeting_strategies\conscious_human.dm" +#include "code\datums\ai\basic_mobs\targeting_strategies\conscious_mob.dm" +#include "code\datums\ai\basic_mobs\targeting_strategies\conscious_snail.dm" +#include "code\datums\ai\basic_mobs\targeting_strategies\damaged_eyes.dm" +#include "code\datums\ai\basic_mobs\targeting_strategies\damaged_machine.dm" +#include "code\datums\ai\basic_mobs\targeting_strategies\dead_mob.dm" +#include "code\datums\ai\basic_mobs\targeting_strategies\decorated_donut.dm" #include "code\datums\ai\basic_mobs\targeting_strategies\dont_target_friends.dm" +#include "code\datums\ai\basic_mobs\targeting_strategies\drillable_ice.dm" +#include "code\datums\ai\basic_mobs\targeting_strategies\empty_paper.dm" +#include "code\datums\ai\basic_mobs\targeting_strategies\finished_stove.dm" +#include "code\datums\ai\basic_mobs\targeting_strategies\food_or_drink.dm" +#include "code\datums\ai\basic_mobs\targeting_strategies\goliath_diggable_turf.dm" +#include "code\datums\ai\basic_mobs\targeting_strategies\goose_edible.dm" +#include "code\datums\ai\basic_mobs\targeting_strategies\huntable.dm" +#include "code\datums\ai\basic_mobs\targeting_strategies\huntable_mouse.dm" +#include "code\datums\ai\basic_mobs\targeting_strategies\injured_mob.dm" +#include "code\datums\ai\basic_mobs\targeting_strategies\injured_raptor.dm" +#include "code\datums\ai\basic_mobs\targeting_strategies\legged_conscious_human.dm" +#include "code\datums\ai\basic_mobs\targeting_strategies\living_not_dead.dm" +#include "code\datums\ai\basic_mobs\targeting_strategies\non_stump_tree.dm" +#include "code\datums\ai\basic_mobs\targeting_strategies\pickup_item.dm" +#include "code\datums\ai\basic_mobs\targeting_strategies\playable_deer.dm" +#include "code\datums\ai\basic_mobs\targeting_strategies\playable_synthesizer.dm" +#include "code\datums\ai\basic_mobs\targeting_strategies\pollinatable_hydro.dm" +#include "code\datums\ai\basic_mobs\targeting_strategies\raptor_trough.dm" +#include "code\datums\ai\basic_mobs\targeting_strategies\slime_food.dm" +#include "code\datums\ai\basic_mobs\targeting_strategies\sniffable_hydro.dm" +#include "code\datums\ai\basic_mobs\targeting_strategies\stealable_item.dm" +#include "code\datums\ai\basic_mobs\targeting_strategies\stocked_beehive.dm" +#include "code\datums\ai\basic_mobs\targeting_strategies\treatable_hydro.dm" +#include "code\datums\ai\basic_mobs\targeting_strategies\trough_with_ore.dm" +#include "code\datums\ai\basic_mobs\targeting_strategies\unbroken_light.dm" +#include "code\datums\ai\basic_mobs\targeting_strategies\uncarried_egg.dm" +#include "code\datums\ai\basic_mobs\targeting_strategies\unlit_bonfire.dm" +#include "code\datums\ai\basic_mobs\targeting_strategies\valid_cat_home.dm" +#include "code\datums\ai\basic_mobs\targeting_strategies\valid_kitten.dm" +#include "code\datums\ai\basic_mobs\targeting_strategies\walkable_turf.dm" +#include "code\datums\ai\basic_mobs\targeting_strategies\water_dispenser.dm" #include "code\datums\ai\basic_mobs\targeting_strategies\with_object.dm" +#include "code\datums\ai\basic_mobs\targeting_strategies\working_machine.dm" +#include "code\datums\ai\bots\bot_decorators.dm" +#include "code\datums\ai\bots\bot_subtrees.dm" #include "code\datums\ai\cursed\cursed_behaviors.dm" #include "code\datums\ai\cursed\cursed_controller.dm" -#include "code\datums\ai\cursed\cursed_subtrees.dm" -#include "code\datums\ai\dog\dog_behaviors.dm" +#include "code\datums\ai\dog\dog_bt.dm" #include "code\datums\ai\dog\dog_controller.dm" -#include "code\datums\ai\dog\dog_subtrees.dm" -#include "code\datums\ai\generic\find_and_set.dm" -#include "code\datums\ai\generic\generic_behaviors.dm" -#include "code\datums\ai\generic\generic_subtrees.dm" +#include "code\datums\ai\generic_behaviors\acquire_injured_target.dm" +#include "code\datums\ai\generic_behaviors\acquire_target.dm" +#include "code\datums\ai\generic_behaviors\ai_interact.dm" +#include "code\datums\ai\generic_behaviors\attack_obstacles.dm" +#include "code\datums\ai\generic_behaviors\battle_screech.dm" +#include "code\datums\ai\generic_behaviors\break_out_of_object.dm" +#include "code\datums\ai\generic_behaviors\break_spine.dm" +#include "code\datums\ai\generic_behaviors\cancel_current_plan.dm" +#include "code\datums\ai\generic_behaviors\clear_key.dm" +#include "code\datums\ai\generic_behaviors\consume.dm" +#include "code\datums\ai\generic_behaviors\copy_bb_key.dm" +#include "code\datums\ai\generic_behaviors\drag_target.dm" +#include "code\datums\ai\generic_behaviors\drop_all_held_items.dm" +#include "code\datums\ai\generic_behaviors\express_happiness.dm" +#include "code\datums\ai\generic_behaviors\face_target_or_face_initial.dm" +#include "code\datums\ai\generic_behaviors\fail.dm" +#include "code\datums\ai\generic_behaviors\find_furthest_turf_from_target.dm" +#include "code\datums\ai\generic_behaviors\find_nearby.dm" +#include "code\datums\ai\generic_behaviors\find_target_facing_turf.dm" +#include "code\datums\ai\generic_behaviors\find_unwebbed_turf.dm" +#include "code\datums\ai\generic_behaviors\find_valid_teleport_location.dm" +#include "code\datums\ai\generic_behaviors\give.dm" +#include "code\datums\ai\generic_behaviors\grab_target.dm" +#include "code\datums\ai\generic_behaviors\heal_eye_damage.dm" +#include "code\datums\ai\generic_behaviors\hunt_target.dm" +#include "code\datums\ai\generic_behaviors\issue_pet_command.dm" +#include "code\datums\ai\generic_behaviors\keep_playing_instrument.dm" +#include "code\datums\ai\generic_behaviors\maintain_distance.dm" +#include "code\datums\ai\generic_behaviors\mine_walls.dm" +#include "code\datums\ai\generic_behaviors\move_to_target.dm" +#include "code\datums\ai\generic_behaviors\perform_emote.dm" +#include "code\datums\ai\generic_behaviors\pick_random_ability.dm" +#include "code\datums\ai\generic_behaviors\pick_up.dm" +#include "code\datums\ai\generic_behaviors\play_instrument.dm" +#include "code\datums\ai\generic_behaviors\random_walk.dm" +#include "code\datums\ai\generic_behaviors\resist.dm" +#include "code\datums\ai\generic_behaviors\run_emote.dm" +#include "code\datums\ai\generic_behaviors\set_bb_cooldown.dm" +#include "code\datums\ai\generic_behaviors\set_bb_key.dm" +#include "code\datums\ai\generic_behaviors\setup_instrument.dm" +#include "code\datums\ai\generic_behaviors\speech.dm" +#include "code\datums\ai\generic_behaviors\spin_web.dm" +#include "code\datums\ai\generic_behaviors\stop_dragging.dm" +#include "code\datums\ai\generic_behaviors\stuff_in_disposal.dm" +#include "code\datums\ai\generic_behaviors\succeed.dm" +#include "code\datums\ai\generic_behaviors\target_retaliate.dm" +#include "code\datums\ai\generic_behaviors\use_in_hand.dm" +#include "code\datums\ai\generic_behaviors\use_on_object.dm" +#include "code\datums\ai\generic_behaviors\virtual_pick_up_item.dm" +#include "code\datums\ai\generic_behaviors\wait.dm" +#include "code\datums\ai\generic_decorators\ability_available.dm" +#include "code\datums\ai\generic_decorators\bb_key_at_least.dm" +#include "code\datums\ai\generic_decorators\bb_key_equals.dm" +#include "code\datums\ai\generic_decorators\bb_key_list_min_count.dm" +#include "code\datums\ai\generic_decorators\bb_key_set.dm" +#include "code\datums\ai\generic_decorators\bb_key_true.dm" +#include "code\datums\ai\generic_decorators\buckle_target_dangerous.dm" +#include "code\datums\ai\generic_decorators\can_see_target.dm" +#include "code\datums\ai\generic_decorators\check_cooldown.dm" +#include "code\datums\ai\generic_decorators\check_rider_stat.dm" +#include "code\datums\ai\generic_decorators\container_attackable.dm" +#include "code\datums\ai\generic_decorators\is_at_distance.dm" +#include "code\datums\ai\generic_decorators\is_dragging.dm" +#include "code\datums\ai\generic_decorators\is_grabbing_target.dm" +#include "code\datums\ai\generic_decorators\is_holding_target.dm" +#include "code\datums\ai\generic_decorators\is_in_vent.dm" +#include "code\datums\ai\generic_decorators\is_target_stunned.dm" +#include "code\datums\ai\generic_decorators\item_inside_pawn.dm" +#include "code\datums\ai\generic_decorators\key_in_typelist.dm" +#include "code\datums\ai\generic_decorators\keys_different_gender.dm" +#include "code\datums\ai\generic_decorators\mob_stat_at_least.dm" +#include "code\datums\ai\generic_decorators\no_humans_watching.dm" +#include "code\datums\ai\generic_decorators\pawn_buckled_to_obj.dm" +#include "code\datums\ai\generic_decorators\pawn_contained_in_obj.dm" +#include "code\datums\ai\generic_decorators\pawn_farther_than_from_key.dm" +#include "code\datums\ai\generic_decorators\pawn_grabbed_by_enemy.dm" +#include "code\datums\ai\generic_decorators\pawn_has_gravity.dm" +#include "code\datums\ai\generic_decorators\pawn_has_trait_from.dm" +#include "code\datums\ai\generic_decorators\pawn_health_below.dm" +#include "code\datums\ai\generic_decorators\pawn_inside_mob.dm" +#include "code\datums\ai\generic_decorators\pawn_is_restrained.dm" +#include "code\datums\ai\generic_decorators\pawn_loc_is_type.dm" +#include "code\datums\ai\generic_decorators\pawn_nutrition_below.dm" +#include "code\datums\ai\generic_decorators\pawn_same_z_as_key.dm" +#include "code\datums\ai\generic_decorators\pawn_turf_has_trait.dm" +#include "code\datums\ai\generic_decorators\random_chance.dm" +#include "code\datums\ai\generic_decorators\random_chance_from_key.dm" +#include "code\datums\ai\generic_decorators\target_has_reagent.dm" +#include "code\datums\ai\generic_decorators\target_has_trait.dm" +#include "code\datums\ai\generic_decorators\target_health_below_fraction.dm" +#include "code\datums\ai\generic_decorators\target_holding_lit_item.dm" +#include "code\datums\ai\generic_decorators\target_is_holding_item.dm" +#include "code\datums\ai\generic_decorators\target_is_type.dm" +#include "code\datums\ai\generic_decorators\target_legcuffed.dm" +#include "code\datums\ai\generic_decorators\target_on_ground.dm" +#include "code\datums\ai\generic_decorators\true_for_time.dm" +#include "code\datums\ai\generic_subtrees\basic_find_target.dm" +#include "code\datums\ai\generic_subtrees\capricious_pick_target.dm" +#include "code\datums\ai\generic_subtrees\climb_tree.dm" +#include "code\datums\ai\generic_subtrees\find_food.dm" +#include "code\datums\ai\generic_subtrees\find_partner.dm" +#include "code\datums\ai\generic_subtrees\find_stealable_object.dm" +#include "code\datums\ai\generic_subtrees\forage_and_retaliate.dm" +#include "code\datums\ai\generic_subtrees\make_babies.dm" +#include "code\datums\ai\generic_subtrees\move_to_and_eat.dm" +#include "code\datums\ai\generic_subtrees\move_to_and_hunt.dm" +#include "code\datums\ai\generic_subtrees\move_to_reinforce.dm" +#include "code\datums\ai\generic_subtrees\pick_retaliate_target.dm" +#include "code\datums\ai\generic_subtrees\random_walk.dm" +#include "code\datums\ai\generic_subtrees\skittish_and_speak.dm" +#include "code\datums\ai\generic_subtrees\steal_and_flee.dm" +#include "code\datums\ai\hauntium\haunted_bt_nodes.dm" #include "code\datums\ai\hauntium\haunted_controller.dm" -#include "code\datums\ai\hauntium\hauntium_subtrees.dm" -#include "code\datums\ai\hunting_behavior\hunting_behaviors.dm" -#include "code\datums\ai\hunting_behavior\hunting_cockroach.dm" -#include "code\datums\ai\hunting_behavior\hunting_corpses.dm" -#include "code\datums\ai\hunting_behavior\hunting_lights.dm" -#include "code\datums\ai\hunting_behavior\hunting_mouse.dm" -#include "code\datums\ai\idle_behaviors\_idle_behavior.dm" -#include "code\datums\ai\idle_behaviors\idle_dog.dm" -#include "code\datums\ai\idle_behaviors\idle_haunted.dm" -#include "code\datums\ai\idle_behaviors\idle_monkey.dm" -#include "code\datums\ai\idle_behaviors\idle_random_walk.dm" -#include "code\datums\ai\monkey\monkey_behaviors.dm" +#include "code\datums\ai\monkey\monkey_bt_nodes.dm" #include "code\datums\ai\monkey\monkey_controller.dm" #include "code\datums\ai\monkey\monkey_subtrees.dm" #include "code\datums\ai\movement\_ai_movement.dm" @@ -1242,7 +1355,6 @@ #include "code\datums\ai\objects\vending_machines\vending_machine_controller.dm" #include "code\datums\ai\robot_customer\robot_customer_behaviors.dm" #include "code\datums\ai\robot_customer\robot_customer_controller.dm" -#include "code\datums\ai\robot_customer\robot_customer_subtrees.dm" #include "code\datums\ai_laws\ai_laws.dm" #include "code\datums\ai_laws\laws_antagonistic.dm" #include "code\datums\ai_laws\laws_neutral.dm" @@ -5485,6 +5597,7 @@ #include "code\modules\mob\living\basic\farm_animals\bee\_bee.dm" #include "code\modules\mob\living\basic\farm_animals\bee\bee_ai_behavior.dm" #include "code\modules\mob\living\basic\farm_animals\bee\bee_ai_subtree.dm" +#include "code\modules\mob\living\basic\farm_animals\bee\bee_bt.dm" #include "code\modules\mob\living\basic\farm_animals\chicken\chick.dm" #include "code\modules\mob\living\basic\farm_animals\chicken\chicken.dm" #include "code\modules\mob\living\basic\farm_animals\cow\_cow.dm" @@ -5589,10 +5702,10 @@ #include "code\modules\mob\living\basic\lavaland\mook\mook.dm" #include "code\modules\mob\living\basic\lavaland\mook\mook_abilities.dm" #include "code\modules\mob\living\basic\lavaland\mook\mook_ai.dm" +#include "code\modules\mob\living\basic\lavaland\mook\mook_bt.dm" #include "code\modules\mob\living\basic\lavaland\mook\mook_village.dm" #include "code\modules\mob\living\basic\lavaland\node_drone\node_drone.dm" #include "code\modules\mob\living\basic\lavaland\raptor\_raptor.dm" -#include "code\modules\mob\living\basic\lavaland\raptor\raptor_ai_behavior.dm" #include "code\modules\mob\living\basic\lavaland\raptor\raptor_ai_controller.dm" #include "code\modules\mob\living\basic\lavaland\raptor\raptor_ai_subtrees.dm" #include "code\modules\mob\living\basic\lavaland\raptor\raptor_color.dm" @@ -5617,12 +5730,11 @@ #include "code\modules\mob\living\basic\pets\pet.dm" #include "code\modules\mob\living\basic\pets\pet_designer.dm" #include "code\modules\mob\living\basic\pets\sloth.dm" -#include "code\modules\mob\living\basic\pets\cat\bread_cat_ai.dm" #include "code\modules\mob\living\basic\pets\cat\cat.dm" #include "code\modules\mob\living\basic\pets\cat\cat_ai.dm" +#include "code\modules\mob\living\basic\pets\cat\cat_bt.dm" #include "code\modules\mob\living\basic\pets\cat\feral.dm" #include "code\modules\mob\living\basic\pets\cat\keeki.dm" -#include "code\modules\mob\living\basic\pets\cat\kitten_ai.dm" #include "code\modules\mob\living\basic\pets\cat\runtime.dm" #include "code\modules\mob\living\basic\pets\dog\_dog.dm" #include "code\modules\mob\living\basic\pets\dog\corgi.dm" @@ -5646,6 +5758,7 @@ #include "code\modules\mob\living\basic\pets\penguin\penguin_ai.dm" #include "code\modules\mob\living\basic\pets\pet_cult\pet_cult_abilities.dm" #include "code\modules\mob\living\basic\pets\pet_cult\pet_cult_ai.dm" +#include "code\modules\mob\living\basic\pets\pet_cult\pet_cult_bt.dm" #include "code\modules\mob\living\basic\ruin_defender\blob_of_flesh.dm" #include "code\modules\mob\living\basic\ruin_defender\cybersun_aicore.dm" #include "code\modules\mob\living\basic\ruin_defender\dark_wizard.dm" @@ -5671,7 +5784,7 @@ #include "code\modules\mob\living\basic\slime\ai\behaviours.dm" #include "code\modules\mob\living\basic\slime\ai\controller.dm" #include "code\modules\mob\living\basic\slime\ai\pet_command.dm" -#include "code\modules\mob\living\basic\slime\ai\subtrees.dm" +#include "code\modules\mob\living\basic\slime\ai\slime_bt.dm" #include "code\modules\mob\living\basic\snails\snail.dm" #include "code\modules\mob\living\basic\snails\snail_ability.dm" #include "code\modules\mob\living\basic\snails\snail_ai.dm" @@ -5706,7 +5819,6 @@ #include "code\modules\mob\living\basic\space_fauna\demon\demon_subtypes.dm" #include "code\modules\mob\living\basic\space_fauna\eyeball\_eyeball.dm" #include "code\modules\mob\living\basic\space_fauna\eyeball\eyeball_ability.dm" -#include "code\modules\mob\living\basic\space_fauna\eyeball\eyeball_ai_behavior.dm" #include "code\modules\mob\living\basic\space_fauna\eyeball\eyeball_ai_subtree.dm" #include "code\modules\mob\living\basic\space_fauna\hivebot\_hivebot.dm" #include "code\modules\mob\living\basic\space_fauna\hivebot\hivebot_behavior.dm" @@ -5732,13 +5844,11 @@ #include "code\modules\mob\living\basic\space_fauna\revenant\revenant_objectives.dm" #include "code\modules\mob\living\basic\space_fauna\snake\banded_snake.dm" #include "code\modules\mob\living\basic\space_fauna\snake\snake.dm" -#include "code\modules\mob\living\basic\space_fauna\snake\snake_ai.dm" #include "code\modules\mob\living\basic\space_fauna\space_dragon\dragon_breath.dm" #include "code\modules\mob\living\basic\space_fauna\space_dragon\dragon_gust.dm" #include "code\modules\mob\living\basic\space_fauna\space_dragon\space_dragon.dm" #include "code\modules\mob\living\basic\space_fauna\spider\spider.dm" #include "code\modules\mob\living\basic\space_fauna\spider\giant_spider\giant_spider_ai.dm" -#include "code\modules\mob\living\basic\space_fauna\spider\giant_spider\giant_spider_subtrees.dm" #include "code\modules\mob\living\basic\space_fauna\spider\giant_spider\giant_spiders.dm" #include "code\modules\mob\living\basic\space_fauna\spider\spider_abilities\hivemind.dm" #include "code\modules\mob\living\basic\space_fauna\spider\spider_abilities\lay_eggs.dm" diff --git a/tgui/packages/tgui/interfaces/BehaviorTreeViewer/index.tsx b/tgui/packages/tgui/interfaces/BehaviorTreeViewer/index.tsx new file mode 100644 index 00000000000..98e72a7192e --- /dev/null +++ b/tgui/packages/tgui/interfaces/BehaviorTreeViewer/index.tsx @@ -0,0 +1,642 @@ +import { + createContext, + useCallback, + useContext, + useEffect, + useMemo, + useRef, + useState, +} from 'react'; +import { + Box, + Button, + InfinitePlane, + Section, + Stack, +} from 'tgui-core/components'; + +import { resolveAsset } from '../../assets'; +import { useBackend } from '../../backend'; +import { Window } from '../../layouts'; +import { + type BehaviorTreeViewerData, + type BlackboardEntry, + BT_ABORT_BOTH, + BT_ABORT_LOWER_PRIORITY, + BT_ABORT_NONE, + BT_ABORT_SELF, + BT_NODE_DECORATOR, + BT_NODE_LEAF, + BT_NODE_PARALLEL, + BT_NODE_SELECTOR, + BT_NODE_SEQUENCE, + BT_NODE_SUBPLAN, + BT_NODE_SUBTREE, + type BtNodeData, +} from './types'; + +const NODE_WIDTH = 160; +const NODE_HEIGHT = 70; +const H_GAP = 12; +const V_GAP = 48; +const FADE_DURATION = 1500; + +type AnimContextType = { + recentNodes: Map; + now: number; +}; +const AnimContext = createContext({ + recentNodes: new Map(), + now: 0, +}); + +function fadeOf( + recentNodes: Map, + execIndex: number, + now: number, +): number { + const ts = recentNodes.get(execIndex); + if (ts === undefined) return 0; + const age = now - ts; + return age >= FADE_DURATION ? 0 : 1 - age / FADE_DURATION; +} + +function lineColor(f: number): string { + if (f < 0.01) return '#555'; + const v = Math.round(85 + 170 * f); + return `rgb(${v}, ${v}, ${v})`; +} + +function nodeTypeBadge(nodeType: number): string { + switch (nodeType) { + case BT_NODE_SEQUENCE: + return 'SEQ'; + case BT_NODE_SELECTOR: + return 'SEL'; + case BT_NODE_PARALLEL: + return 'PAR'; + case BT_NODE_DECORATOR: + return 'DEC'; + case BT_NODE_SUBTREE: + return 'SUB'; + case BT_NODE_SUBPLAN: + return 'PLAN'; + default: + return ''; + } +} + +// Returns the last execution index in the node's subtree. +function lastExecIndex(node: BtNodeData): number { + return node.last_exec_index ?? node.exec_index; +} + +function childNodes( + node: BtNodeData, + nodeMap: Map, +): BtNodeData[] { + return (node.children ?? []).flatMap((idx) => { + const n = nodeMap.get(idx); + return n ? [n] : []; + }); +} + +// The pixel width that the subtree rooted at `node` will occupy. +function treeWidth(node: BtNodeData, nodeMap: Map): number { + const kids = childNodes(node, nodeMap); + if (kids.length === 0) return NODE_WIDTH; + const total = kids.reduce( + (sum, kid, i) => sum + treeWidth(kid, nodeMap) + (i > 0 ? H_GAP : 0), + 0, + ); + return Math.max(NODE_WIDTH, total); +} + +function nodeColor( + node: BtNodeData, + activeIdx: number, + selectedDec: BtNodeData | null, +): string { + if ( + node.node_type === BT_NODE_LEAF && + activeIdx > 0 && + activeIdx === node.exec_index + ) { + return 'green'; + } + if ( + activeIdx > 0 && + activeIdx >= node.exec_index && + activeIdx <= lastExecIndex(node) + ) { + return 'good'; + } + if (selectedDec) { + const abort = selectedDec.observer_abort ?? BT_ABORT_NONE; + const inRange = + node.exec_index >= selectedDec.exec_index && + node.exec_index <= lastExecIndex(selectedDec); + const lowerPriority = node.exec_index > lastExecIndex(selectedDec); + if ( + (abort === BT_ABORT_SELF && inRange) || + (abort === BT_ABORT_LOWER_PRIORITY && lowerPriority) || + (abort === BT_ABORT_BOTH && (inRange || lowerPriority)) + ) { + return 'orange'; + } + if (node.exec_index === selectedDec.exec_index) { + return 'blue'; + } + } + return 'default'; +} + +type BtNodeProps = { + nodeIdx: number; + nodeMap: Map; + activeIdx: number; + selectedDec: BtNodeData | null; + onSelectDec: (idx: number | null) => void; +}; + +function BtNodeTree(props: BtNodeProps) { + const { nodeIdx, nodeMap, activeIdx, selectedDec, onSelectDec } = props; + const node = nodeMap.get(nodeIdx); + if (!node) return null; + const color = nodeColor(node, activeIdx, selectedDec); + const badge = nodeTypeBadge(node.node_type); + const abort = node.observer_abort ?? BT_ABORT_NONE; + const kids = childNodes(node, nodeMap); + const { recentNodes, now } = useContext(AnimContext); + const nodeFade = fadeOf(recentNodes, nodeIdx, now); + const kidFades = kids.map((k) => fadeOf(recentNodes, k.exec_index, now)); + const isSelectedDec = + selectedDec !== null && node.exec_index === selectedDec.exec_index; + const isClickableDec = + node.node_type === BT_NODE_DECORATOR && abort !== BT_ABORT_NONE; + + function handleClick() { + if (!isClickableDec) return; + onSelectDec(isSelectedDec ? null : nodeIdx); + } + + let borderColor: string; + if (color === 'green' || color === 'good') { + borderColor = '#00cc44'; + } else if (color === 'orange') { + borderColor = '#ff8800'; + } else if (color === 'blue') { + borderColor = '#4488ff'; + } else { + borderColor = '#444'; + } + + const bgColor = color === 'green' ? '#005522' : '#1a1a1a'; + + const nodeTreeWidth = treeWidth(node, nodeMap); + const connectorCx = nodeTreeWidth / 2; + let cumChildX = 0; + const childCenters = kids.map((kid) => { + const center = cumChildX + treeWidth(kid, nodeMap) / 2; + cumChildX += treeWidth(kid, nodeMap) + H_GAP; + return center; + }); + + return ( +

+
0.01 + ? `0 0 ${Math.round(4 + nodeFade * 14)}px rgba(255, 255, 255, ${(nodeFade * 0.85).toFixed(2)})` + : undefined, + boxSizing: 'border-box', + }} + > + {/* Top row: badge + priority */} +
+ {badge} + #{node.priority} +
+ + {/* Label */} +
+ {node.label} +
+ + {/* Decorator badges */} + {node.node_type === BT_NODE_DECORATOR && ( +
+ {abort !== BT_ABORT_NONE && ( + + OBS + + )} + {node.invert && ( + + NOT + + )} + {abort === BT_ABORT_SELF && ( + SELF + )} + {abort === BT_ABORT_LOWER_PRIORITY && ( + LO-PRI + )} + {abort === BT_ABORT_BOTH && ( + BOTH + )} +
+ )} +
+ + {/* Children */} + {kids.length > 0 && ( + <> + + {/* Vertical from parent center down to junction */} + + {/* Per-child: horizontal arm from spine to child, then vertical down */} + {kids.map((kid, i) => { + const kx = childCenters[i]; + const fade = kidFades[i] ?? 0; + return ( + + + + + ); + })} + +
+ {kids.map((child) => ( + + ))} +
+ + )} +
+ ); +} + +function BlackboardPanel({ entries }: { entries: BlackboardEntry[] }) { + return ( +
+
+ Blackboard +
+
+ {entries.length === 0 ? ( +
+ No entries +
+ ) : ( + entries.map((entry) => ( +
+
+ {entry.key} +
+
+ {entry.value} +
+
+ )) + )} +
+
+ ); +} + +export function BehaviorTreeViewer() { + const { act, data } = useBackend(); + const { + mob_name, + controller_type, + active_execution_index, + fired_indices, + awaiting_pick, + roots, + nodes, + blackboard, + } = data; + + const nodeMap = useMemo(() => { + const map = new Map(); + for (const node of nodes) { + map.set(node.exec_index, node); + } + return map; + }, [nodes]); + + const [selectedDecIdx, setSelectedDecIdx] = useState(null); + const selectedDec = + selectedDecIdx !== null ? (nodeMap.get(selectedDecIdx) ?? null) : null; + + const recentNodesRef = useRef>(new Map()); + const animRafRef = useRef(null); + const [animNow, setAnimNow] = useState(() => Date.now()); + + const startAnimLoop = useCallback(() => { + if (animRafRef.current !== null) return; + const tick = () => { + const curNow = Date.now(); + let anyFading = false; + for (const ts of recentNodesRef.current.values()) { + if (curNow - ts < FADE_DURATION) { + anyFading = true; + break; + } + } + if (anyFading) { + setAnimNow(curNow); + animRafRef.current = requestAnimationFrame(tick); + } else { + animRafRef.current = null; + } + }; + animRafRef.current = requestAnimationFrame(tick); + }, []); + + useEffect(() => { + return () => { + if (animRafRef.current !== null) { + cancelAnimationFrame(animRafRef.current); + } + }; + }, []); + + useEffect(() => { + if (!fired_indices?.length && active_execution_index <= 0) return; + const curNow = Date.now(); + const toStamp = new Set(fired_indices ?? []); + if (active_execution_index > 0) toStamp.add(active_execution_index); + for (const node of nodes) { + for (const idx of toStamp) { + if ( + node.exec_index <= idx && + idx <= (node.last_exec_index ?? node.exec_index) + ) { + recentNodesRef.current.set(node.exec_index, curNow); + } + } + } + startAnimLoop(); + }, [active_execution_index, fired_indices, nodes, startAnimLoop]); + + const animCtxValue = useMemo( + () => ({ recentNodes: recentNodesRef.current, now: animNow }), + [animNow], + ); + + return ( + + +
+
+ + + {mob_name ? ( + + {mob_name} + + {controller_type} + + + ) : ( + No mob selected + )} + + + + + + + + + {selectedDec && ( + + Observer: {selectedDec.label} — highlighted nodes would + be cancelled. Click node again to deselect. + {(selectedDec.observed_keys?.length ?? 0) > 0 && ( + + Watching: {selectedDec.observed_keys!.join(', ')} + + )} + + )} +
+
+
+ +
+ + + {roots && roots.length > 0 && ( +
+ {roots.map((rootIdx) => ( + + ))} +
+ )} +
+
+ {(!roots || roots.length === 0) && ( + + {mob_name + ? 'No behavior nodes in controller.' + : 'Pick a mob to view its behavior tree.'} + + )} +
+
+
+
+ ); +} diff --git a/tgui/packages/tgui/interfaces/BehaviorTreeViewer/types.ts b/tgui/packages/tgui/interfaces/BehaviorTreeViewer/types.ts new file mode 100644 index 00000000000..8fcbee70fda --- /dev/null +++ b/tgui/packages/tgui/interfaces/BehaviorTreeViewer/types.ts @@ -0,0 +1,41 @@ +export type BlackboardEntry = { + key: string; // BB_* constant name + value: string; // stringified value +}; + +export type BehaviorTreeViewerData = { + mob_name: string | null; + controller_type: string | null; + active_execution_index: number; + fired_indices: number[]; // all leaf execution indices that fired since last poll + awaiting_pick: boolean; + roots: number[]; // execution indices of root nodes + nodes: BtNodeData[]; // flat list of all nodes in the tree + blackboard: BlackboardEntry[]; +}; + +// All dem nodes +export const BT_NODE_SELECTOR = 0; +export const BT_NODE_SEQUENCE = 1; +export const BT_NODE_PARALLEL = 2; +export const BT_NODE_DECORATOR = 3; +export const BT_NODE_LEAF = 4; +export const BT_NODE_SUBTREE = 5; +export const BT_NODE_SUBPLAN = 6; + +export type BtNodeData = { + exec_index: number; // unique key for this node instance + label: string; // display label + node_type: number; // BT_NODE_* constant + priority: number; // sibling priority (1-based) + last_exec_index?: number; // last execution index in subtree — omitted when same as exec_index + children?: number[]; // child exec_indices — omitted when no children + observer_abort?: 0 | 1 | 2 | 3; // abort scope — only on observing decorators + observed_keys?: string[]; // watched blackboard keys — only when non-empty + invert?: boolean; // condition is inverted — only when true +}; + +export const BT_ABORT_NONE = 0; +export const BT_ABORT_SELF = 1; +export const BT_ABORT_LOWER_PRIORITY = 2; +export const BT_ABORT_BOTH = 3; diff --git a/tools/build/build.ts b/tools/build/build.ts index 7bd395a9db2..ce44d51875b 100644 --- a/tools/build/build.ts +++ b/tools/build/build.ts @@ -180,6 +180,19 @@ export const DmMapsIncludeTarget = new Juke.Target({ }, }); +export const BehaviorTreeCompilerTarget = new Juke.Target({ + inputs: ['code/**/*.bt.json', 'code/__DEFINES/**/*.dm'], + outputs: () => { + return Juke.glob('code/**/*.bt.json').map((file) => { + const rel = file.replace(/^code\//, '').replace(/\.bt\.json$/, ''); + return `build/behavior_trees/${rel}.bt.compiled.json`; + }); + }, + executes: async () => { + await Juke.exec('python', ['tools/build_bt.py']); + }, +}); + export const DmTarget = new Juke.Target({ parameters: [ DefineParameter, @@ -191,6 +204,7 @@ export const DmTarget = new Juke.Target({ dependsOn: ({ get }) => [ get(DefineParameter).includes('ALL_TEMPLATES') && DmMapsIncludeTarget, !get(SkipIconCutter) && IconCutterTarget, + BehaviorTreeCompilerTarget, ], inputs: [ '_maps/map_files/generic/**', diff --git a/tools/build_bt.py b/tools/build_bt.py new file mode 100644 index 00000000000..805d5d3d60f --- /dev/null +++ b/tools/build_bt.py @@ -0,0 +1,349 @@ +#!/usr/bin/env python3 +""" +Behavior tree JSON compiler/parser. Converts the .bt.json formated files +into a compacted and runtime-readable converted json format. + +It does this by parsing all defines and replacing the defines in the json with their values. It also checks some specific macros like SECONDS, MINUTES and HOURS. + +This lets us use defines in the JSON :D + +Usage: + python tools/build_bt.py [--repo-root PATH] [--check] + +Options: + --check Verify that all generated files are up to date without writing anything. + Exits with code 1 if any file differs. Used by continious int (hopefully). +""" + +import ast +import json +import os +import re +import sys +import warnings +from pathlib import Path + +# The time macros, so we can use them in JSON files +TIMING_SUBS = [ + (re.compile(r'\bHOURS\b'), '*36000'), + (re.compile(r'\bMINUTES\b'), '*600'), + (re.compile(r'\bSECONDS\b'), '*10'), +] + +# Matches any DM identifier; used for single-pass define substitution. +_IDENT_RE = re.compile(r'\b([A-Za-z_]\w*)\b') + +# Shorthand for our nodes that never change, the others get subtyped, these in theory dont. +STATIC_NODES: dict[str, str] = { + 'selector': '/datum/bt_node/composite/selector', + 'sequence': '/datum/bt_node/composite/sequence', + 'parallel': '/datum/bt_node/composite/parallel', + 'subplan': '/datum/bt_node/composite/subplan', +} + +# Source JSON structural keys that are consumed/transformed during compilation. please keep these updated if u add new stuff :D +_CONSUMED_KEYS = frozenset({'type', 'children', 'child', 'decorator', 'behavior', 'vars', 'subtype', 'dm_type', 'bindings'}) + + + +def parse_defines(repo_root: Path) -> dict: + """ + Scan all .dm files under code/__DEFINES/ and resolve every #define to a + Python int, float, or str value. + + Uses multi-pass resolution so that defines referencing other defines work + regardless of file or declaration order. Stops when a full pass makes no + new progress. + """ + defines: dict = { + 'TRUE': 1, + 'FALSE': 0, + 'null': None, + } + + pending: list[tuple[str, str]] = [] + seen_names: set[str] = set(defines) + defines_dir = repo_root / 'code' + for fpath in sorted(defines_dir.rglob('*.dm')): + for line in fpath.read_text(encoding='utf-8', errors='ignore').splitlines(): + line = line.strip() + if not line.startswith('#define '): + continue + rest = line[len('#define '):] + parts = rest.split(None, 1) + if len(parts) < 2: + continue + name, raw = parts[0], parts[1].strip() + raw = re.sub(r'\s*//.*$', '', raw).strip() + if not raw or name in seen_names: + continue # value-less define or already seen (first wins) + seen_names.add(name) + pending.append((name, raw)) + + while pending: + resolved_this_pass = 0 + still_pending: list[tuple[str, str]] = [] + for name, raw in pending: + value = _resolve_expr(raw, defines) + if value is not None: + defines[name] = value + resolved_this_pass += 1 + else: + still_pending.append((name, raw)) + pending = still_pending + if resolved_this_pass == 0: + break # no new defines found + + return defines + + +def _resolve_expr(raw: str, defines: dict): + """ + Try to resolve a raw define RHS string to a Python number or str. + Returns None if the expression cannot be evaluated. + """ + def _sub(m): + name = m.group(1) + if name not in defines: + return name + val = defines[name] + if val is None: + return 'None' + if isinstance(val, str): + return repr(val) + if isinstance(val, (int, float)): + return str(val) + return name + + expr = _IDENT_RE.sub(_sub, raw) + + # Apply DM postfix timing operators + for pattern, replacement in TIMING_SUBS: + expr = pattern.sub(replacement, expr) + + expr = expr.strip() + + try: + with warnings.catch_warnings(): + warnings.simplefilter('ignore', SyntaxWarning) + val = ast.literal_eval(expr) + if isinstance(val, (int, float, str)): + return val + return None + except Exception: + pass + + try: + with warnings.catch_warnings(): + warnings.simplefilter('ignore', SyntaxWarning) + val = eval(expr, {'__builtins__': {}}, {}) # noqa: S307 + if isinstance(val, (int, float)): + return val + except Exception: + pass + + return None + + +def _split_list_args(inner: str) -> list[str]: + """Split comma-separated args respecting nested parentheses.""" + args: list[str] = [] + depth = 0 + current: list[str] = [] + for ch in inner: + if ch == ',' and depth == 0: + args.append(''.join(current).strip()) + current = [] + else: + if ch == '(': + depth += 1 + elif ch == ')': + depth -= 1 + current.append(ch) + if current: + args.append(''.join(current).strip()) + return [a for a in args if a] + + +def resolve_value(val, defines: dict): + """Resolve a JSON value: define lookup, then arithmetic/timing expression, then raw. + Strings matching list(...) are parsed into Python lists with each element resolved.""" + if not isinstance(val, str): + return val + if val.startswith('$'): + return val # binding reference placeholder — preserve as-is + stripped = val.strip() + if stripped.startswith('list(') and stripped.endswith(')'): + inner = stripped[5:-1] + return [resolve_value(a, defines) for a in _split_list_args(inner)] + if val in defines: + return defines[val] + resolved = _resolve_expr(val, defines) + return resolved if resolved is not None else val + + +def compile_node(src: dict, defines: dict) -> dict: + """Recursively compile one source JSON node into a DM descriptor dict.""" + # Read structural keys from defines + desc_type = defines.get('BT_DESC_TYPE', 'type') + desc_children = defines.get('BT_DESC_CHILDREN', 'children') + + node_type = src.get('type', '') + out: dict = {} + + #Resolve what typepath we need to use for the node + if node_type in STATIC_NODES: + out[desc_type] = STATIC_NODES[node_type] + elif node_type == 'decorator': + out[desc_type] = src['decorator'] + elif node_type == 'leaf': + out[desc_type] = src['behavior'] + elif node_type == 'subtree': + out[desc_type] = src.get('subtype', '/datum/bt_node/subtree') + else: + raise ValueError(f'Unknown node type: {node_type!r}') + + # Setup children nodes recursively + if 'children' in src: + out[desc_children] = [compile_node(c, defines) for c in src['children']] + elif 'child' in src: + out[desc_children] = [compile_node(src['child'], defines)] + + # Instance vars — "" means omit the key (cuz then we use the default) + for key, val in src.get('vars', {}).items(): + rv = resolve_value(val, defines) + if rv != '': + out[key] = rv + + # Bindings: declaration on a subtree definition file's root vs call-site overrides + if 'bindings' in src: + if node_type == 'subtree': + # Call-site overrides: resolve values through defines, emit as "bindings" (becomes node.vars) + # Empty string means "use the default from the declaration" — omit the key. + out['bindings'] = {k: rv for k, v in src['bindings'].items() if (rv := resolve_value(v, defines)) != ''} + else: + # Declaration: emit as "__bindings" with label + resolved default (consumed by DM runtime) + out['__bindings'] = { + name: {'label': info.get('label', name), 'default': resolve_value(info.get('default'), defines)} + for name, info in src['bindings'].items() + } + + # anything else + for key, val in src.items(): + if key in _CONSUMED_KEYS: + continue + out[key] = resolve_value(val, defines) + + return out + + +def main() -> int: + check_mode = '--check' in sys.argv + + # Determine repo root (two levels up from this script, IDK if theres a better way to do this) + repo_root = Path(__file__).resolve().parent.parent + for arg in sys.argv[1:]: + if arg.startswith('--repo-root='): + repo_root = Path(arg.split('=', 1)[1]).resolve() + elif arg == '--repo-root' and sys.argv.index(arg) + 1 < len(sys.argv): + idx = sys.argv.index(arg) + repo_root = Path(sys.argv[idx + 1]).resolve() + + generated_dir = repo_root / 'build' / 'behavior_trees' + code_dir = repo_root / 'code' + generated_dir.mkdir(parents=True, exist_ok=True) + + print('Parsing DM defines...') + defines = parse_defines(repo_root) + print(f' Resolved {len(defines)} defines.') + + bt_files = sorted(repo_root.glob('code/**/*.bt.json')) + print(f'Found {len(bt_files)} .bt.json source files.') + + errors = 0 + dirty = 0 + # Maps each target compiled path back to the source that produced it, so we can + # detect two sources colliding onto one output. shouldn't happen, can happen. + produced: dict[Path, Path] = {} + generated_paths: set[Path] = set() + + for src_path in bt_files: + # The compiled file mirrors the source path relative to code/, so trees that share a basename dont fucking break. + rel = src_path.relative_to(code_dir).as_posix() # "datums/ai/dog/dog.bt.json" + tree_name = rel[:-len('.json')] # "datums/ai/dog/dog.bt" + compiled_path = generated_dir / f'{tree_name}.compiled.json' + + prior = produced.get(compiled_path) + if prior is not None: + print( + f'ERROR: {src_path.relative_to(repo_root)} and {prior.relative_to(repo_root)} ' + f'both compile to {compiled_path.relative_to(repo_root)}', + file=sys.stderr, + ) + errors += 1 + continue + produced[compiled_path] = src_path + generated_paths.add(compiled_path) + + # compile json + try: + src_json = json.loads(src_path.read_text(encoding='utf-8')) + except Exception as exc: + print(f'ERROR reading {src_path.relative_to(repo_root)}: {exc}', file=sys.stderr) + errors += 1 + continue + + try: + compiled = compile_node(src_json, defines) + except Exception as exc: + print(f'ERROR compiling {src_path.relative_to(repo_root)}: {exc}', file=sys.stderr) + errors += 1 + continue + + compiled_text = json.dumps(compiled, separators=(',', ':')) + '\n' + + # either write or check depending on flag + if check_mode: + existing = compiled_path.read_text(encoding='utf-8') if compiled_path.exists() else '' + if existing != compiled_text: + print(f'OUT OF DATE: {compiled_path.relative_to(repo_root)}', file=sys.stderr) + dirty += 1 + else: + compiled_path.parent.mkdir(parents=True, exist_ok=True) + compiled_path.write_text(compiled_text, encoding='utf-8') + + # Remove stale compiled files that no longer correspond to a source tree — + stale = [p for p in generated_dir.rglob('*.compiled.json') if p not in generated_paths] + for path in stale: + if check_mode: + print(f'STALE: {path.relative_to(repo_root)}', file=sys.stderr) + dirty += 1 + else: + path.unlink() + + if not check_mode: + # Prune now-empty directories left behind under the generated tree. + for path in sorted(generated_dir.rglob('*'), reverse=True): + if path.is_dir() and not any(path.iterdir()): + path.rmdir() + + if check_mode: + if dirty: + print( + f'\n{dirty} file(s) are out of date. Run `python tools/build_bt.py` to regenerate.', + file=sys.stderr, + ) + return 1 + print('All generated BT files are up to date.') + return 0 + + if errors: + print(f'\n{errors} error(s) encountered.', file=sys.stderr) + return 1 + + print('Done.') + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/tools/ci/run_server.sh b/tools/ci/run_server.sh index 0240a6deb26..055a2d67b8b 100644 --- a/tools/ci/run_server.sh +++ b/tools/ci/run_server.sh @@ -7,6 +7,7 @@ MAP_CONFIG=${2:-""} echo Testing $MAP tools/deploy.sh ci_test + mkdir -p ci_test/config mkdir -p ci_test/data diff --git a/tools/deploy.sh b/tools/deploy.sh index c13ba0639fd..5135e5c9f15 100755 --- a/tools/deploy.sh +++ b/tools/deploy.sh @@ -11,6 +11,7 @@ fi mkdir -p \ $1/_maps \ + $1/build/behavior_trees \ $1/code/datums/greyscale/json_configs \ $1/data/spritesheets \ $1/icons \ @@ -26,6 +27,7 @@ fi cp tgstation.dmb tgstation.rsc $1/ cp -r _maps/* $1/_maps/ +cp -r build/behavior_trees/* $1/build/behavior_trees/ cp -r code/datums/greyscale/json_configs/* $1/code/datums/greyscale/json_configs/ cp -r icons/* $1/icons/ cp -r sound/runtime/* $1/sound/runtime/