diff --git a/code/__DEFINES/ai.dm b/code/__DEFINES/ai.dm index 5475eccc61efbd..9c9b2f98b88749 100644 --- a/code/__DEFINES/ai.dm +++ b/code/__DEFINES/ai.dm @@ -237,6 +237,17 @@ ///How long have we spent with no target? #define BB_TARGETLESS_TIME "BB_targetless_time" +/// Is there something that scared us into being stationary? If so, hold the reference here +#define BB_STATIONARY_CAUSE "BB_thing_that_made_us_stationary" +///How long should we remain stationary for? +#define BB_STATIONARY_SECONDS "BB_stationary_time_in_seconds" +///Should we move towards the target that triggered us to be stationary? +#define BB_STATIONARY_MOVE_TO_TARGET "BB_stationary_move_to_target" +/// What targets will trigger us to be stationary? Must be a list. +#define BB_STATIONARY_TARGETS "BB_stationary_targets" +/// How often can we get spooked by a target? +#define BB_STATIONARY_COOLDOWN "BB_stationary_cooldown" + ///List of mobs who have damaged us #define BB_BASIC_MOB_RETALIATE_LIST "BB_basic_mob_shitlist" diff --git a/code/controllers/subsystem/movement/movement_types.dm b/code/controllers/subsystem/movement/movement_types.dm index bcf1d281ab5f47..c5dd5d3993a95d 100644 --- a/code/controllers/subsystem/movement/movement_types.dm +++ b/code/controllers/subsystem/movement/movement_types.dm @@ -739,6 +739,28 @@ moving.Move(target_turf, get_dir(moving, target_turf)) return old_loc != moving?.loc ? MOVELOOP_SUCCESS : MOVELOOP_FAILURE +/** + * Assigns a target to a move loop that immediately freezes for a set duration of time. + * + * Returns TRUE if the loop sucessfully started, or FALSE if it failed + * + * Arguments: + * moving - The atom we want to move + * halted_turf - The turf we want to freeze on. This should typically be the loc of moving. + * delay - How many deci-seconds to wait between fires. Defaults to the lowest value, 0.1 + * timeout - Time in deci-seconds until the moveloop self expires. This should be considered extremely non-optional as it will completely stun out the movement loop forever if unset. + * subsystem - The movement subsystem to use. Defaults to SSmovement. Only one loop can exist for any one subsystem + * priority - Defines how different move loops override each other. Lower numbers beat higher numbers, equal defaults to what currently exists. Defaults to MOVEMENT_DEFAULT_PRIORITY + * flags - Set of bitflags that effect move loop behavior in some way. Check _DEFINES/movement.dm + */ +/datum/controller/subsystem/move_manager/proc/freeze(moving, halted_turf, delay, timeout, subsystem, priority, flags, datum/extra_info) + return add_to_loop(moving, subsystem, /datum/move_loop/freeze, priority, flags, extra_info, delay, timeout, halted_turf) + +/// As close as you can get to a "do-nothing" move loop, the pure intention of this is to absolutely resist all and any automated movement until the move loop times out. +/datum/move_loop/freeze + +/datum/move_loop/freeze/move() + return MOVELOOP_SUCCESS // it's successful because it's not moving. we autoclear outselves when `timeout` is reached /** * Helper proc for the move_rand datum diff --git a/code/datums/ai/basic_mobs/basic_ai_behaviors/stop_and_stare.dm b/code/datums/ai/basic_mobs/basic_ai_behaviors/stop_and_stare.dm new file mode 100644 index 00000000000000..989837cf88d782 --- /dev/null +++ b/code/datums/ai/basic_mobs/basic_ai_behaviors/stop_and_stare.dm @@ -0,0 +1,28 @@ +/// Makes a mob simply stop and stare at a movable... yea... +/datum/ai_behavior/stop_and_stare + behavior_flags = AI_BEHAVIOR_MOVE_AND_PERFORM + +/datum/ai_behavior/stop_and_stare/setup(datum/ai_controller/controller, target_key) + . = ..() + var/datum/weakref/weak_target = controller.blackboard[target_key] + var/atom/movable/target = weak_target?.resolve() + return ismovable(target) && isturf(target.loc) && ismob(controller.pawn) + +/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/datum/weakref/weak_target = controller.blackboard[target_key] + var/atom/movable/target = weak_target?.resolve() + if(!ismovable(target) || !isturf(target.loc)) // just to make sure that nothing funky happened between setup and perform + return + + var/mob/pawn_mob = controller.pawn + var/turf/pawn_turf = get_turf(pawn_mob) + + pawn_mob.face_atom(target) + pawn_mob.balloon_alert_to_viewers("stops and stares...") + set_movement_target(controller, pawn_turf, /datum/ai_movement/complete_stop) + + if(controller.blackboard[BB_STATIONARY_MOVE_TO_TARGET]) + addtimer(CALLBACK(src, PROC_REF(set_movement_target), controller, target, initial(controller.ai_movement)), (controller.blackboard[BB_STATIONARY_SECONDS] + 1 SECONDS)) diff --git a/code/datums/ai/basic_mobs/basic_subtrees/speech_subtree.dm b/code/datums/ai/basic_mobs/basic_subtrees/speech_subtree.dm index b3b7bd09d0a7e8..fdc19c91146aef 100644 --- a/code/datums/ai/basic_mobs/basic_subtrees/speech_subtree.dm +++ b/code/datums/ai/basic_mobs/basic_subtrees/speech_subtree.dm @@ -101,6 +101,12 @@ speak = GLOB.wisdoms //Done here so it's setup properly sound = list() +/datum/ai_planning_subtree/random_speech/deer + speech_chance = 1 + speak = list("Weeeeeeee?", "Weeee", "WEOOOOOOOOOO") + emote_hear = list("brays.") + emote_see = list("shakes her head.") + /datum/ai_planning_subtree/random_speech/dog speech_chance = 1 diff --git a/code/datums/ai/basic_mobs/basic_subtrees/stare_at_thing.dm b/code/datums/ai/basic_mobs/basic_subtrees/stare_at_thing.dm new file mode 100644 index 00000000000000..1beb70f710c6e6 --- /dev/null +++ b/code/datums/ai/basic_mobs/basic_subtrees/stare_at_thing.dm @@ -0,0 +1,14 @@ +/// Locate a thing (practically any atom) to stop and stare at. +/datum/ai_planning_subtree/stare_at_thing + +/datum/ai_planning_subtree/stare_at_thing/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + var/datum/weakref/weak_target = controller.blackboard[BB_STATIONARY_CAUSE] + var/atom/target = weak_target?.resolve() + + if(isnull(target)) // No target? Time to locate one using the list we set in this mob's blackboard. + var/list/potential_scares = controller.blackboard[BB_STATIONARY_TARGETS] + controller.queue_behavior(/datum/ai_behavior/find_and_set/in_list, BB_STATIONARY_CAUSE, potential_scares) + return + + controller.queue_behavior(/datum/ai_behavior/stop_and_stare, BB_STATIONARY_CAUSE) + diff --git a/code/datums/ai/movement/ai_movement_complete_stop.dm b/code/datums/ai/movement/ai_movement_complete_stop.dm new file mode 100644 index 00000000000000..7805eeb3f6f656 --- /dev/null +++ b/code/datums/ai/movement/ai_movement_complete_stop.dm @@ -0,0 +1,15 @@ +/// Come to a complete stop for a set amount of time. +/datum/ai_movement/complete_stop + max_pathing_attempts = INFINITE // path all you want, you can not escape your fate + +/datum/ai_movement/complete_stop/start_moving_towards(datum/ai_controller/controller, atom/current_movement_target, min_distance) + . = ..() + var/atom/movable/moving = controller.pawn + var/stopping_time = controller.blackboard[BB_STATIONARY_SECONDS] + var/delay_time = (stopping_time * 0.5) // no real reason to fire any more often than this really + // assume that the current_movement_target is our location + var/datum/move_loop/loop = SSmove_manager.freeze(moving, current_movement_target, delay = delay_time, timeout = stopping_time, subsystem = SSai_movement, extra_info = controller) + RegisterSignal(loop, COMSIG_MOVELOOP_PREPROCESS_CHECK, PROC_REF(pre_move)) + +/datum/ai_movement/complete_stop/allowed_to_move(datum/move_loop/source) + return // no movement allowed diff --git a/code/modules/mob/living/basic/farm_animals/deer.dm b/code/modules/mob/living/basic/farm_animals/deer.dm new file mode 100644 index 00000000000000..a016f8b74a6f95 --- /dev/null +++ b/code/modules/mob/living/basic/farm_animals/deer.dm @@ -0,0 +1,49 @@ +/mob/living/basic/deer + name = "doe" + desc = "A gentle, peaceful forest animal. How did this get into space?" + icon_state = "deer-doe" + icon_living = "deer-doe" + icon_dead = "deer-doe-dead" + gender = FEMALE + mob_biotypes = MOB_ORGANIC|MOB_BEAST + speak_emote = list("grunts", "grunts lowly") + butcher_results = list(/obj/item/food/meat/slab = 3) + response_help_continuous = "pets" + response_help_simple = "pet" + response_disarm_continuous = "gently nudges" + response_disarm_simple = "gently nudges aside" + response_harm_continuous = "kicks" + response_harm_simple = "kick" + attack_verb_continuous = "bucks" + attack_verb_simple = "buck" + attack_sound = 'sound/weapons/punch1.ogg' + health = 75 + maxHealth = 75 + blood_volume = BLOOD_VOLUME_NORMAL + ai_controller = /datum/ai_controller/basic_controller/deer + /// Things that will scare us into being stationary. Vehicles are scary to deers because they might have headlights. + var/static/list/stationary_scary_things = list(/obj/vehicle) + +/mob/living/basic/deer/Initialize(mapload) + . = ..() + AddElement(/datum/element/footstep, footstep_type = FOOTSTEP_MOB_SHOE) + var/time_to_freeze_for = (rand(5, 10) SECONDS) + ai_controller.blackboard[BB_STATIONARY_SECONDS] = time_to_freeze_for + ai_controller.blackboard[BB_STATIONARY_COOLDOWN] = (time_to_freeze_for * (rand(3, 5))) + ai_controller.blackboard[BB_STATIONARY_TARGETS] = stationary_scary_things + +/datum/ai_controller/basic_controller/deer + blackboard = list( + BB_BASIC_MOB_FLEEING = TRUE, + BB_STATIONARY_MOVE_TO_TARGET = TRUE, + BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/ignore_faction, + ) + ai_traits = STOP_MOVING_WHEN_PULLED + ai_movement = /datum/ai_movement/basic_avoidance + idle_behavior = /datum/idle_behavior/idle_random_walk + planning_subtrees = list( + /datum/ai_planning_subtree/random_speech/deer, + /datum/ai_planning_subtree/stare_at_thing, + /datum/ai_planning_subtree/find_nearest_thing_which_attacked_me_to_flee, + /datum/ai_planning_subtree/flee_target, + ) diff --git a/code/modules/mob/living/simple_animal/friendly/farm_animals.dm b/code/modules/mob/living/simple_animal/friendly/farm_animals.dm index 47fbb0d20a540e..b8a23c16da74c0 100644 --- a/code/modules/mob/living/simple_animal/friendly/farm_animals.dm +++ b/code/modules/mob/living/simple_animal/friendly/farm_animals.dm @@ -250,32 +250,3 @@ GLOBAL_VAR_INIT(chicken_count, 0) location_allowlist = typecacheof(list(/turf)),\ spoilable = TRUE,\ ) - -/mob/living/simple_animal/deer - name = "doe" - desc = "A gentle, peaceful forest animal. How did this get into space?" - icon_state = "deer-doe" - icon_living = "deer-doe" - icon_dead = "deer-doe-dead" - gender = FEMALE - mob_biotypes = MOB_ORGANIC|MOB_BEAST - speak = list("Weeeeeeee?","Weeee","WEOOOOOOOOOO") - speak_emote = list("grunts","grunts lowly") - emote_hear = list("brays.") - emote_see = list("shakes her head.") - speak_chance = 1 - turns_per_move = 5 - butcher_results = list(/obj/item/food/meat/slab = 3) - response_help_continuous = "pets" - response_help_simple = "pet" - response_disarm_continuous = "gently nudges" - response_disarm_simple = "gently nudges aside" - response_harm_continuous = "kicks" - response_harm_simple = "kick" - attack_verb_continuous = "bucks" - attack_verb_simple = "buck" - attack_sound = 'sound/weapons/punch1.ogg' - health = 75 - maxHealth = 75 - blood_volume = BLOOD_VOLUME_NORMAL - footstep_type = FOOTSTEP_MOB_SHOE diff --git a/code/modules/research/bepis.dm b/code/modules/research/bepis.dm index 7eb20098f1c4d7..a88c65307eba7e 100644 --- a/code/modules/research/bepis.dm +++ b/code/modules/research/bepis.dm @@ -279,7 +279,7 @@ return if(gauss_real <= -1) //Critical Failure say("ERROR: CRITICAL MACHIME MALFUNCTI- ON. CURRENCY IS NOT CRASH. CANNOT COMPUTE COMMAND: 'make bucks'") //not a typo, for once. - new /mob/living/simple_animal/deer(dropturf, 1) + new /mob/living/basic/deer(dropturf, 1) use_power(MACHINE_OVERLOAD * power_saver) //To prevent gambling at low cost and also prevent spamming for infinite deer. return //Minor Failure diff --git a/code/modules/unit_tests/simple_animal_freeze.dm b/code/modules/unit_tests/simple_animal_freeze.dm index 9feb864a056b7a..06912c55397927 100644 --- a/code/modules/unit_tests/simple_animal_freeze.dm +++ b/code/modules/unit_tests/simple_animal_freeze.dm @@ -42,7 +42,6 @@ /mob/living/simple_animal/crab/evil/kreb, /mob/living/simple_animal/crab/jon, /mob/living/simple_animal/crab/kreb, - /mob/living/simple_animal/deer, /mob/living/simple_animal/drone, /mob/living/simple_animal/drone/classic, /mob/living/simple_animal/drone/derelict, diff --git a/code/modules/vehicles/cars/clowncar.dm b/code/modules/vehicles/cars/clowncar.dm index 2ff0929a3dc681..3efe4469eaf2d6 100644 --- a/code/modules/vehicles/cars/clowncar.dm +++ b/code/modules/vehicles/cars/clowncar.dm @@ -108,7 +108,7 @@ /obj/vehicle/sealed/car/clowncar/Bump(atom/bumped) . = ..() - if(isliving(bumped) && !istype(bumped, /mob/living/simple_animal/deer)) + if(isliving(bumped) && !istype(bumped, /mob/living/basic/deer)) if(ismegafauna(bumped)) return var/mob/living/hittarget_living = bumped diff --git a/tgstation.dme b/tgstation.dme index 7a231abd8822ff..f28b1b52538784 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -839,6 +839,7 @@ #include "code\datums\ai\basic_mobs\basic_ai_behaviors\pick_up_item.dm" #include "code\datums\ai\basic_mobs\basic_ai_behaviors\run_away_from_target.dm" #include "code\datums\ai\basic_mobs\basic_ai_behaviors\step_towards_turf.dm" +#include "code\datums\ai\basic_mobs\basic_ai_behaviors\stop_and_stare.dm" #include "code\datums\ai\basic_mobs\basic_ai_behaviors\targeted_mob_ability.dm" #include "code\datums\ai\basic_mobs\basic_ai_behaviors\targetting.dm" #include "code\datums\ai\basic_mobs\basic_ai_behaviors\tipped_reaction.dm" @@ -851,6 +852,7 @@ #include "code\datums\ai\basic_mobs\basic_subtrees\simple_find_target.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\sleep_with_no_target.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\speech_subtree.dm" +#include "code\datums\ai\basic_mobs\basic_subtrees\stare_at_thing.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\target_retaliate.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\targeted_mob_ability.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\tipped_subtree.dm" @@ -888,6 +890,7 @@ #include "code\datums\ai\monkey\punpun_subtrees.dm" #include "code\datums\ai\movement\_ai_movement.dm" #include "code\datums\ai\movement\ai_movement_basic_avoidance.dm" +#include "code\datums\ai\movement\ai_movement_complete_stop.dm" #include "code\datums\ai\movement\ai_movement_dumb.dm" #include "code\datums\ai\movement\ai_movement_jps.dm" #include "code\datums\ai\objects\mod.dm" @@ -3968,6 +3971,7 @@ #include "code\modules\mob\living\basic\festivus_pole.dm" #include "code\modules\mob\living\basic\health_adjustment.dm" #include "code\modules\mob\living\basic\tree.dm" +#include "code\modules\mob\living\basic\farm_animals\deer.dm" #include "code\modules\mob\living\basic\farm_animals\pig.dm" #include "code\modules\mob\living\basic\farm_animals\rabbit.dm" #include "code\modules\mob\living\basic\farm_animals\sheep.dm"