[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:
Swift
2024-04-25 23:13:16 -05:00
committed by GitHub
parent 2c8b50a9a2
commit 67a3eb415d
28 changed files with 437 additions and 81 deletions

View File

@@ -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)

View File

@@ -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]"

View File

@@ -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]"

View File

@@ -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))

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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)

View File

@@ -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)

View File

@@ -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

View 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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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))

View File

@@ -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)
. = ..()

View File

@@ -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)

View File

@@ -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)

View File

@@ -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()
..()

View File

@@ -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)

View File

@@ -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

View File

@@ -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"