From 8164175aa2b5c60555ad0a7f99f7ac6b5e90df99 Mon Sep 17 00:00:00 2001 From: SkyratBot <59378654+SkyratBot@users.noreply.github.com> Date: Sun, 23 Apr 2023 19:01:36 +0100 Subject: [PATCH] [MIRROR] Turns Deer into Basic Mob - They Freeze At The Sight of Vehicles [MDB IGNORE] (#20711) * 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> * Turns Deer into Basic Mob - They Freeze At The Sight of Vehicles --------- Co-authored-by: san7890 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 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"