mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-25 06:00:16 +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
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user