mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2025-12-10 01:34:01 +00:00
[UPSTREAM] [NO GBP] Slime AI fixes + prerequisites (#1432)
…not idle) (#27470) * Reworks targeting behavior to fall back onto proximity monitors. Refactors ai cooldowns a bit (#82640) Nother bit ripped out of #79498 [Implements a get_cooldown() proc to get around dumb manual overrides and empower me to optimize the findtarget logic](7047d294dd) [Adds modify_cooldown, uses it to optimize find_potential_targets further](4ebc8cedce) No sense running the behavior if we're just waiting on its output, so let's run it once a minute just in case, then push an update instantly if we find something [Optimizes connect_range and promxity_monitors](bcf7d7c5b3) We know what turfs exist before and after a move We can use this information to prevent trying to update turfs we don't care about. This is important because post these changes mobs with fields will be moving a lot more, so it's gotta be cheap [Implements a special kind of field to handle ai targeting](80b63b3445) If we run targeting and don't like, find anything, we should setup a field that listens for things coming near us and then handle those things as we find them. This incurs a slight startup cost but saves so much time on the churn of constant costs Note: We should also work to figure out a way to avoid waking ais if none is near them/they aren't doing anything interesting We don't need to do that immediately this acts as somewhat of a stopgap (and would be good regardless) but it is worth keeping in mind) I am unsure whether this is worth it anymore since #82539 was merged. As I say it was done as a stopgap because ais didn't know how to idle. If not I'll rip er out and we'll keep the other refactoring/optimizations. Cleaner basic ai code, maybe? faster basic ai code, for sure faster proximity monitors (significantly) * ai controllers use cell trackers to know when to idle (#82691) this makes ai controllers use cell trackers and signals to determine when to idle might be better than looping over all clients for every controller 🆑 code: The way mobs idle has been refactored, please report any issues with non-reactive mobs /🆑 * makes slimes not idle (#82742) slimes should still be able to do their everyday routine without needing to be watched over makes xenobiologist's lives easier 🆑 qol: slimes will stay active without needing any one to watch over /🆑 --------- <!-- Write **BELOW** The Headers and **ABOVE** The comments else it may not be viewable. --> <!-- You can view Contributing.MD for a detailed description of the pull request process. --> ## About The Pull Request <!-- Describe The Pull Request. Please be sure every change is documented or this can delay review and even discourage maintainers from merging your PR! --> <!-- Please make sure to actually test your PRs. If you have not tested your PR mention it. --> ## Why It's Good For The Game <!-- Argue for the merits of your changes and how they benefit the game, especially if they are controversial and/or far reaching. If you can't actually explain WHY what you are doing will improve the game, then it probably isn't good for the game in the first place. --> ## Proof Of Testing <!-- Compile and run your code locally. Make sure it works. This is the place to show off your changes! We are not responsible for testing your features. --> ## Changelog <!-- If your PR modifies aspects of the game that can be concretely observed by players or admins you should add a changelog. If your change does NOT meet this description, remove this section. Be sure to properly mark your PRs to prevent unnecessary GBP loss. You can read up on GBP and it's effects on PRs in the tgstation guides for contributors. Please note that maintainers freely reserve the right to remove and add tags should they deem it appropriate. You can attempt to finagle the system all you want, but it's best to shoot for clear communication right off the bat. --> 🆑 add: Added new mechanics or gameplay changes add: Added more things del: Removed old things qol: made something easier to use balance: rebalanced something fix: fixed a few things sound: added/modified/removed audio or sound effects image: added/modified/removed some icons or images spellcheck: fixed a few typos code: changed some code refactor: refactored some code config: changed some config setting admin: messed with admin stuff server: something server ops should know /🆑 <!-- Both 🆑's are required for the changelog to work! You can put your name to the right of the first 🆑 if you want to overwrite your GitHub username as author ingame. --> <!-- You can use multiple of the same prefix (they're only used for the icon ingame) and delete the unneeded ones. Despite some of the tags, changelogs should generally represent how a player might be affected by the changes rather than a summary of the PR's contents. --> <!-- By opening a pull request. You have read and understood the repository rules located on the main README.md on this project. --> --------- Co-authored-by: Waterpig <49160555+Majkl-J@users.noreply.github.com> Co-authored-by: LemonInTheDark <58055496+LemonInTheDark@users.noreply.github.com> Co-authored-by: Ben10Omintrix <138636438+Ben10Omintrix@users.noreply.github.com> Co-authored-by: SkyratBot <59378654+SkyratBot@users.noreply.github.com> Co-authored-by: Iamgoofball <iamgoofball@gmail.com> Co-authored-by: Waterpig <wtryoutube@seznam.cz>
This commit is contained in:
@@ -2,17 +2,23 @@
|
||||
#define GET_TARGETING_STRATEGY(targeting_type) SSai_behaviors.targeting_strategies[targeting_type]
|
||||
#define HAS_AI_CONTROLLER_TYPE(thing, type) istype(thing?.ai_controller, type)
|
||||
|
||||
|
||||
//AI controller flags
|
||||
//If you add a new status, be sure to add it to the ai_controllers subsystem's ai_controllers_by_status list.
|
||||
///The AI is currently active.
|
||||
#define AI_STATUS_ON "ai_on"
|
||||
///The AI is currently offline for any reason.
|
||||
#define AI_STATUS_OFF "ai_off"
|
||||
///The AI is currently in idle mode.
|
||||
#define AI_STATUS_IDLE "ai_idle"
|
||||
|
||||
///For JPS pathing, the maximum length of a path we'll try to generate. Should be modularized depending on what we're doing later on
|
||||
#define AI_MAX_PATH_LENGTH 30 // 30 is possibly overkill since by default we lose interest after 14 tiles of distance, but this gives wiggle room for weaving around obstacles
|
||||
#define AI_BOT_PATH_LENGTH 150
|
||||
|
||||
// How far should we, by default, be looking for interesting things to de-idle?
|
||||
#define AI_DEFAULT_INTERESTING_DIST 10
|
||||
|
||||
///Cooldown on planning if planning failed last time
|
||||
|
||||
#define AI_FAILED_PLANNING_COOLDOWN (1.5 SECONDS)
|
||||
|
||||
@@ -144,3 +144,8 @@
|
||||
|
||||
///Text we display when we befriend someone
|
||||
#define BB_FRIENDLY_MESSAGE "friendly_message"
|
||||
|
||||
// Keys used by one and only one behavior
|
||||
// Used to hold state without making bigass lists
|
||||
/// For /datum/ai_behavior/find_potential_targets, what if any field are we using currently
|
||||
#define BB_FIND_TARGETS_FIELD(type) "bb_find_targets_field_[type]"
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
|
||||
///sent from ai controllers when they possess a pawn: (datum/ai_controller/source_controller)
|
||||
#define COMSIG_AI_CONTROLLER_POSSESSED_PAWN "ai_controller_possessed_pawn"
|
||||
///sent from ai controllers when they pick behaviors: (list/datum/ai_behavior/old_behaviors, list/datum/ai_behavior/new_behaviors)
|
||||
#define COMSIG_AI_CONTROLLER_PICKED_BEHAVIORS "ai_controller_picked_behaviors"
|
||||
///sent from ai controllers when a behavior is inserted into the queue: (list/new_arguments)
|
||||
#define AI_CONTROLLER_BEHAVIOR_QUEUED(type) "ai_controller_behavior_queued_[type]"
|
||||
|
||||
@@ -13,9 +13,14 @@ SUBSYSTEM_DEF(ai_controllers)
|
||||
var/list/ai_controllers_by_status = list(
|
||||
AI_STATUS_ON = list(),
|
||||
AI_STATUS_OFF = list(),
|
||||
AI_STATUS_IDLE = list(),
|
||||
)
|
||||
///Assoc List of all AI controllers and the Z level they are on, which we check when someone enters/leaves a Z level to turn them on/off.
|
||||
var/list/ai_controllers_by_zlevel = list()
|
||||
/// The tick cost of all active AI, calculated on fire.
|
||||
var/cost_on
|
||||
/// The tick cost of all idle AI, calculated on fire.
|
||||
var/cost_idle
|
||||
|
||||
/datum/controller/subsystem/ai_controllers/Initialize()
|
||||
setup_subtrees()
|
||||
@@ -24,10 +29,15 @@ SUBSYSTEM_DEF(ai_controllers)
|
||||
/datum/controller/subsystem/ai_controllers/stat_entry(msg)
|
||||
var/list/active_list = ai_controllers_by_status[AI_STATUS_ON]
|
||||
var/list/inactive_list = ai_controllers_by_status[AI_STATUS_OFF]
|
||||
msg = "Active AIs:[length(active_list)]|Inactive:[length(inactive_list)]"
|
||||
var/list/idle_list = ai_controllers_by_status[AI_STATUS_IDLE]
|
||||
msg = "Active AIs:[length(active_list)]/[round(cost_on,1)]%|Inactive:[length(inactive_list)]|Idle:[length(idle_list)]/[round(cost_idle,1)]%"
|
||||
return ..()
|
||||
|
||||
/datum/controller/subsystem/ai_controllers/fire(resumed)
|
||||
var/timer = TICK_USAGE_REAL
|
||||
cost_idle = MC_AVERAGE(cost_idle, TICK_DELTA_TO_MS(TICK_USAGE_REAL - timer))
|
||||
|
||||
timer = TICK_USAGE_REAL
|
||||
for(var/datum/ai_controller/ai_controller as anything in ai_controllers_by_status[AI_STATUS_ON])
|
||||
if(!COOLDOWN_FINISHED(ai_controller, failed_planning_cooldown))
|
||||
continue
|
||||
@@ -38,6 +48,8 @@ SUBSYSTEM_DEF(ai_controllers)
|
||||
if(!LAZYLEN(ai_controller.current_behaviors)) //Still no plan
|
||||
COOLDOWN_START(ai_controller, failed_planning_cooldown, AI_FAILED_PLANNING_COOLDOWN)
|
||||
|
||||
cost_on = MC_AVERAGE(cost_on, TICK_DELTA_TO_MS(TICK_USAGE_REAL - timer))
|
||||
|
||||
///Creates all instances of ai_subtrees and assigns them to the ai_subtrees list.
|
||||
/datum/controller/subsystem/ai_controllers/proc/setup_subtrees()
|
||||
for(var/subtree_type in subtypesof(/datum/ai_planning_subtree))
|
||||
|
||||
@@ -5,8 +5,14 @@
|
||||
///Flags for extra behavior
|
||||
var/behavior_flags = NONE
|
||||
///Cooldown between actions performances, defaults to the value of CLICK_CD_MELEE because that seemed like a nice standard for the speed of AI behavior
|
||||
///Do not read directly or mutate, instead use get_cooldown()
|
||||
var/action_cooldown = CLICK_CD_MELEE
|
||||
|
||||
/// Returns the delay to use for this behavior in the moment
|
||||
/// Override to return a conditional delay
|
||||
/datum/ai_behavior/proc/get_cooldown(datum/ai_controller/cooldown_for)
|
||||
return action_cooldown
|
||||
|
||||
/// Called by the ai controller when first being added. Additional arguments depend on the behavior type.
|
||||
/// Return FALSE to cancel
|
||||
/datum/ai_behavior/proc/setup(datum/ai_controller/controller, ...)
|
||||
@@ -14,7 +20,7 @@
|
||||
|
||||
///Called by the AI controller when this action is performed
|
||||
/datum/ai_behavior/proc/perform(seconds_per_tick, datum/ai_controller/controller, ...)
|
||||
controller.behavior_cooldowns[src] = world.time + action_cooldown
|
||||
controller.behavior_cooldowns[src] = world.time + get_cooldown(controller)
|
||||
return
|
||||
|
||||
///Called when the action is finished. This needs the same args as perform besides the default ones
|
||||
|
||||
@@ -46,6 +46,8 @@ multiple modular subtrees with behaviors
|
||||
|
||||
///The idle behavior this AI performs when it has no actions.
|
||||
var/datum/idle_behavior/idle_behavior = null
|
||||
///our current cell grid
|
||||
var/datum/cell_tracker/our_cells
|
||||
|
||||
// Movement related things here
|
||||
///Reference to the movement datum we use. Is a type on initialize but becomes a ref afterwards.
|
||||
@@ -56,6 +58,10 @@ multiple modular subtrees with behaviors
|
||||
// The variables below are fucking stupid and should be put into the blackboard at some point.
|
||||
///AI paused time
|
||||
var/paused_until = 0
|
||||
///Can this AI idle?
|
||||
var/can_idle = TRUE
|
||||
///What distance should we be checking for interesting things when considering idling/deidling? Defaults to AI_DEFAULT_INTERESTING_DIST
|
||||
var/interesting_dist = AI_DEFAULT_INTERESTING_DIST
|
||||
|
||||
/datum/ai_controller/New(atom/new_pawn)
|
||||
change_ai_movement_type(ai_movement)
|
||||
@@ -69,6 +75,7 @@ multiple modular subtrees with behaviors
|
||||
|
||||
/datum/ai_controller/Destroy(force)
|
||||
UnpossessPawn(FALSE)
|
||||
our_cells = null
|
||||
set_movement_target(type, null)
|
||||
if(ai_movement.moving_controllers[src])
|
||||
ai_movement.stop_moving_towards(src)
|
||||
@@ -127,6 +134,61 @@ multiple modular subtrees with behaviors
|
||||
RegisterSignal(pawn, COMSIG_MOB_LOGIN, PROC_REF(on_sentience_gained))
|
||||
RegisterSignal(pawn, COMSIG_QDELETING, PROC_REF(on_pawn_qdeleted))
|
||||
|
||||
our_cells = new(interesting_dist, interesting_dist, 1)
|
||||
set_new_cells()
|
||||
|
||||
RegisterSignal(pawn, COMSIG_MOVABLE_MOVED, PROC_REF(update_grid))
|
||||
|
||||
/datum/ai_controller/proc/update_grid(datum/source, datum/spatial_grid_cell/new_cell)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
set_new_cells()
|
||||
|
||||
/datum/ai_controller/proc/set_new_cells()
|
||||
if(isnull(our_cells))
|
||||
return
|
||||
|
||||
var/turf/our_turf = get_turf(pawn)
|
||||
|
||||
if(isnull(our_turf))
|
||||
return
|
||||
|
||||
var/list/cell_collections = our_cells.recalculate_cells(our_turf)
|
||||
|
||||
for(var/datum/old_grid as anything in cell_collections[2])
|
||||
UnregisterSignal(old_grid, list(SPATIAL_GRID_CELL_ENTERED(SPATIAL_GRID_CONTENTS_TYPE_CLIENTS), SPATIAL_GRID_CELL_EXITED(SPATIAL_GRID_CONTENTS_TYPE_CLIENTS)))
|
||||
|
||||
for(var/datum/spatial_grid_cell/new_grid as anything in cell_collections[1])
|
||||
RegisterSignal(new_grid, SPATIAL_GRID_CELL_ENTERED(SPATIAL_GRID_CONTENTS_TYPE_CLIENTS), PROC_REF(on_client_enter))
|
||||
RegisterSignal(new_grid, SPATIAL_GRID_CELL_EXITED(SPATIAL_GRID_CONTENTS_TYPE_CLIENTS), PROC_REF(on_client_exit))
|
||||
|
||||
recalculate_idle()
|
||||
|
||||
/datum/ai_controller/proc/should_idle()
|
||||
if(!can_idle || isnull(our_cells))
|
||||
return FALSE
|
||||
for(var/datum/spatial_grid_cell/grid as anything in our_cells.member_cells)
|
||||
if(length(grid.client_contents))
|
||||
return FALSE
|
||||
return TRUE
|
||||
|
||||
/datum/ai_controller/proc/recalculate_idle()
|
||||
if(ai_status == AI_STATUS_OFF)
|
||||
return
|
||||
if(should_idle())
|
||||
set_ai_status(AI_STATUS_IDLE)
|
||||
|
||||
/datum/ai_controller/proc/on_client_enter(datum/source, atom/target)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
if(ai_status == AI_STATUS_IDLE)
|
||||
set_ai_status(AI_STATUS_ON)
|
||||
|
||||
/datum/ai_controller/proc/on_client_exit(datum/source, datum/exited)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
recalculate_idle()
|
||||
|
||||
/// Sets the AI on or off based on current conditions, call to reset after you've manually disabled it somewhere
|
||||
/datum/ai_controller/proc/reset_ai_status()
|
||||
set_ai_status(get_expected_ai_status())
|
||||
@@ -137,6 +199,7 @@ multiple modular subtrees with behaviors
|
||||
* Returns AI_STATUS_ON otherwise.
|
||||
*/
|
||||
/datum/ai_controller/proc/get_expected_ai_status()
|
||||
|
||||
if (!ismob(pawn))
|
||||
return AI_STATUS_ON
|
||||
|
||||
@@ -148,7 +211,7 @@ multiple modular subtrees with behaviors
|
||||
if(ai_traits & CAN_ACT_WHILE_DEAD)
|
||||
return AI_STATUS_ON
|
||||
return AI_STATUS_OFF
|
||||
|
||||
|
||||
var/turf/pawn_turf = get_turf(mob_pawn)
|
||||
#ifdef TESTING
|
||||
if(!pawn_turf)
|
||||
@@ -156,6 +219,8 @@ multiple modular subtrees with behaviors
|
||||
#endif
|
||||
if(!length(SSmobs.clients_by_zlevel[pawn_turf.z]))
|
||||
return AI_STATUS_OFF
|
||||
if(should_idle())
|
||||
return AI_STATUS_IDLE
|
||||
return AI_STATUS_ON
|
||||
|
||||
/datum/ai_controller/proc/get_current_turf()
|
||||
@@ -175,7 +240,7 @@ multiple modular subtrees with behaviors
|
||||
SSai_controllers.ai_controllers_by_zlevel[new_turf.z] += src
|
||||
var/new_level_clients = SSmobs.clients_by_zlevel[new_turf.z].len
|
||||
if(new_level_clients)
|
||||
set_ai_status(AI_STATUS_ON)
|
||||
set_ai_status(AI_STATUS_IDLE)
|
||||
else
|
||||
set_ai_status(AI_STATUS_OFF)
|
||||
|
||||
@@ -210,7 +275,6 @@ multiple modular subtrees with behaviors
|
||||
return FALSE
|
||||
return TRUE
|
||||
|
||||
|
||||
///Runs any actions that are currently running
|
||||
/datum/ai_controller/process(seconds_per_tick)
|
||||
|
||||
@@ -238,7 +302,7 @@ multiple modular subtrees with behaviors
|
||||
// Convert the current behaviour action cooldown to realtime seconds from deciseconds.current_behavior
|
||||
// Then pick the max of this and the seconds_per_tick passed to ai_controller.process()
|
||||
// Action cooldowns cannot happen faster than seconds_per_tick, so seconds_per_tick should be the value used in this scenario.
|
||||
var/action_seconds_per_tick = max(current_behavior.action_cooldown * 0.1, seconds_per_tick)
|
||||
var/action_seconds_per_tick = max(current_behavior.get_cooldown(src) * 0.1, seconds_per_tick)
|
||||
|
||||
if(current_behavior.behavior_flags & AI_BEHAVIOR_REQUIRE_MOVEMENT) //Might need to move closer
|
||||
if(!current_movement_target)
|
||||
@@ -294,6 +358,7 @@ multiple modular subtrees with behaviors
|
||||
if(subtree.SelectBehaviors(src, seconds_per_tick) == SUBTREE_RETURN_FINISH_PLANNING)
|
||||
break
|
||||
|
||||
SEND_SIGNAL(src, COMSIG_AI_CONTROLLER_PICKED_BEHAVIORS, current_behaviors, planned_behaviors)
|
||||
for(var/datum/ai_behavior/current_behavior as anything in current_behaviors)
|
||||
if(LAZYACCESS(planned_behaviors, current_behavior))
|
||||
continue
|
||||
@@ -307,7 +372,7 @@ multiple modular subtrees with behaviors
|
||||
/datum/ai_controller/proc/set_ai_status(new_ai_status)
|
||||
if(ai_status == new_ai_status)
|
||||
return FALSE //no change
|
||||
|
||||
|
||||
//remove old status, if we've got one
|
||||
if(ai_status)
|
||||
SSai_controllers.ai_controllers_by_status[ai_status] -= src
|
||||
@@ -319,10 +384,16 @@ multiple modular subtrees with behaviors
|
||||
if(AI_STATUS_OFF)
|
||||
STOP_PROCESSING(SSai_behaviors, src)
|
||||
CancelActions()
|
||||
if(AI_STATUS_IDLE)
|
||||
STOP_PROCESSING(SSai_behaviors, src)
|
||||
CancelActions()
|
||||
|
||||
/datum/ai_controller/proc/PauseAi(time)
|
||||
paused_until = world.time + time
|
||||
|
||||
/datum/ai_controller/proc/modify_cooldown(datum/ai_behavior/behavior, new_cooldown)
|
||||
behavior_cooldowns[behavior.type] = new_cooldown
|
||||
|
||||
///Call this to add a behavior to the stack.
|
||||
/datum/ai_controller/proc/queue_behavior(behavior_type, ...)
|
||||
var/datum/ai_behavior/behavior = GET_AI_BEHAVIOR(behavior_type)
|
||||
@@ -344,6 +415,7 @@ multiple modular subtrees with behaviors
|
||||
behavior_args[behavior_type] = arguments
|
||||
else
|
||||
behavior_args -= behavior_type
|
||||
SEND_SIGNAL(src, AI_CONTROLLER_BEHAVIOR_QUEUED(behavior_type), arguments)
|
||||
|
||||
/datum/ai_controller/proc/ProcessBehavior(seconds_per_tick, datum/ai_behavior/behavior)
|
||||
var/list/arguments = list(seconds_per_tick, src)
|
||||
@@ -378,7 +450,7 @@ multiple modular subtrees with behaviors
|
||||
/datum/ai_controller/proc/on_sentience_lost()
|
||||
SIGNAL_HANDLER
|
||||
UnregisterSignal(pawn, COMSIG_MOB_LOGOUT)
|
||||
set_ai_status(AI_STATUS_ON) //Can't do anything while player is connected
|
||||
set_ai_status(AI_STATUS_IDLE) //Can't do anything while player is connected
|
||||
RegisterSignal(pawn, COMSIG_MOB_LOGIN, PROC_REF(on_sentience_gained))
|
||||
|
||||
// Turn the controller off if the pawn has been qdeleted
|
||||
|
||||
@@ -7,9 +7,10 @@
|
||||
var/atom/movable/target = controller.blackboard[target_key]
|
||||
return ismovable(target) && isturf(target.loc) && ismob(controller.pawn)
|
||||
|
||||
/datum/ai_behavior/stop_and_stare/get_cooldown(datum/ai_controller/cooldown_for)
|
||||
return cooldown_for.blackboard[BB_STATIONARY_COOLDOWN]
|
||||
|
||||
/datum/ai_behavior/stop_and_stare/perform(seconds_per_tick, datum/ai_controller/controller, target_key)
|
||||
// i don't really like doing this but we wanna make sure that the cooldown is pertinent to what we need for this specific controller before we invoke parent
|
||||
action_cooldown = controller.blackboard[BB_STATIONARY_COOLDOWN]
|
||||
. = ..()
|
||||
var/atom/movable/target = controller.blackboard[target_key]
|
||||
if(!ismovable(target) || !isturf(target.loc)) // just to make sure that nothing funky happened between setup and perform
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
/// List of objects that AIs will treat as targets
|
||||
GLOBAL_LIST_EMPTY_TYPED(hostile_machines, /atom)
|
||||
/// Static typecache list of things we are interested in
|
||||
/// Consider this a union of the for loop and the hearers call from below
|
||||
/// Must be kept up to date with the contents of hostile_machines
|
||||
GLOBAL_LIST_INIT(target_interested_atoms, typecacheof(list(/mob, /obj/machinery/porta_turret, /obj/vehicle/sealed/mecha)))
|
||||
|
||||
/datum/ai_behavior/find_potential_targets
|
||||
action_cooldown = 2 SECONDS
|
||||
@@ -8,8 +12,12 @@ GLOBAL_LIST_EMPTY_TYPED(hostile_machines, /atom)
|
||||
/// Blackboard key for aggro range, uses vision range if not specified
|
||||
var/aggro_range_key = BB_AGGRO_RANGE
|
||||
|
||||
/datum/ai_behavior/find_potential_targets/get_cooldown(datum/ai_controller/cooldown_for)
|
||||
if(cooldown_for.blackboard[BB_FIND_TARGETS_FIELD(type)])
|
||||
return 60 SECONDS
|
||||
return ..()
|
||||
|
||||
/datum/ai_behavior/find_potential_targets/perform(seconds_per_tick, datum/ai_controller/controller, target_key, targeting_strategy_key, hiding_location_key)
|
||||
. = ..()
|
||||
var/mob/living/living_mob = controller.pawn
|
||||
var/datum/targeting_strategy/targeting_strategy = GET_TARGETING_STRATEGY(controller.blackboard[targeting_strategy_key])
|
||||
|
||||
@@ -25,6 +33,10 @@ GLOBAL_LIST_EMPTY_TYPED(hostile_machines, /atom)
|
||||
|
||||
controller.clear_blackboard_key(target_key)
|
||||
|
||||
// If we're using a field rn, just don't do anything yeah?
|
||||
if(controller.blackboard[BB_FIND_TARGETS_FIELD(type)])
|
||||
return
|
||||
|
||||
var/list/potential_targets = hearers(aggro_range, get_turf(controller.pawn)) - living_mob //Remove self, so we don't suicide
|
||||
|
||||
for (var/atom/hostile_machine as anything in GLOB.hostile_machines)
|
||||
@@ -32,6 +44,7 @@ GLOBAL_LIST_EMPTY_TYPED(hostile_machines, /atom)
|
||||
potential_targets += hostile_machine
|
||||
|
||||
if(!potential_targets.len)
|
||||
failed_to_find_anyone(controller, target_key, targeting_strategy_key, hiding_location_key)
|
||||
finish_action(controller, succeeded = FALSE)
|
||||
return
|
||||
|
||||
@@ -43,6 +56,7 @@ GLOBAL_LIST_EMPTY_TYPED(hostile_machines, /atom)
|
||||
continue
|
||||
|
||||
if(!filtered_targets.len)
|
||||
failed_to_find_anyone(controller, target_key, targeting_strategy_key, hiding_location_key)
|
||||
finish_action(controller, succeeded = FALSE)
|
||||
return
|
||||
|
||||
@@ -56,10 +70,86 @@ GLOBAL_LIST_EMPTY_TYPED(hostile_machines, /atom)
|
||||
|
||||
finish_action(controller, succeeded = TRUE)
|
||||
|
||||
/datum/ai_behavior/find_potential_targets/finish_action(datum/ai_controller/controller, succeeded, ...)
|
||||
/datum/ai_behavior/find_potential_targets/proc/failed_to_find_anyone(datum/ai_controller/controller, target_key, targeting_strategy_key, hiding_location_key)
|
||||
var/aggro_range = controller.blackboard[aggro_range_key] || vision_range
|
||||
// takes the larger between our range() input and our implicit hearers() input (world.view)
|
||||
aggro_range = max(aggro_range, ROUND_UP(max(getviewsize(world.view)) / 2))
|
||||
// Alright, here's the interesting bit
|
||||
// We're gonna use this max range to hook into a proximity field so we can just await someone interesting to come along
|
||||
// Rather then trying to check every few seconds
|
||||
var/datum/proximity_monitor/advanced/ai_target_tracking/detection_field = new(
|
||||
controller.pawn,
|
||||
aggro_range,
|
||||
TRUE,
|
||||
src,
|
||||
controller,
|
||||
target_key,
|
||||
targeting_strategy_key,
|
||||
hiding_location_key,
|
||||
)
|
||||
// We're gonna store this field in our blackboard, so we can clear it away if we end up finishing successsfully
|
||||
controller.set_blackboard_key(BB_FIND_TARGETS_FIELD(type), detection_field)
|
||||
|
||||
/datum/ai_behavior/find_potential_targets/proc/new_turf_found(turf/found, datum/ai_controller/controller, datum/targeting_strategy/strategy)
|
||||
var/valid_found = FALSE
|
||||
var/mob/pawn = controller.pawn
|
||||
for(var/maybe_target as anything in found)
|
||||
if(maybe_target == pawn)
|
||||
continue
|
||||
if(!is_type_in_typecache(maybe_target, GLOB.target_interested_atoms))
|
||||
continue
|
||||
if(!strategy.can_attack(pawn, maybe_target))
|
||||
continue
|
||||
valid_found = TRUE
|
||||
break
|
||||
if(!valid_found)
|
||||
return
|
||||
// If we found any one thing we "could" attack, then run the full search again so we can select from the best possible canidate
|
||||
var/datum/proximity_monitor/field = controller.blackboard[BB_FIND_TARGETS_FIELD(type)]
|
||||
qdel(field) // autoclears so it's fine
|
||||
// Fire instantly, you should find something I hope
|
||||
controller.modify_cooldown(src, world.time)
|
||||
|
||||
/datum/ai_behavior/find_potential_targets/proc/atom_allowed(atom/movable/checking, datum/targeting_strategy/strategy, mob/pawn)
|
||||
if(checking == pawn)
|
||||
return FALSE
|
||||
if(!ismob(checking) && !is_type_in_typecache(checking, GLOB.target_interested_atoms))
|
||||
return FALSE
|
||||
if(!strategy.can_attack(pawn, checking))
|
||||
return FALSE
|
||||
return TRUE
|
||||
|
||||
/datum/ai_behavior/find_potential_targets/proc/new_atoms_found(list/atom/movable/found, datum/ai_controller/controller, target_key, datum/targeting_strategy/strategy, hiding_location_key)
|
||||
var/mob/pawn = controller.pawn
|
||||
var/list/accepted_targets = list()
|
||||
for(var/maybe_target as anything in found)
|
||||
if(maybe_target == pawn)
|
||||
continue
|
||||
// Need to better handle viewers here
|
||||
if(!ismob(maybe_target) && !is_type_in_typecache(maybe_target, GLOB.target_interested_atoms))
|
||||
continue
|
||||
if(!strategy.can_attack(pawn, maybe_target))
|
||||
continue
|
||||
accepted_targets += maybe_target
|
||||
|
||||
// Alright, we found something acceptable, let's use it yeah?
|
||||
var/atom/target = pick_final_target(controller, accepted_targets)
|
||||
controller.set_blackboard_key(target_key, target)
|
||||
|
||||
var/atom/potential_hiding_location = strategy.find_hidden_mobs(pawn, target)
|
||||
|
||||
if(potential_hiding_location) //If they're hiding inside of something, we need to know so we can go for that instead initially.
|
||||
controller.set_blackboard_key(hiding_location_key, potential_hiding_location)
|
||||
|
||||
finish_action(controller, succeeded = TRUE)
|
||||
|
||||
/datum/ai_behavior/find_potential_targets/finish_action(datum/ai_controller/controller, succeeded, target_key, targeting_strategy_key, hiding_location_key)
|
||||
. = ..()
|
||||
if (succeeded)
|
||||
var/datum/proximity_monitor/field = controller.blackboard[BB_FIND_TARGETS_FIELD(type)]
|
||||
qdel(field) // autoclears so it's fine
|
||||
controller.CancelActions() // On retarget cancel any further queued actions so that they will setup again with new target
|
||||
controller.modify_cooldown(controller, get_cooldown(controller))
|
||||
|
||||
/// Returns the desired final target from the filtered list of targets
|
||||
/datum/ai_behavior/find_potential_targets/proc/pick_final_target(datum/ai_controller/controller, list/filtered_targets)
|
||||
|
||||
@@ -8,8 +8,10 @@
|
||||
/datum/ai_behavior/crawl_through_vents
|
||||
action_cooldown = 10 SECONDS
|
||||
|
||||
/datum/ai_behavior/crawl_through_vents/get_cooldown(datum/ai_controller/cooldown_for)
|
||||
return cooldown_for.blackboard[BB_VENTCRAWL_COOLDOWN] || initial(action_cooldown)
|
||||
|
||||
/datum/ai_behavior/crawl_through_vents/setup(datum/ai_controller/controller, target_key)
|
||||
action_cooldown = controller.blackboard[BB_VENTCRAWL_COOLDOWN] || initial(action_cooldown)
|
||||
. = ..()
|
||||
var/obj/machinery/atmospherics/components/unary/vent_pump/target = controller.blackboard[target_key] || controller.blackboard[BB_ENTRY_VENT_TARGET]
|
||||
return istype(target) && isliving(controller.pawn) // only mobs can vent crawl in the current framework
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
required_distance = 3
|
||||
|
||||
/datum/ai_behavior/basic_melee_attack/dog/perform(seconds_per_tick, datum/ai_controller/controller, target_key, targeting_strategy_key, hiding_location_key)
|
||||
controller.behavior_cooldowns[src] = world.time + action_cooldown
|
||||
controller.behavior_cooldowns[src] = world.time + get_cooldown(controller)
|
||||
var/mob/living/living_pawn = controller.pawn
|
||||
if(!(isturf(living_pawn.loc) || HAS_TRAIT(living_pawn, TRAIT_AI_BAGATTACK))) // Void puppies can attack from inside bags
|
||||
finish_action(controller, FALSE, target_key, targeting_strategy_key, hiding_location_key)
|
||||
|
||||
@@ -514,7 +514,7 @@
|
||||
owner.grant_language(/datum/language/monkey, UNDERSTOOD_LANGUAGE, TRAUMA_TRAIT)
|
||||
owner.ai_controller.set_blackboard_key(BB_MONKEY_AGGRESSIVE, prob(75))
|
||||
if(owner.ai_controller.ai_status == AI_STATUS_OFF)
|
||||
owner.ai_controller.set_ai_status(AI_STATUS_ON)
|
||||
owner.ai_controller.set_ai_status(AI_STATUS_IDLE)
|
||||
owner.log_message("became controlled by monkey instincts ([owner.ai_controller.blackboard[BB_MONKEY_AGGRESSIVE] ? "aggressive" : "docile"])", LOG_ATTACK, color = "orange")
|
||||
to_chat(owner, span_warning("You feel the urge to act on your primal instincts..."))
|
||||
// extend original timer if we roll the effect while it's already ongoing
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
|
||||
/// An assoc list of signal -> procpath to register to the loc this object is on.
|
||||
var/list/connections
|
||||
/// The turfs currently connected to this component
|
||||
var/list/turfs = list()
|
||||
/**
|
||||
* The atom the component is tracking. The component will delete itself if the tracked is deleted.
|
||||
* Signals will also be updated whenever it moves (if it's a movable).
|
||||
@@ -41,7 +43,7 @@
|
||||
if(src.range == range && src.works_in_containers == works_in_containers)
|
||||
return
|
||||
//Unregister the signals with the old settings.
|
||||
unregister_signals(isturf(tracked) ? tracked : tracked.loc)
|
||||
unregister_signals(isturf(tracked) ? tracked : tracked.loc, turfs)
|
||||
src.range = range
|
||||
src.works_in_containers = works_in_containers
|
||||
//Re-register the signals with the new settings.
|
||||
@@ -49,7 +51,7 @@
|
||||
|
||||
/datum/component/connect_range/proc/set_tracked(atom/new_tracked)
|
||||
if(tracked) //Unregister the signals from the old tracked and its surroundings
|
||||
unregister_signals(isturf(tracked) ? tracked : tracked.loc)
|
||||
unregister_signals(isturf(tracked) ? tracked : tracked.loc, turfs)
|
||||
UnregisterSignal(tracked, list(
|
||||
COMSIG_MOVABLE_MOVED,
|
||||
COMSIG_QDELETING,
|
||||
@@ -66,28 +68,34 @@
|
||||
SIGNAL_HANDLER
|
||||
qdel(src)
|
||||
|
||||
/datum/component/connect_range/proc/update_signals(atom/target, atom/old_loc, forced = FALSE)
|
||||
/datum/component/connect_range/proc/update_signals(atom/target, atom/old_loc)
|
||||
var/turf/current_turf = get_turf(target)
|
||||
var/on_same_turf = current_turf == get_turf(old_loc) //Only register/unregister turf signals if it's moved to a new turf.
|
||||
unregister_signals(old_loc, on_same_turf)
|
||||
|
||||
if(isnull(current_turf))
|
||||
unregister_signals(old_loc, turfs)
|
||||
turfs = list()
|
||||
return
|
||||
|
||||
if(ismovable(target.loc))
|
||||
if(!works_in_containers)
|
||||
unregister_signals(old_loc, turfs)
|
||||
turfs = list()
|
||||
return
|
||||
//Keep track of possible movement of all movables the target is in.
|
||||
for(var/atom/movable/container as anything in get_nested_locs(target))
|
||||
RegisterSignal(container, COMSIG_MOVABLE_MOVED, PROC_REF(on_moved))
|
||||
|
||||
if(on_same_turf && !forced)
|
||||
//Only register/unregister turf signals if it's moved to a new turf.
|
||||
if(current_turf == get_turf(old_loc))
|
||||
unregister_signals(old_loc, null)
|
||||
return
|
||||
for(var/turf/target_turf in RANGE_TURFS(range, current_turf))
|
||||
var/list/old_turfs = turfs
|
||||
turfs = RANGE_TURFS(range, current_turf)
|
||||
unregister_signals(old_loc, old_turfs - turfs)
|
||||
for(var/turf/target_turf as anything in turfs - old_turfs)
|
||||
for(var/signal in connections)
|
||||
parent.RegisterSignal(target_turf, signal, connections[signal])
|
||||
|
||||
/datum/component/connect_range/proc/unregister_signals(atom/location, on_same_turf = FALSE)
|
||||
/datum/component/connect_range/proc/unregister_signals(atom/location, list/remove_from)
|
||||
//The location is null or is a container and the component shouldn't have register signals on it
|
||||
if(isnull(location) || (!works_in_containers && !isturf(location)))
|
||||
return
|
||||
@@ -96,10 +104,9 @@
|
||||
for(var/atom/movable/target as anything in (get_nested_locs(location) + location))
|
||||
UnregisterSignal(target, COMSIG_MOVABLE_MOVED)
|
||||
|
||||
if(on_same_turf)
|
||||
if(!length(remove_from))
|
||||
return
|
||||
var/turf/previous_turf = get_turf(location)
|
||||
for(var/turf/target_turf in RANGE_TURFS(range, previous_turf))
|
||||
for(var/turf/target_turf as anything in remove_from)
|
||||
parent.UnregisterSignal(target_turf, connections)
|
||||
|
||||
/datum/component/connect_range/proc/on_moved(atom/movable/movable, atom/old_loc)
|
||||
|
||||
@@ -8,6 +8,10 @@
|
||||
if (!isatom(target))
|
||||
return ELEMENT_INCOMPATIBLE
|
||||
|
||||
#ifdef UNIT_TESTS
|
||||
if(!GLOB.target_interested_atoms[target.type])
|
||||
stack_trace("Tried to make a hostile machine without updating ai targeting to include it, they must be synced")
|
||||
#endif
|
||||
GLOB.hostile_machines += target
|
||||
|
||||
/datum/element/hostile_machine/Detach(datum/source)
|
||||
|
||||
@@ -27,35 +27,59 @@
|
||||
/datum/proximity_monitor/advanced/proc/cleanup_field()
|
||||
for(var/turf/turf as anything in edge_turfs)
|
||||
cleanup_edge_turf(turf)
|
||||
edge_turfs = list()
|
||||
for(var/turf/turf as anything in field_turfs)
|
||||
cleanup_field_turf(turf)
|
||||
field_turfs = list()
|
||||
|
||||
//Call every time the field moves (done automatically if you use update_center) or a setup specification is changed.
|
||||
/datum/proximity_monitor/advanced/proc/recalculate_field()
|
||||
/datum/proximity_monitor/advanced/proc/recalculate_field(full_recalc = FALSE)
|
||||
var/list/new_turfs = update_new_turfs()
|
||||
|
||||
var/list/new_field_turfs = new_turfs[FIELD_TURFS_KEY]
|
||||
var/list/new_edge_turfs = new_turfs[EDGE_TURFS_KEY]
|
||||
var/list/old_field_turfs = field_turfs
|
||||
var/list/old_edge_turfs = edge_turfs
|
||||
field_turfs = new_turfs[FIELD_TURFS_KEY]
|
||||
edge_turfs = new_turfs[EDGE_TURFS_KEY]
|
||||
if(!full_recalc)
|
||||
field_turfs = list()
|
||||
edge_turfs = list()
|
||||
|
||||
for(var/turf/old_turf as anything in field_turfs)
|
||||
if(!(old_turf in new_field_turfs))
|
||||
cleanup_field_turf(old_turf)
|
||||
for(var/turf/old_turf as anything in edge_turfs)
|
||||
for(var/turf/old_turf as anything in old_field_turfs - field_turfs)
|
||||
if(QDELETED(src))
|
||||
return
|
||||
cleanup_field_turf(old_turf)
|
||||
for(var/turf/old_turf as anything in old_edge_turfs - edge_turfs)
|
||||
if(QDELETED(src))
|
||||
return
|
||||
cleanup_edge_turf(old_turf)
|
||||
|
||||
for(var/turf/new_turf as anything in new_field_turfs)
|
||||
field_turfs |= new_turf
|
||||
if(full_recalc)
|
||||
old_field_turfs = list()
|
||||
old_edge_turfs = list()
|
||||
field_turfs = new_turfs[FIELD_TURFS_KEY]
|
||||
edge_turfs = new_turfs[EDGE_TURFS_KEY]
|
||||
|
||||
for(var/turf/new_turf as anything in field_turfs - old_field_turfs)
|
||||
if(QDELETED(src))
|
||||
return
|
||||
field_turfs += new_turf
|
||||
setup_field_turf(new_turf)
|
||||
for(var/turf/new_turf as anything in new_edge_turfs)
|
||||
edge_turfs |= new_turf
|
||||
for(var/turf/new_turf as anything in edge_turfs - old_edge_turfs)
|
||||
if(QDELETED(src))
|
||||
return
|
||||
edge_turfs += new_turf
|
||||
setup_edge_turf(new_turf)
|
||||
|
||||
/datum/proximity_monitor/advanced/on_entered(turf/source, atom/movable/entered)
|
||||
/datum/proximity_monitor/advanced/on_initialized(turf/location, atom/created, init_flags)
|
||||
. = ..()
|
||||
on_entered(location, created, null)
|
||||
|
||||
/datum/proximity_monitor/advanced/on_entered(turf/source, atom/movable/entered, turf/old_loc)
|
||||
. = ..()
|
||||
if(get_dist(source, host) == current_range)
|
||||
field_edge_crossed(entered, source)
|
||||
field_edge_crossed(entered, old_loc, source)
|
||||
else
|
||||
field_turf_crossed(entered, source)
|
||||
field_turf_crossed(entered, old_loc, source)
|
||||
|
||||
/datum/proximity_monitor/advanced/on_moved(atom/movable/movable, atom/old_loc)
|
||||
. = ..()
|
||||
@@ -68,21 +92,22 @@
|
||||
if(isturf(old_loc))
|
||||
cleanup_field()
|
||||
return
|
||||
recalculate_field()
|
||||
recalculate_field(full_recalc = FALSE)
|
||||
|
||||
/datum/proximity_monitor/advanced/on_uncrossed(turf/source, atom/movable/gone, direction)
|
||||
if(get_dist(source, host) == current_range)
|
||||
field_edge_uncrossed(gone, source)
|
||||
field_edge_uncrossed(gone, source, get_turf(gone))
|
||||
else
|
||||
field_turf_uncrossed(gone, source)
|
||||
field_turf_uncrossed(gone, source, get_turf(gone))
|
||||
|
||||
/// Called when a turf in the field of the monitor is linked
|
||||
/datum/proximity_monitor/advanced/proc/setup_field_turf(turf/target)
|
||||
return
|
||||
|
||||
/// Called when a turf in the field of the monitor is unlinked
|
||||
/// Do NOT call this manually, requires management of the field_turfs list
|
||||
/datum/proximity_monitor/advanced/proc/cleanup_field_turf(turf/target)
|
||||
field_turfs -= target
|
||||
return
|
||||
|
||||
/// Called when a turf in the edge of the monitor is linked
|
||||
/datum/proximity_monitor/advanced/proc/setup_edge_turf(turf/target)
|
||||
@@ -90,21 +115,22 @@
|
||||
setup_field_turf(target)
|
||||
|
||||
/// Called when a turf in the edge of the monitor is unlinked
|
||||
/// Do NOT call this manually, requires management of the edge_turfs list
|
||||
/datum/proximity_monitor/advanced/proc/cleanup_edge_turf(turf/target)
|
||||
if(edge_is_a_field) // If the edge is considered a field, clean it up like one
|
||||
cleanup_field_turf(target)
|
||||
edge_turfs -= target
|
||||
|
||||
/datum/proximity_monitor/advanced/proc/update_new_turfs()
|
||||
. = list(FIELD_TURFS_KEY = list(), EDGE_TURFS_KEY = list())
|
||||
if(ignore_if_not_on_turf && !isturf(host.loc))
|
||||
return
|
||||
return list(FIELD_TURFS_KEY = list(), EDGE_TURFS_KEY = list())
|
||||
var/list/local_field_turfs = list()
|
||||
var/list/local_edge_turfs = list()
|
||||
var/turf/center = get_turf(host)
|
||||
for(var/turf/target in RANGE_TURFS(current_range, center))
|
||||
if(get_dist(center, target) == current_range)
|
||||
.[EDGE_TURFS_KEY] += target
|
||||
else
|
||||
.[FIELD_TURFS_KEY] += target
|
||||
if(current_range > 0)
|
||||
local_field_turfs += RANGE_TURFS(current_range - 1, center)
|
||||
if(current_range > 1)
|
||||
local_edge_turfs = local_field_turfs - RANGE_TURFS(current_range, center)
|
||||
return list(FIELD_TURFS_KEY = local_field_turfs, EDGE_TURFS_KEY = local_edge_turfs)
|
||||
|
||||
//Gets edge direction/corner, only works with square radius/WDH fields!
|
||||
/datum/proximity_monitor/advanced/proc/get_edgeturf_direction(turf/T, turf/center_override = null)
|
||||
@@ -124,19 +150,19 @@
|
||||
if(T.y == (checking_from.y + current_range))
|
||||
return NORTH
|
||||
|
||||
/datum/proximity_monitor/advanced/proc/field_turf_crossed(atom/movable/movable, turf/location)
|
||||
/datum/proximity_monitor/advanced/proc/field_turf_crossed(atom/movable/movable, turf/old_location, turf/new_location)
|
||||
return
|
||||
|
||||
/datum/proximity_monitor/advanced/proc/field_turf_uncrossed(atom/movable/movable, turf/location)
|
||||
/datum/proximity_monitor/advanced/proc/field_turf_uncrossed(atom/movable/movable, turf/old_location, turf/new_location)
|
||||
return
|
||||
|
||||
/datum/proximity_monitor/advanced/proc/field_edge_crossed(atom/movable/movable, turf/location)
|
||||
/datum/proximity_monitor/advanced/proc/field_edge_crossed(atom/movable/movable, turf/old_location, turf/new_location)
|
||||
if(edge_is_a_field) // If the edge is considered a field, pass crossed to that
|
||||
field_turf_crossed(movable, location)
|
||||
field_turf_crossed(movable, old_location, new_location)
|
||||
|
||||
/datum/proximity_monitor/advanced/proc/field_edge_uncrossed(atom/movable/movable, turf/location)
|
||||
/datum/proximity_monitor/advanced/proc/field_edge_uncrossed(atom/movable/movable, turf/old_location, turf/new_location)
|
||||
if(edge_is_a_field) // If the edge is considered a field, pass uncrossed to that
|
||||
field_turf_uncrossed(movable, location)
|
||||
field_turf_uncrossed(movable, old_location, new_location)
|
||||
|
||||
//DEBUG FIELD ITEM
|
||||
/obj/item/multitool/field_debug
|
||||
@@ -153,7 +179,7 @@
|
||||
current = new(src, 5, FALSE)
|
||||
current.set_fieldturf_color = "#aaffff"
|
||||
current.set_edgeturf_color = "#ffaaff"
|
||||
current.recalculate_field()
|
||||
current.recalculate_field(full_recalc = TRUE)
|
||||
|
||||
/obj/item/multitool/field_debug/attack_self(mob/user)
|
||||
operating = !operating
|
||||
|
||||
113
code/datums/proximity_monitor/fields/ai_target_tracking.dm
Normal file
113
code/datums/proximity_monitor/fields/ai_target_tracking.dm
Normal file
@@ -0,0 +1,113 @@
|
||||
// Proximity monitor that checks to see if anything interesting enters our bounds
|
||||
/datum/proximity_monitor/advanced/ai_target_tracking
|
||||
edge_is_a_field = TRUE
|
||||
/// The ai behavior who owns us
|
||||
var/datum/ai_behavior/find_potential_targets/owning_behavior
|
||||
/// The ai controller we're using
|
||||
var/datum/ai_controller/controller
|
||||
/// The target key we're trying to fill
|
||||
var/target_key
|
||||
/// The targeting strategy KEY we're using
|
||||
var/targeting_strategy_key
|
||||
/// The hiding location key we're using
|
||||
var/hiding_location_key
|
||||
|
||||
/// The targeting strategy we're using
|
||||
var/datum/targeting_strategy/filter
|
||||
/// If we've built our field yet
|
||||
/// Prevents wasted work on the first build (since the behavior did it)
|
||||
var/first_build = TRUE
|
||||
|
||||
// Initially, run the check manually
|
||||
// If that fails, set up a field and have it manage the behavior fully
|
||||
/datum/proximity_monitor/advanced/ai_target_tracking/New(atom/_host, range, _ignore_if_not_on_turf = TRUE, datum/ai_behavior/find_potential_targets/owning_behavior, datum/ai_controller/controller, target_key, targeting_strategy_key, hiding_location_key)
|
||||
. = ..()
|
||||
src.owning_behavior = owning_behavior
|
||||
src.controller = controller
|
||||
src.target_key = target_key
|
||||
src.targeting_strategy_key = targeting_strategy_key
|
||||
src.hiding_location_key = hiding_location_key
|
||||
src.filter = GET_TARGETING_STRATEGY(controller.blackboard[targeting_strategy_key])
|
||||
RegisterSignal(controller, COMSIG_QDELETING, PROC_REF(controller_deleted))
|
||||
RegisterSignal(controller, COMSIG_AI_CONTROLLER_PICKED_BEHAVIORS, PROC_REF(controller_think))
|
||||
RegisterSignal(controller, COMSIG_AI_CONTROLLER_POSSESSED_PAWN, PROC_REF(pawn_changed))
|
||||
RegisterSignal(controller, AI_CONTROLLER_BEHAVIOR_QUEUED(owning_behavior.type), PROC_REF(behavior_requeued))
|
||||
RegisterSignal(controller, COMSIG_AI_BLACKBOARD_KEY_SET(targeting_strategy_key), PROC_REF(targeting_datum_changed))
|
||||
RegisterSignal(controller, COMSIG_AI_BLACKBOARD_KEY_CLEARED(targeting_strategy_key), PROC_REF(targeting_datum_cleared))
|
||||
recalculate_field(full_recalc = TRUE)
|
||||
|
||||
/datum/proximity_monitor/advanced/ai_target_tracking/Destroy()
|
||||
. = ..()
|
||||
if(!QDELETED(controller) && owning_behavior)
|
||||
controller.modify_cooldown(owning_behavior, owning_behavior.get_cooldown(controller))
|
||||
owning_behavior = null
|
||||
controller = null
|
||||
target_key = null
|
||||
targeting_strategy_key = null
|
||||
hiding_location_key = null
|
||||
filter = null
|
||||
|
||||
/datum/proximity_monitor/advanced/ai_target_tracking/recalculate_field(full_recalc = FALSE)
|
||||
. = ..()
|
||||
first_build = FALSE
|
||||
|
||||
/datum/proximity_monitor/advanced/ai_target_tracking/setup_field_turf(turf/target)
|
||||
. = ..()
|
||||
if(first_build)
|
||||
return
|
||||
owning_behavior.new_turf_found(target, controller, filter)
|
||||
|
||||
/datum/proximity_monitor/advanced/ai_target_tracking/field_turf_crossed(atom/movable/movable, turf/location, turf/old_location)
|
||||
. = ..()
|
||||
|
||||
if(!owning_behavior.atom_allowed(movable, filter, controller.pawn))
|
||||
return
|
||||
|
||||
owning_behavior.new_atoms_found(list(movable), controller, target_key, filter, hiding_location_key)
|
||||
|
||||
/// React to controller planning
|
||||
/datum/proximity_monitor/advanced/ai_target_tracking/proc/controller_deleted(datum/source)
|
||||
SIGNAL_HANDLER
|
||||
qdel(src)
|
||||
|
||||
/// React to the pawn goin byebye
|
||||
/datum/proximity_monitor/advanced/ai_target_tracking/proc/pawn_changed(datum/source)
|
||||
SIGNAL_HANDLER
|
||||
qdel(src)
|
||||
|
||||
/// React to controller planning
|
||||
/datum/proximity_monitor/advanced/ai_target_tracking/proc/controller_think(datum/ai_controller/source, list/datum/ai_behavior/old_behaviors, list/datum/ai_behavior/new_behaviors)
|
||||
SIGNAL_HANDLER
|
||||
// If our parent was forgotten, nuke ourselves
|
||||
if(!new_behaviors[owning_behavior])
|
||||
qdel(src)
|
||||
|
||||
/datum/proximity_monitor/advanced/ai_target_tracking/proc/behavior_requeued(datum/source, list/new_arguments)
|
||||
SIGNAL_HANDLER
|
||||
check_new_args(arglist(new_arguments))
|
||||
|
||||
/// Ensure our args and locals are up to date
|
||||
/datum/proximity_monitor/advanced/ai_target_tracking/proc/check_new_args(target_key, targeting_strategy_key, hiding_location_key)
|
||||
var/update_filter = FALSE
|
||||
if(src.target_key != target_key)
|
||||
src.target_key = target_key
|
||||
if(src.targeting_strategy_key != targeting_strategy_key)
|
||||
src.targeting_strategy_key = targeting_strategy_key
|
||||
update_filter = TRUE
|
||||
if(src.hiding_location_key != hiding_location_key)
|
||||
src.hiding_location_key = hiding_location_key
|
||||
if(update_filter)
|
||||
targeting_datum_changed(null)
|
||||
|
||||
/datum/proximity_monitor/advanced/ai_target_tracking/proc/targeting_datum_changed(datum/source)
|
||||
SIGNAL_HANDLER
|
||||
filter = controller.blackboard[targeting_strategy_key]
|
||||
// Filter changed, need to do a full reparse
|
||||
// Fucking 9 * 9 out here I stg
|
||||
for(var/turf/in_field as anything in field_turfs + edge_turfs)
|
||||
owning_behavior.new_turf_found(in_field, controller, filter)
|
||||
|
||||
/datum/proximity_monitor/advanced/ai_target_tracking/proc/targeting_datum_cleared(datum/source)
|
||||
SIGNAL_HANDLER
|
||||
// Go fuckin home bros
|
||||
qdel(src)
|
||||
@@ -7,7 +7,7 @@
|
||||
/datum/proximity_monitor/advanced/gravity/New(atom/_host, range, _ignore_if_not_on_turf = TRUE, gravity)
|
||||
. = ..()
|
||||
gravity_value = gravity
|
||||
recalculate_field()
|
||||
recalculate_field(full_recalc = TRUE)
|
||||
|
||||
/datum/proximity_monitor/advanced/gravity/setup_field_turf(turf/target)
|
||||
. = ..()
|
||||
@@ -42,15 +42,15 @@
|
||||
for(var/mob/living/guy in target)
|
||||
warn_mob(guy, target)
|
||||
|
||||
/datum/proximity_monitor/advanced/gravity/warns_on_entrance/field_edge_crossed(atom/movable/movable, turf/location)
|
||||
/datum/proximity_monitor/advanced/gravity/warns_on_entrance/field_edge_crossed(atom/movable/movable, turf/old_location, turf/new_location)
|
||||
. = ..()
|
||||
if(isliving(movable))
|
||||
warn_mob(movable, location)
|
||||
warn_mob(movable, new_location)
|
||||
|
||||
/datum/proximity_monitor/advanced/gravity/warns_on_entrance/field_edge_uncrossed(atom/movable/movable, turf/location)
|
||||
/datum/proximity_monitor/advanced/gravity/warns_on_entrance/field_edge_uncrossed(atom/movable/movable, turf/old_location, turf/new_location)
|
||||
. = ..()
|
||||
if(isliving(movable))
|
||||
warn_mob(movable, location)
|
||||
warn_mob(movable, old_location)
|
||||
|
||||
/datum/proximity_monitor/advanced/gravity/warns_on_entrance/proc/warn_mob(mob/living/to_warn, turf/location)
|
||||
var/mob_ref_key = REF(to_warn)
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
/datum/proximity_monitor/advanced/projectile_dampener/New(atom/_host, range, _ignore_if_not_on_turf = TRUE, atom/projector)
|
||||
..()
|
||||
RegisterSignal(projector, COMSIG_QDELETING, PROC_REF(on_projector_del))
|
||||
recalculate_field()
|
||||
recalculate_field(full_recalc = TRUE)
|
||||
START_PROCESSING(SSfastprocess, src)
|
||||
|
||||
/datum/proximity_monitor/advanced/projectile_dampener/Destroy()
|
||||
@@ -48,7 +48,7 @@
|
||||
LAZYSET(edgeturf_effects, target, effect)
|
||||
|
||||
/datum/proximity_monitor/advanced/projectile_dampener/on_z_change(datum/source)
|
||||
recalculate_field()
|
||||
recalculate_field(full_recalc = TRUE)
|
||||
|
||||
/datum/proximity_monitor/advanced/projectile_dampener/cleanup_edge_turf(turf/target)
|
||||
. = ..()
|
||||
@@ -90,16 +90,15 @@
|
||||
|
||||
/datum/proximity_monitor/advanced/projectile_dampener/proc/on_projector_del(datum/source)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
qdel(src)
|
||||
|
||||
/datum/proximity_monitor/advanced/projectile_dampener/field_edge_uncrossed(atom/movable/movable, turf/location)
|
||||
/datum/proximity_monitor/advanced/projectile_dampener/field_edge_uncrossed(atom/movable/movable, turf/old_location, turf/new_location)
|
||||
if(isprojectile(movable) && get_dist(movable, host) > current_range)
|
||||
if(movable in tracked)
|
||||
release_projectile(movable)
|
||||
|
||||
/datum/proximity_monitor/advanced/projectile_dampener/field_edge_crossed(atom/movable/movable, turf/location)
|
||||
if(isprojectile(movable) && !(movable in tracked))
|
||||
/datum/proximity_monitor/advanced/projectile_dampener/field_edge_crossed(atom/movable/movable, turf/location, turf/old_location)
|
||||
if(isprojectile(movable))
|
||||
capture_projectile(movable)
|
||||
|
||||
/datum/proximity_monitor/advanced/projectile_dampener/peaceborg/process(seconds_per_tick)
|
||||
|
||||
@@ -82,7 +82,7 @@
|
||||
src.immune = immune
|
||||
src.antimagic_flags = antimagic_flags
|
||||
src.channelled = channelled
|
||||
recalculate_field()
|
||||
recalculate_field(full_recalc = TRUE)
|
||||
START_PROCESSING(SSfastprocess, src)
|
||||
|
||||
/datum/proximity_monitor/advanced/timestop/Destroy()
|
||||
@@ -93,7 +93,7 @@
|
||||
STOP_PROCESSING(SSfastprocess, src)
|
||||
return ..()
|
||||
|
||||
/datum/proximity_monitor/advanced/timestop/field_turf_crossed(atom/movable/movable, turf/location)
|
||||
/datum/proximity_monitor/advanced/timestop/field_turf_crossed(atom/movable/movable, turf/old_location, turf/new_location)
|
||||
freeze_atom(movable)
|
||||
|
||||
/datum/proximity_monitor/advanced/timestop/proc/freeze_atom(atom/movable/A)
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
var/static/list/loc_connections = list(
|
||||
COMSIG_ATOM_ENTERED = PROC_REF(on_entered),
|
||||
COMSIG_ATOM_EXITED = PROC_REF(on_uncrossed),
|
||||
COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZED_ON = PROC_REF(on_entered),
|
||||
COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZED_ON = PROC_REF(on_initialized),
|
||||
)
|
||||
|
||||
/datum/proximity_monitor/New(atom/_host, range, _ignore_if_not_on_turf = TRUE)
|
||||
@@ -78,7 +78,12 @@
|
||||
SIGNAL_HANDLER
|
||||
return //Used by the advanced subtype for effect fields.
|
||||
|
||||
/datum/proximity_monitor/proc/on_entered(atom/source, atom/movable/arrived)
|
||||
/datum/proximity_monitor/proc/on_entered(atom/source, atom/movable/arrived, turf/old_loc)
|
||||
SIGNAL_HANDLER
|
||||
if(source != host)
|
||||
hasprox_receiver?.HasProximity(arrived)
|
||||
|
||||
/datum/proximity_monitor/proc/on_initialized(turf/location, atom/created, init_flags)
|
||||
SIGNAL_HANDLER
|
||||
if(location != host)
|
||||
hasprox_receiver?.HasProximity(created)
|
||||
|
||||
@@ -79,7 +79,7 @@
|
||||
src.department = department
|
||||
RegisterSignal(host, COMSIG_ATOM_EXAMINE, PROC_REF(on_examine))
|
||||
|
||||
/datum/proximity_monitor/advanced/quirk_posters/field_turf_crossed(atom/movable/crossed, turf/location)
|
||||
/datum/proximity_monitor/advanced/quirk_posters/field_turf_crossed(atom/movable/crossed, turf/old_location, turf/new_location)
|
||||
if (!isliving(crossed) || !can_see(crossed, host, current_range))
|
||||
return
|
||||
on_seen(crossed)
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
src.moods = moods
|
||||
RegisterSignal(host, COMSIG_ATOM_EXAMINE, PROC_REF(on_examine))
|
||||
|
||||
/datum/proximity_monitor/advanced/demoraliser/field_turf_crossed(atom/movable/crossed, turf/location)
|
||||
/datum/proximity_monitor/advanced/demoraliser/field_turf_crossed(atom/movable/crossed, turf/old_location, turf/new_location)
|
||||
if (!isliving(crossed))
|
||||
return
|
||||
if (!can_see(crossed, host, current_range))
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
var/current_pathing_attempts = 0
|
||||
///if we cant reach it after this many attempts, add it to our ignore list
|
||||
var/max_pathing_attempts = 25
|
||||
can_idle = FALSE // we want these to be running always
|
||||
|
||||
/datum/ai_controller/basic_controller/bot/TryPossessPawn(atom/new_pawn)
|
||||
. = ..()
|
||||
|
||||
@@ -26,6 +26,7 @@ GLOBAL_LIST_INIT(mook_commands, list(
|
||||
/datum/ai_planning_subtree/mine_walls/mook,
|
||||
/datum/ai_planning_subtree/wander_away_from_village,
|
||||
)
|
||||
can_idle = FALSE // these guys are intended to operate even if nobody's around
|
||||
|
||||
///check for faction if not a ash walker, otherwise just attack
|
||||
/datum/targeting_strategy/basic/mook/faction_check(datum/ai_controller/controller, mob/living/living_mob, mob/living/the_target)
|
||||
|
||||
@@ -173,7 +173,6 @@
|
||||
/mob/living/basic/living_limb_flesh/proc/wake_up(atom/limb)
|
||||
visible_message(span_warning("[src] begins flailing around!"))
|
||||
Shake(6, 6, 0.5 SECONDS)
|
||||
ai_controller.set_ai_status(AI_STATUS_ON)
|
||||
ai_controller.set_ai_status(AI_STATUS_IDLE)
|
||||
forceMove(limb.drop_location())
|
||||
qdel(limb)
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
/datum/ai_planning_subtree/basic_melee_attack_subtree/slime,
|
||||
/datum/ai_planning_subtree/random_speech/slime,
|
||||
)
|
||||
can_idle = FALSE
|
||||
|
||||
/datum/ai_controller/basic_controller/slime/CancelActions()
|
||||
..()
|
||||
|
||||
@@ -67,7 +67,7 @@ GLOBAL_LIST_EMPTY(elevator_music)
|
||||
QDEL_LIST_ASSOC_VAL(tracked_mobs)
|
||||
return ..()
|
||||
|
||||
/datum/proximity_monitor/advanced/elevator_music_area/field_turf_crossed(mob/entered, turf/location)
|
||||
/datum/proximity_monitor/advanced/elevator_music_area/field_turf_crossed(mob/entered, turf/old_location, turf/new_location)
|
||||
if (!istype(entered) || !entered.mind)
|
||||
return
|
||||
|
||||
@@ -80,7 +80,7 @@ GLOBAL_LIST_EMPTY(elevator_music)
|
||||
tracked_mobs[entered] = null // Still add it to the list so we don't keep making this check
|
||||
RegisterSignal(entered, COMSIG_QDELETING, PROC_REF(mob_destroyed))
|
||||
|
||||
/datum/proximity_monitor/advanced/elevator_music_area/field_turf_uncrossed(mob/exited, turf/location)
|
||||
/datum/proximity_monitor/advanced/elevator_music_area/field_turf_uncrossed(mob/exited, turf/old_location, turf/new_location)
|
||||
if (!(exited in tracked_mobs))
|
||||
return
|
||||
if (exited.z == host.z && get_dist(exited, host) <= current_range)
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
var/fake_dt = SSai_controllers.wait * 0.1
|
||||
// Set AI - AIs by default are off in z-levels with no client, we have to force it on.
|
||||
biter.ai_controller.set_ai_status(AI_STATUS_ON)
|
||||
biter.ai_controller.can_idle = FALSE
|
||||
// Select behavior - this will queue finding the cable
|
||||
biter.ai_controller.SelectBehaviors(fake_dt)
|
||||
// Process behavior - this will execute the "locate the cable" behavior
|
||||
|
||||
@@ -1749,6 +1749,7 @@
|
||||
#include "code\datums\mutations\webbing.dm"
|
||||
#include "code\datums\proximity_monitor\field.dm"
|
||||
#include "code\datums\proximity_monitor\proximity_monitor.dm"
|
||||
#include "code\datums\proximity_monitor\fields\ai_target_tracking.dm"
|
||||
#include "code\datums\proximity_monitor\fields\gravity.dm"
|
||||
#include "code\datums\proximity_monitor\fields\projectile_dampener.dm"
|
||||
#include "code\datums\proximity_monitor\fields\timestop.dm"
|
||||
|
||||
Reference in New Issue
Block a user