mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-23 13:10:02 +01:00
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 <img width="1795" height="1268" alt="image" src="https://github.com/user-attachments/assets/56aa2f0b-3cf9-449f-bca4-8281fca82db6" /> 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. 🆑 CabinetOnFire, Iamgoofball, SmartKar, Ben10omintrix refactor: Replaces our AI system with behavior trees, porting all datum/ai to it /🆑 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 <iamgoofball@gmail.com> 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>
This commit is contained in:
committed by
The Sharkenning
co-authored by
Iamgoofball
SmArtKar
Ghom
Ben10Omintrix
SyncIt21
parent
ad0d6a3e7d
commit
df7832aa43
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
+132
@@ -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
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
. = ..()
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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."
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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 ..()
|
||||
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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]
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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 ..()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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("<b>[super_beeps]</b> points at [blackboard[BB_BASIC_MOB_CURRENT_TARGET]]!")
|
||||
super_beeps.visible_message("<b>[super_beeps]</b> points at [blackboard[BB_CURRENT_TARGET]]!")
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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(
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"dm_type": "/datum/ai_controller/basic_controller/juggernaut",
|
||||
"type": "subtree",
|
||||
"subtype": "/datum/bt_node/subtree/simple_hostile_obstacles_combat"
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"dm_type": "/datum/ai_controller/basic_controller/chicken",
|
||||
"type": "subtree",
|
||||
"subtype": "/datum/bt_node/subtree/skittish_and_speak"
|
||||
}
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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?")
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user