diff --git a/code/__DEFINES/dcs/signals.dm b/code/__DEFINES/dcs/signals.dm index 52e0be78c38..a9b37740060 100644 --- a/code/__DEFINES/dcs/signals.dm +++ b/code/__DEFINES/dcs/signals.dm @@ -1064,6 +1064,8 @@ #define COMSIG_MOB_ATTACK_RANGED "mob_attack_ranged" ///From base of atom/ctrl_click(): (atom/A) #define COMSIG_MOB_CTRL_CLICKED "mob_ctrl_clicked" +///From base of mob/update_movespeed():area +#define COMSIG_MOB_MOVESPEED_UPDATED "mob_update_movespeed" ///from mob/living/carbon/human/UnarmedAttack(): (atom/target, proximity) #define COMSIG_HUMAN_EARLY_UNARMED_ATTACK "human_early_unarmed_attack" ///from mob/living/carbon/human/UnarmedAttack(): (atom/target, proximity) diff --git a/code/__DEFINES/subsystems.dm b/code/__DEFINES/subsystems.dm index 84c86a075a3..5d945cf5d1c 100644 --- a/code/__DEFINES/subsystems.dm +++ b/code/__DEFINES/subsystems.dm @@ -117,6 +117,8 @@ #define INIT_ORDER_EVENTS 70 #define INIT_ORDER_JOBS 65 #define INIT_ORDER_QUIRKS 60 +#define INIT_ORDER_AI_MOVEMENT 56 //We need the movement setup +#define INIT_ORDER_AI_CONTROLLERS 55 //So the controller can get the ref #define INIT_ORDER_TICKER 55 #define INIT_ORDER_TCG 55 #define INIT_ORDER_REAGENTS 55 //HAS to be before mapping - mapping creates objects, which creates reagents, which relies on lists made in this subsystem @@ -160,6 +162,7 @@ #define FIRE_PRIORITY_WET_FLOORS 20 #define FIRE_PRIORITY_AIR 20 #define FIRE_PRIORITY_NPC 20 +#define FIRE_PRIORITY_NPC_MOVEMENT 21 #define FIRE_PRIORITY_PROCESS 25 #define FIRE_PRIORITY_THROWING 25 #define FIRE_PRIORITY_REAGENTS 26 diff --git a/code/controllers/subsystem/processing/ai_controllers.dm b/code/controllers/subsystem/processing/ai_controllers.dm index 093ce9962dc..fbdb7adb37b 100644 --- a/code/controllers/subsystem/processing/ai_controllers.dm +++ b/code/controllers/subsystem/processing/ai_controllers.dm @@ -4,6 +4,7 @@ PROCESSING_SUBSYSTEM_DEF(ai_controllers) flags = SS_POST_FIRE_TIMING|SS_BACKGROUND priority = FIRE_PRIORITY_NPC runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME + init_order = INIT_ORDER_AI_CONTROLLERS wait = 8 //Uses the value of CLICK_CD_MELEE because that seemed like a nice standard for the speed of AI behavior ///an assoc list of all ai_behaviors by type, to diff --git a/code/controllers/subsystem/processing/ai_movement.dm b/code/controllers/subsystem/processing/ai_movement.dm new file mode 100644 index 00000000000..6a6d64548ca --- /dev/null +++ b/code/controllers/subsystem/processing/ai_movement.dm @@ -0,0 +1,21 @@ +/// The subsystem used to tick [/datum/ai_movement] instances. Handling the movement of individual AI instances +PROCESSING_SUBSYSTEM_DEF(ai_movement) + name = "AI movement" + flags = SS_KEEP_TIMING|SS_BACKGROUND + priority = FIRE_PRIORITY_NPC_MOVEMENT + runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME + init_order = INIT_ORDER_AI_MOVEMENT + wait = 1 + + ///an assoc list of all ai_movement types. Assoc type to instance + var/list/movement_types + +/datum/controller/subsystem/processing/ai_movement/Initialize(timeofday) + SetupAIMovementInstances() + return ..() + +/datum/controller/subsystem/processing/ai_movement/proc/SetupAIMovementInstances() + movement_types = list() + for(var/key as anything in subtypesof(/datum/ai_movement)) + var/datum/ai_movement/ai_movement = new key + movement_types[key] = ai_movement diff --git a/code/datums/ai/_ai_behavior.dm b/code/datums/ai/_ai_behavior.dm index 2961ed010ad..b22b8e8014f 100644 --- a/code/datums/ai/_ai_behavior.dm +++ b/code/datums/ai/_ai_behavior.dm @@ -17,4 +17,5 @@ controller.current_behaviors.Remove(src) if(behavior_flags & AI_BEHAVIOR_REQUIRE_MOVEMENT) //If this was a movement task, reset our movement target. controller.current_movement_target = null + controller.ai_movement.stop_moving_towards(controller) return diff --git a/code/datums/ai/_ai_controller.dm b/code/datums/ai/_ai_controller.dm index c44a00230b7..abe43e515cd 100644 --- a/code/datums/ai/_ai_controller.dm +++ b/code/datums/ai/_ai_controller.dm @@ -26,8 +26,15 @@ have ways of interacting with a specific atom and control it. They posses a blac var/continue_processing_when_client = FALSE ///distance to give up on target var/max_target_distance = 14 + ///Reference to the movement datum we use. Is a type on initialize but becomes a ref afterwards. + var/datum/ai_movement/ai_movement = /datum/ai_movement/dumb + ///Cooldown until next movement + COOLDOWN_DECLARE(movement_cooldown) + ///Delay between movements. This is on the controller so we can keep the movement datum singleton + var/movement_delay = 0.1 SECONDS /datum/ai_controller/New(atom/new_pawn) + ai_movement = SSai_movement.movement_types[ai_movement] PossessPawn(new_pawn) /datum/ai_controller/Destroy(force, ...) @@ -81,8 +88,7 @@ have ways of interacting with a specific atom and control it. They posses a blac /// Generates a plan and see if our existing one is still valid. /datum/ai_controller/process(delta_time) if(!able_to_run()) - var/atom/movable/movable_pawn = pawn - walk(movable_pawn, 0) //stop moving + walk(pawn, 0) //stop moving return //this should remove them from processing in the future through event-based stuff. if(!current_behaviors?.len) SelectBehaviors(delta_time) @@ -90,40 +96,33 @@ have ways of interacting with a specific atom and control it. They posses a blac PerformIdleBehavior(delta_time) //Do some stupid shit while we have nothing to do return - var/want_to_move = FALSE + + if(current_movement_target && get_dist(pawn, current_movement_target) > max_target_distance) //The distance is out of range + CancelActions() + return + for(var/i in current_behaviors) var/datum/ai_behavior/current_behavior = i if(behavior_cooldowns[current_behavior] > world.time) //Still on cooldown continue - if(current_behavior.behavior_flags & AI_BEHAVIOR_REQUIRE_MOVEMENT && current_movement_target && current_behavior.required_distance < get_dist(pawn, current_movement_target)) //Move closer - want_to_move = TRUE - if(current_behavior.behavior_flags & AI_BEHAVIOR_MOVE_AND_PERFORM) //Move and perform the action + if(current_behavior.behavior_flags & AI_BEHAVIOR_REQUIRE_MOVEMENT && current_movement_target) //Might need to move closer + if(current_behavior.required_distance >= get_dist(pawn, current_movement_target)) ///Are we close enough to engage? + if(ai_movement.moving_controllers[src] == current_movement_target) //We are close enough, if we're moving stop.else + ai_movement.stop_moving_towards(src) current_behavior.perform(delta_time, src) - else //Perform the action + return + + else if(ai_movement.moving_controllers[src] != current_movement_target) //We're too far, if we're not already moving start doing it. + ai_movement.start_moving_towards(src, current_movement_target) //Then start moving + + if(current_behavior.behavior_flags & AI_BEHAVIOR_MOVE_AND_PERFORM) //If we can move and perform then do so. + current_behavior.perform(delta_time, src) + return + else //No movement required current_behavior.perform(delta_time, src) - - if(want_to_move) - MoveTo(delta_time) //Need to add some code to check if we can perform the actions now without too much overhead - - -///Move somewhere using dumb movement (byond base) -/datum/ai_controller/proc/MoveTo(delta_time) - var/current_loc = get_turf(pawn) - var/atom/movable/movable_pawn = pawn - - var/turf/target_turf = get_step_towards(movable_pawn, current_movement_target) - - if(!is_type_in_typecache(target_turf, GLOB.dangerous_turfs)) - movable_pawn.Move(target_turf, get_dir(current_loc, target_turf)) - if(get_dist(movable_pawn, current_movement_target) > max_target_distance) - CancelActions() - pathing_attempts = 0 - if(current_loc == get_turf(movable_pawn)) - if(++pathing_attempts >= MAX_PATHING_ATTEMPTS) - CancelActions() - pathing_attempts = 0 + return ///Perform some dumb idle behavior. diff --git a/code/datums/ai/hauntium/haunted_controller.dm b/code/datums/ai/hauntium/haunted_controller.dm index 9c3a1438dd6..515efcdc623 100644 --- a/code/datums/ai/hauntium/haunted_controller.dm +++ b/code/datums/ai/hauntium/haunted_controller.dm @@ -1,5 +1,6 @@ /datum/ai_controller/haunted + movement_delay = 0.4 SECONDS blackboard = list(BB_TO_HAUNT_LIST = list(), BB_HAUNT_TARGET, BB_HAUNTED_THROW_ATTEMPT_COUNT) @@ -14,9 +15,6 @@ UnregisterSignal(pawn, COMSIG_ITEM_EQUIPPED) return ..() //Run parent at end -/datum/ai_controller/haunted/able_to_run() - return TRUE - /datum/ai_controller/haunted/SelectBehaviors(delta_time) current_behaviors = list() var/obj/item/item_pawn = pawn diff --git a/code/datums/ai/monkey/monkey_behaviors.dm b/code/datums/ai/monkey/monkey_behaviors.dm index 841ab617b7f..11f3edde4b9 100644 --- a/code/datums/ai/monkey/monkey_behaviors.dm +++ b/code/datums/ai/monkey/monkey_behaviors.dm @@ -31,6 +31,7 @@ // Strong weapon else if(target.force > best_force) + living_pawn.drop_all_held_items() living_pawn.put_in_hands(target) controller.blackboard[BB_MONKEY_BEST_FORCE_FOUND] = target.force finish_action(controller, TRUE) @@ -77,7 +78,7 @@ var/mob/living/living_pawn = controller.pawn - victim.visible_message("[living_pawn] starts trying to take [target] from [controller.current_movement_target]!", "[living_pawn] tries to take [target]!") + victim.visible_message("[living_pawn] starts trying to take [target] from [victim]!", "[living_pawn] tries to take [target]!") controller.blackboard[BB_MONKEY_PICKPOCKETING] = TRUE diff --git a/code/datums/ai/monkey/monkey_controller.dm b/code/datums/ai/monkey/monkey_controller.dm index 833093becc8..15bacae2d11 100644 --- a/code/datums/ai/monkey/monkey_controller.dm +++ b/code/datums/ai/monkey/monkey_controller.dm @@ -5,6 +5,7 @@ have ways of interacting with a specific mob and control it. ///OOK OOK OOK /datum/ai_controller/monkey + movement_delay = 0.4 SECONDS blackboard = list(BB_MONKEY_AGRESSIVE = FALSE,\ BB_MONKEY_BEST_FORCE_FOUND = 0,\ BB_MONKEY_ENEMIES = list(),\ @@ -27,6 +28,7 @@ have ways of interacting with a specific mob and control it. /datum/ai_controller/monkey/TryPossessPawn(atom/new_pawn) if(!isliving(new_pawn)) return AI_CONTROLLER_INCOMPATIBLE + var/mob/living/living_pawn = new_pawn RegisterSignal(new_pawn, COMSIG_PARENT_ATTACKBY, .proc/on_attackby) RegisterSignal(new_pawn, COMSIG_ATOM_ATTACK_HAND, .proc/on_attack_hand) RegisterSignal(new_pawn, COMSIG_ATOM_ATTACK_PAW, .proc/on_attack_paw) @@ -37,11 +39,13 @@ have ways of interacting with a specific mob and control it. RegisterSignal(new_pawn, COMSIG_LIVING_TRY_SYRINGE, .proc/on_try_syringe) RegisterSignal(new_pawn, COMSIG_ATOM_HULK_ATTACK, .proc/on_attack_hulk) RegisterSignal(new_pawn, COMSIG_CARBON_CUFF_ATTEMPTED, .proc/on_attempt_cuff) + RegisterSignal(new_pawn, COMSIG_MOB_MOVESPEED_UPDATED, .proc/update_movespeed) + movement_delay = living_pawn.cached_multiplicative_slowdown return ..() //Run parent at end /datum/ai_controller/monkey/UnpossessPawn(destroy) UnregisterSignal(pawn, list(COMSIG_PARENT_ATTACKBY, COMSIG_ATOM_ATTACK_HAND, COMSIG_ATOM_ATTACK_PAW, COMSIG_ATOM_BULLET_ACT, COMSIG_ATOM_HITBY, COMSIG_MOVABLE_CROSSED, COMSIG_LIVING_START_PULL,\ - COMSIG_LIVING_TRY_SYRINGE, COMSIG_ATOM_HULK_ATTACK, COMSIG_CARBON_CUFF_ATTEMPTED)) + COMSIG_LIVING_TRY_SYRINGE, COMSIG_ATOM_HULK_ATTACK, COMSIG_CARBON_CUFF_ATTEMPTED, COMSIG_MOB_MOVESPEED_UPDATED)) return ..() //Run parent at end /datum/ai_controller/monkey/able_to_run() @@ -116,7 +120,7 @@ have ways of interacting with a specific mob and control it. for(var/obj/item/i in oview(2, living_pawn)) if(!istype(i)) continue - if(HAS_TRAIT(i, TRAIT_NEEDS_TWO_HANDS) || blackboard[BB_MONKEY_BLACKLISTITEMS][i] || i.force > blackboard[BB_MONKEY_BEST_FORCE_FOUND]) + if(HAS_TRAIT(i, TRAIT_NEEDS_TWO_HANDS) || blackboard[BB_MONKEY_BLACKLISTITEMS][i] || i.force < blackboard[BB_MONKEY_BEST_FORCE_FOUND]) continue W = i break @@ -216,3 +220,7 @@ have ways of interacting with a specific mob and control it. // chance of monkey retaliation if(prob(MONKEY_CUFF_RETALIATION_PROB)) retaliate(user) + +/datum/ai_controller/monkey/proc/update_movespeed(mob/living/pawn) + SIGNAL_HANDLER + movement_delay = pawn.cached_multiplicative_slowdown diff --git a/code/datums/ai/movement/_ai_movement.dm b/code/datums/ai/movement/_ai_movement.dm new file mode 100644 index 00000000000..6b0eb02cfb9 --- /dev/null +++ b/code/datums/ai/movement/_ai_movement.dm @@ -0,0 +1,18 @@ +///This datum is an abstract class that can be overriden for different types of movement +/datum/ai_movement + ///Assoc list ist of controllers that are currently moving as key, and what they are moving to as value + var/list/moving_controllers = list() + +/datum/ai_movement/proc/start_moving_towards(datum/ai_controller/controller, atom/current_movement_target) + controller.pathing_attempts = 0 + if(!moving_controllers.len) + START_PROCESSING(SSai_movement, src) + moving_controllers[controller] = current_movement_target + +/datum/ai_movement/proc/stop_moving_towards(datum/ai_controller/controller) + controller.pathing_attempts = 0 + moving_controllers -= controller + + if(!moving_controllers.len) + STOP_PROCESSING(SSai_movement, src) + diff --git a/code/datums/ai/movement/ai_movement_dumb.dm b/code/datums/ai/movement/ai_movement_dumb.dm new file mode 100644 index 00000000000..cb7d11286d8 --- /dev/null +++ b/code/datums/ai/movement/ai_movement_dumb.dm @@ -0,0 +1,27 @@ +///The most braindead type of movement, bee-line to the target with no concern of whats infront of us. +/datum/ai_movement/dumb + + +///Put your movement behavior in here! +/datum/ai_movement/dumb/process(delta_time) + for(var/datum/ai_controller/controller as anything in moving_controllers) + if(!COOLDOWN_FINISHED(controller, movement_cooldown)) + continue + COOLDOWN_START(controller, movement_cooldown, controller.movement_delay) + + var/atom/movable/movable_pawn = controller.pawn + + if(!isturf(movable_pawn.loc)) //No moving if not on a turf + continue + + var/current_loc = get_turf(movable_pawn) + + var/turf/target_turf = get_step_towards(movable_pawn, controller.current_movement_target) + + if(!is_type_in_typecache(target_turf, GLOB.dangerous_turfs)) + movable_pawn.Move(target_turf, get_dir(current_loc, target_turf)) + + if(current_loc == get_turf(movable_pawn)) //Did we even move after trying to move? + controller.pathing_attempts++ + if(controller.pathing_attempts >= MAX_PATHING_ATTEMPTS) + controller.CancelActions() diff --git a/code/datums/ai/objects/vending_machines/vending_machine_behaviors.dm b/code/datums/ai/objects/vending_machines/vending_machine_behaviors.dm index bb3ab54b290..88f85dbd280 100644 --- a/code/datums/ai/objects/vending_machines/vending_machine_behaviors.dm +++ b/code/datums/ai/objects/vending_machines/vending_machine_behaviors.dm @@ -10,6 +10,7 @@ if(controller.blackboard[BB_VENDING_BUSY_TILTING]) return + controller.ai_movement.stop_moving_towards(controller) controller.blackboard[BB_VENDING_BUSY_TILTING] = TRUE var/turf/target_turf = get_turf(controller.blackboard[BB_VENDING_CURRENT_TARGET]) new /obj/effect/temp_visual/telegraphing/vending_machine_tilt(target_turf) diff --git a/code/datums/ai/objects/vending_machines/vending_machine_controller.dm b/code/datums/ai/objects/vending_machines/vending_machine_controller.dm index e6a4e32206a..4f82bcc5a97 100644 --- a/code/datums/ai/objects/vending_machines/vending_machine_controller.dm +++ b/code/datums/ai/objects/vending_machines/vending_machine_controller.dm @@ -1,5 +1,6 @@ ///AI controller for vending machine gone rogue, Don't try using this on anything else, it wont work. /datum/ai_controller/vending_machine + movement_delay = 0.4 SECONDS blackboard = list(BB_VENDING_CURRENT_TARGET = null, BB_VENDING_TILT_COOLDOWN = 0, BB_VENDING_UNTILT_COOLDOWN = 0, diff --git a/code/modules/movespeed/_movespeed_modifier.dm b/code/modules/movespeed/_movespeed_modifier.dm index 2a49d842fe0..c5c5a457f48 100644 --- a/code/modules/movespeed/_movespeed_modifier.dm +++ b/code/modules/movespeed/_movespeed_modifier.dm @@ -186,6 +186,7 @@ GLOBAL_LIST_EMPTY(movespeed_modification_cache) continue . += amt cached_multiplicative_slowdown = . + SEND_SIGNAL(src, COMSIG_MOB_MOVESPEED_UPDATED) /// Get the move speed modifiers list of the mob /mob/proc/get_movespeed_modifiers() diff --git a/tgstation.dme b/tgstation.dme index dc3da18f9a7..cb0c24297e2 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -369,6 +369,7 @@ #include "code\controllers\subsystem\weather.dm" #include "code\controllers\subsystem\processing\acid.dm" #include "code\controllers\subsystem\processing\ai_controllers.dm" +#include "code\controllers\subsystem\processing\ai_movement.dm" #include "code\controllers\subsystem\processing\fastprocess.dm" #include "code\controllers\subsystem\processing\fields.dm" #include "code\controllers\subsystem\processing\fluids.dm" @@ -441,6 +442,8 @@ #include "code\datums\ai\hauntium\haunted_controller.dm" #include "code\datums\ai\monkey\monkey_behaviors.dm" #include "code\datums\ai\monkey\monkey_controller.dm" +#include "code\datums\ai\movement\_ai_movement.dm" +#include "code\datums\ai\movement\ai_movement_dumb.dm" #include "code\datums\ai\objects\vending_machines\vending_machine_behaviors.dm" #include "code\datums\ai\objects\vending_machines\vending_machine_controller.dm" #include "code\datums\announcers\_announcer.dm"