From 43473a4dac07c40faed45808b61b9c6de46ffcb6 Mon Sep 17 00:00:00 2001 From: san7890 Date: Sun, 23 Apr 2023 09:31:03 -0600 Subject: [PATCH] Turns Deer into Basic Mob - They Freeze At The Sight of Vehicles (#74784) ## About The Pull Request deers only show up in the BEPIS but i decided that they would be easy enough to turn into a basic mob (they were). it was so easy in fact that i decided to dip my toes into coding AI behavior, and made them freeze up whenever they see a vehicle. this required a lot of code in a bunch of places that i was quite unfamiliar with before starting this project, so do let me know if i glonked up anywhere and i can work on smoothing it out. ## Why It's Good For The Game one less simple animal on the list. deers staring at headlights is pretty cool i think, neato interaction for when you do get them beyond the joke the bepis makes i'm also amenable to dropping the whole "deer in headlights" code if you don't like that for w/e reason- just wanted to make them basic at the very least ## Changelog :cl: add: If you ever happen upon a wild deer, try not to ride your fancy vehicles too close to it as it'll freeze up like a... you know where I'm going with this. /:cl: --------- Co-authored-by: Mothblocks <35135081+Mothblocks@users.noreply.github.com> --- code/__DEFINES/ai.dm | 11 +++++ .../subsystem/movement/movement_types.dm | 22 +++++++++ .../basic_ai_behaviors/stop_and_stare.dm | 28 +++++++++++ .../basic_subtrees/speech_subtree.dm | 6 +++ .../basic_subtrees/stare_at_thing.dm | 14 ++++++ .../ai/movement/ai_movement_complete_stop.dm | 15 ++++++ .../mob/living/basic/farm_animals/deer.dm | 49 +++++++++++++++++++ .../simple_animal/friendly/farm_animals.dm | 29 ----------- code/modules/research/bepis.dm | 2 +- .../unit_tests/simple_animal_freeze.dm | 1 - code/modules/vehicles/cars/clowncar.dm | 2 +- tgstation.dme | 4 ++ 12 files changed, 151 insertions(+), 32 deletions(-) create mode 100644 code/datums/ai/basic_mobs/basic_ai_behaviors/stop_and_stare.dm create mode 100644 code/datums/ai/basic_mobs/basic_subtrees/stare_at_thing.dm create mode 100644 code/datums/ai/movement/ai_movement_complete_stop.dm create mode 100644 code/modules/mob/living/basic/farm_animals/deer.dm diff --git a/code/__DEFINES/ai.dm b/code/__DEFINES/ai.dm index 5475eccc61e..9c9b2f98b88 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 bcf1d281ab5..c5dd5d3993a 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 00000000000..989837cf88d --- /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 b3b7bd09d0a..fdc19c91146 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 00000000000..1beb70f710c --- /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 00000000000..7805eeb3f6f --- /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 00000000000..a016f8b74a6 --- /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 47fbb0d20a5..b8a23c16da7 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 83d25211e8b..fab66268f94 100644 --- a/code/modules/research/bepis.dm +++ b/code/modules/research/bepis.dm @@ -278,7 +278,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 0a71847d5fb..54f4e578458 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 2ff0929a3dc..3efe4469eaf 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 421b1f0cba4..dce87ecc8e2 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -757,6 +757,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" @@ -769,6 +770,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" @@ -806,6 +808,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" @@ -3887,6 +3890,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"