diff --git a/_maps/map_files/stations/emeraldstation.dmm b/_maps/map_files/stations/emeraldstation.dmm index 750f2d9aa87..2b60e7c9f1f 100644 --- a/_maps/map_files/stations/emeraldstation.dmm +++ b/_maps/map_files/stations/emeraldstation.dmm @@ -96809,7 +96809,7 @@ /obj/structure/flora/ausbushes/fullgrass, /obj/structure/flora/ausbushes/grassybush, /obj/structure/railing, -/mob/living/simple_animal/deer{ +/mob/living/basic/deer{ name = "Reed" }, /turf/simulated/floor/grass, diff --git a/code/__DEFINES/ai/blackboard_defines.dm b/code/__DEFINES/ai/blackboard_defines.dm index ce749d8c5a5..b317d977c87 100644 --- a/code/__DEFINES/ai/blackboard_defines.dm +++ b/code/__DEFINES/ai/blackboard_defines.dm @@ -42,6 +42,15 @@ ///List of mobs who have damaged us #define BB_BASIC_MOB_RETALIATE_LIST "BB_basic_mob_shitlist" +//Hunting BB keys + +/// Key that holds our current hunting target +#define BB_CURRENT_HUNTING_TARGET "BB_current_hunting_target" +/// Key that holds our less priority hunting target +#define BB_LOW_PRIORITY_HUNTING_TARGET "BB_low_priority_hunting_target" +/// Key that holds the cooldown for our hunting subtree +#define BB_HUNTING_COOLDOWN(type) "BB_HUNTING_COOLDOWN_[type]" + // Food and eating /// list of foods this mob likes @@ -62,6 +71,17 @@ /// the motherfucker who tipped us #define BB_BASIC_MOB_TIPPER "BB_basic_tip_tipper" +/// 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" + // Misc /// For /datum/ai_behavior/find_potential_targets, what if any field are we using currently diff --git a/code/__DEFINES/basic_mob_defines.dm b/code/__DEFINES/basic_mob_defines.dm new file mode 100644 index 00000000000..0942b6ef759 --- /dev/null +++ b/code/__DEFINES/basic_mob_defines.dm @@ -0,0 +1,16 @@ +//deer + +/// Our water target +#define BB_DEER_WATER_TARGET "deer_water_target" +/// Our grass target +#define BB_DEER_GRASS_TARGET "deer_grass_target" +/// Our tree target +#define BB_DEER_TREE_TARGET "deer_tree_target" +/// Our temporary playmate +#define BB_DEER_PLAYFRIEND "deer_playfriend" +/// Our home target +#define BB_DEER_TREEHOME "deer_home" +/// Our resting duration +#define BB_DEER_RESTING "deer_resting" +/// Time till our next rest duration +#define BB_DEER_NEXT_REST_TIMER "deer_next_rest_timer" diff --git a/code/__DEFINES/misc_defines.dm b/code/__DEFINES/misc_defines.dm index ba08edc8af7..73cc3f1168e 100644 --- a/code/__DEFINES/misc_defines.dm +++ b/code/__DEFINES/misc_defines.dm @@ -435,6 +435,7 @@ #define INVESTIGATE_SINGULO "singulo" #define INVESTIGATE_SUPERMATTER "supermatter" #define INVESTIGATE_WIRES "wires" +#define INVESTIGATE_DEATHS "deaths" // The SQL version required by this version of the code #define SQL_VERSION 67 diff --git a/code/datums/ai/basic_mobs/basic_ai_behaviors/emote_with_target.dm b/code/datums/ai/basic_mobs/basic_ai_behaviors/emote_with_target.dm new file mode 100644 index 00000000000..5884d08020f --- /dev/null +++ b/code/datums/ai/basic_mobs/basic_ai_behaviors/emote_with_target.dm @@ -0,0 +1,24 @@ +/datum/ai_behavior/emote_on_target + behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH + +/datum/ai_behavior/emote_on_target/setup(datum/ai_controller/controller, target_key) + . = ..() + var/atom/hunt_target = controller.blackboard[target_key] + if(isnull(hunt_target)) + return FALSE + set_movement_target(controller, hunt_target) + +/datum/ai_behavior/emote_on_target/perform(seconds_per_tick, datum/ai_controller/controller, target_key, list/emote_list) + var/atom/target = controller.blackboard[target_key] + if(!length(emote_list) || isnull(target)) + return AI_BEHAVIOR_FAILED | AI_BEHAVIOR_DELAY + run_emote(controller.pawn, target, emote_list) + return AI_BEHAVIOR_SUCCEEDED | AI_BEHAVIOR_DELAY + +/datum/ai_behavior/emote_on_target/finish_action(datum/ai_controller/controller, succeeded, target_key) + . = ..() + if(succeeded) + controller.clear_blackboard_key(target_key) + +/datum/ai_behavior/emote_on_target/proc/run_emote(mob/living/living_pawn, atom/target, list/emote_list) + living_pawn.custom_emote(EMOTE_VISIBLE, "[pick(emote_list)] [target]") 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..7e308866c91 --- /dev/null +++ b/code/datums/ai/basic_mobs/basic_ai_behaviors/stop_and_stare.dm @@ -0,0 +1,26 @@ +/// 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/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) + 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 + return AI_BEHAVIOR_DELAY + + var/mob/pawn_mob = controller.pawn + var/turf/pawn_turf = get_turf(pawn_mob) + + pawn_mob.face_atom(target) + 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)) + return AI_BEHAVIOR_DELAY 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..780b1b58846 --- /dev/null +++ b/code/datums/ai/basic_mobs/basic_subtrees/stare_at_thing.dm @@ -0,0 +1,12 @@ +/// 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/select_behaviors(datum/ai_controller/controller, seconds_per_tick) + var/atom/target = controller.blackboard[BB_STATIONARY_CAUSE] + + 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/basic_mobs/hunting_behavior/hunting_behaviors.dm b/code/datums/ai/basic_mobs/hunting_behavior/hunting_behaviors.dm new file mode 100644 index 00000000000..cc4a8bc607e --- /dev/null +++ b/code/datums/ai/basic_mobs/hunting_behavior/hunting_behaviors.dm @@ -0,0 +1,125 @@ +/** + * Tells the AI to find a certain target nearby to hunt. + * If a target has been found, we will start to move towards it, and eventually attack it. + */ +/datum/ai_planning_subtree/find_and_hunt_target + /// What key in the blacbkboard do we store our hunting target? + /// If you want to have multiple hunting behaviors on a controller be sure that this is unique + var/target_key = BB_CURRENT_HUNTING_TARGET + /// What behavior to execute if we have no target + var/finding_behavior = /datum/ai_behavior/find_hunt_target + /// What behavior to execute if we do have a target + var/hunting_behavior = /datum/ai_behavior/hunt_target + /// What targets we're hunting for + var/list/hunt_targets + /// In what radius will we hunt + var/hunt_range = 2 + /// What are the chances we hunt something at any given moment + var/hunt_chance = 100 + ///do we finish planning subtree + var/finish_planning = TRUE + +/datum/ai_planning_subtree/find_and_hunt_target/New() + . = ..() + hunt_targets = typecacheof(hunt_targets) + +/datum/ai_planning_subtree/find_and_hunt_target/select_behaviors(datum/ai_controller/controller, seconds_per_tick) + if(!SPT_PROB(hunt_chance, seconds_per_tick)) + return + + if(controller.blackboard[BB_HUNTING_COOLDOWN(type)] >= world.time) + return + + if(!controller.blackboard_key_exists(target_key)) + controller.queue_behavior(finding_behavior, target_key, hunt_targets, hunt_range) + return + + // We ARE hunting something, execute the hunt. + // Note that if our AI controller has multiple hunting subtrees set, + // we may accidentally be executing another tree's hunt - not ideal, + // try to set a unique target key if you have multiple + + controller.queue_behavior(hunting_behavior, target_key, BB_HUNTING_COOLDOWN(type)) + if(finish_planning) + return SUBTREE_RETURN_FINISH_PLANNING //If we're hunting we're too busy for anything else + +/// Finds a specific atom type to hunt. +/datum/ai_behavior/find_hunt_target + ///is this only meant to search for turf types? + var/search_turf_types = FALSE + +/datum/ai_behavior/find_hunt_target/perform(seconds_per_tick, datum/ai_controller/controller, hunting_target_key, types_to_hunt, hunt_range) + var/mob/living/living_mob = controller.pawn + var/list/interesting_objects = search_turf_types ? RANGE_TURFS(hunt_range, living_mob) : oview(hunt_range, living_mob) + for(var/atom/possible_dinner as anything in typecache_filter_list(interesting_objects, types_to_hunt)) + if(!valid_dinner(living_mob, possible_dinner, hunt_range, controller, seconds_per_tick)) + continue + controller.set_blackboard_key(hunting_target_key, possible_dinner) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + +/datum/ai_behavior/find_hunt_target/proc/valid_dinner(mob/living/source, atom/dinner, radius, datum/ai_controller/controller, seconds_per_tick) + if(isliving(dinner)) + var/mob/living/living_target = dinner + if(living_target.stat == DEAD) + return FALSE + + return can_see(source, dinner, radius) + +/datum/ai_behavior/find_hunt_target/search_turf_types + search_turf_types = TRUE + +/// Hunts down a specific atom type. +/datum/ai_behavior/hunt_target + behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH + /// How long do we have to wait after a successful hunt? + var/hunt_cooldown = 5 SECONDS + /// Do we reset the target after attacking something, so we can check for status changes. + var/always_reset_target = FALSE + +/datum/ai_behavior/hunt_target/setup(datum/ai_controller/controller, hunting_target_key, hunting_cooldown_key) + . = ..() + var/atom/hunt_target = controller.blackboard[hunting_target_key] + if(isnull(hunt_target)) + return FALSE + set_movement_target(controller, hunt_target) + +/datum/ai_behavior/hunt_target/perform(seconds_per_tick, datum/ai_controller/controller, hunting_target_key, hunting_cooldown_key) + var/mob/living/hunter = controller.pawn + var/atom/hunted = controller.blackboard[hunting_target_key] + + if(QDELETED(hunted)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + target_caught(hunter, hunted) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + +/datum/ai_behavior/hunt_target/proc/target_caught(mob/living/hunter, atom/hunted) + if(isliving(hunted)) // Are we hunting a living mob? + var/mob/living/living_target = hunted + hunter.manual_emote("chomps [living_target]!") + living_target.investigate_log("has been killed by [key_name(hunter)].", INVESTIGATE_DEATHS) + living_target.death() + + else if(isfood(hunted)) + hunted.attack_animal(hunter) + + else // We're hunting an object, and should delete it instead of killing it. Mostly useful for decal bugs like ants or spider webs. + hunter.manual_emote("chomps [hunted]!") + qdel(hunted) + +/datum/ai_behavior/hunt_target/finish_action(datum/ai_controller/controller, succeeded, hunting_target_key, hunting_cooldown_key) + . = ..() + if(succeeded && hunting_cooldown_key) + controller.set_blackboard_key(hunting_cooldown_key, world.time + hunt_cooldown) + else if(hunting_target_key) + controller.clear_blackboard_key(hunting_target_key) + if(always_reset_target && hunting_target_key) + controller.clear_blackboard_key(hunting_target_key) + +/datum/ai_behavior/hunt_target/interact_with_target + /// Which intent should we use to interact with + var/behavior_intent = INTENT_HARM + +/datum/ai_behavior/hunt_target/interact_with_target/target_caught(mob/living/hunter, obj/structure/cable/hunted) + var/datum/ai_controller/controller = hunter.ai_controller + controller.ai_interact(target = hunted, intent = behavior_intent) diff --git a/code/datums/ai/generic/find_and_set.dm b/code/datums/ai/generic/find_and_set.dm index 52972c7bfca..ffef9def4f9 100644 --- a/code/datums/ai/generic/find_and_set.dm +++ b/code/datums/ai/generic/find_and_set.dm @@ -29,3 +29,15 @@ var/list/found = typecache_filter_list(oview(search_range, controller.pawn), locate_paths) if(length(found)) return pick(found) + +/datum/ai_behavior/find_and_set/in_list/turf_types + +/datum/ai_behavior/find_and_set/in_list/turf_types/search_tactic(datum/ai_controller/controller, locate_paths, search_range) + var/list/found = RANGE_TURFS(search_range, controller.pawn) + shuffle_inplace(found) + for(var/turf/possible_turf as anything in found) + if(!is_type_in_typecache(possible_turf, locate_paths)) + continue + if(can_see(controller.pawn, possible_turf, search_range)) + return possible_turf + return null diff --git a/code/datums/ai/movement/complete_stop.dm b/code/datums/ai/movement/complete_stop.dm new file mode 100644 index 00000000000..e9609a30dda --- /dev/null +++ b/code/datums/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 = INFINITY // 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 = GLOB.move_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 FALSE diff --git a/code/game/objects/structures/crates_lockers/crittercrate.dm b/code/game/objects/structures/crates_lockers/crittercrate.dm index 4f22cfa549d..99d1e7b2de8 100644 --- a/code/game/objects/structures/crates_lockers/crittercrate.dm +++ b/code/game/objects/structures/crates_lockers/crittercrate.dm @@ -45,17 +45,19 @@ desc = "A crate designed for safe transport of animals. The contents are a mystery." /obj/structure/closet/critter/random/populate_contents() - content_mob = pick(/mob/living/simple_animal/pet/dog/corgi, - /mob/living/simple_animal/pet/dog/corgi/lisa, + content_mob = pick( /mob/living/basic/cow, + /mob/living/basic/deer, /mob/living/basic/pig, + + /mob/living/simple_animal/pet/dog/corgi, + /mob/living/simple_animal/pet/dog/corgi/lisa, /mob/living/simple_animal/hostile/retaliate/goat, /mob/living/simple_animal/turkey, /mob/living/simple_animal/chick, /mob/living/simple_animal/pet/cat, /mob/living/simple_animal/pet/dog/pug, /mob/living/simple_animal/pet/dog/fox, - /mob/living/simple_animal/deer, /mob/living/simple_animal/bunny) /obj/structure/closet/critter/corgi @@ -115,7 +117,7 @@ /obj/structure/closet/critter/deer name = "deer crate" - content_mob = /mob/living/simple_animal/deer + content_mob = /mob/living/basic/deer /obj/structure/closet/critter/bunny name = "bunny crate" diff --git a/code/modules/mob/living/basic/cow.dm b/code/modules/mob/living/basic/farm_animals/cow.dm similarity index 100% rename from code/modules/mob/living/basic/cow.dm rename to code/modules/mob/living/basic/farm_animals/cow.dm 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..e7cdad1f8fc --- /dev/null +++ b/code/modules/mob/living/basic/farm_animals/deer.dm @@ -0,0 +1,27 @@ +/mob/living/basic/deer + name = "deer" + desc = "A strong, brave deer." + icon_state = "deer" + icon_living = "deer" + icon_dead = "deer_dead" + faction = list("neutral", "jungle") + see_in_dark = 0 //I'm so funny + butcher_results = list(/obj/item/food/meat = 4) + mob_biotypes = MOB_ORGANIC | MOB_BEAST + gold_core_spawnable = FRIENDLY_SPAWN + 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, + /obj/tgvehicle, + ) + +/mob/living/basic/deer/Initialize(mapload) + . = ..() + AddElement(/datum/element/wears_collar) + AddElement(/datum/element/ai_retaliate) + AddComponent(/datum/component/footstep, FOOTSTEP_MOB_SHOE) + var/time_to_freeze_for = (rand(5, 10) SECONDS) + ai_controller.set_blackboard_key(BB_STATIONARY_SECONDS, time_to_freeze_for) + ai_controller.set_blackboard_key(BB_STATIONARY_COOLDOWN, (time_to_freeze_for * (rand(3, 5)))) + ai_controller.set_blackboard_key(BB_STATIONARY_TARGETS, typecacheof(stationary_scary_things)) diff --git a/code/modules/mob/living/basic/farm_animals/deer_ai.dm b/code/modules/mob/living/basic/farm_animals/deer_ai.dm new file mode 100644 index 00000000000..c0bbd85b984 --- /dev/null +++ b/code/modules/mob/living/basic/farm_animals/deer_ai.dm @@ -0,0 +1,147 @@ +/datum/ai_planning_subtree/random_speech/deer + speech_chance = 2 + speak_verbs = list("grunts", "snorts", "bleats") + emote_hear = list("grunts.", "snorts.", "bleats.") + emote_see = list("shakes its head.") + +/datum/ai_controller/basic_controller/deer + blackboard = list( + BB_STATIONARY_MOVE_TO_TARGET = TRUE, + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + ) + ai_traits = AI_FLAG_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, + /datum/ai_planning_subtree/rest_at_home, + /datum/ai_planning_subtree/play_with_friends, + /datum/ai_planning_subtree/find_and_hunt_target/mark_territory, + /datum/ai_planning_subtree/find_and_hunt_target/graze, + /datum/ai_planning_subtree/find_and_hunt_target/drink_water, + ) + +/// subtree to go around drinking water +/datum/ai_planning_subtree/find_and_hunt_target/drink_water + target_key = BB_DEER_WATER_TARGET + finding_behavior = /datum/ai_behavior/find_and_set/in_list/turf_types + hunting_behavior = /datum/ai_behavior/hunt_target/drink_water + hunt_targets = list( + /turf/simulated/floor/beach/away/water, + ) + hunt_range = 7 + hunt_chance = 5 + +/datum/ai_behavior/hunt_target/drink_water + always_reset_target = TRUE + hunt_cooldown = 20 SECONDS + +/datum/ai_behavior/hunt_target/drink_water/target_caught(mob/living/hunter, atom/hunted) + var/static/list/possible_emotes = list("drinks the water!", "dances in the water!", "splashes around happily!") + hunter.custom_emote(EMOTE_VISIBLE, pick(possible_emotes)) + +/// subtree to go around grazing +/datum/ai_planning_subtree/find_and_hunt_target/graze + target_key = BB_DEER_GRASS_TARGET + finding_behavior = /datum/ai_behavior/find_and_set/in_list/turf_types + hunting_behavior = /datum/ai_behavior/hunt_target/eat_grass + hunt_targets = list( + /turf/simulated/floor/grass, + ) + hunt_range = 7 + hunt_chance = 45 + +/datum/ai_behavior/hunt_target/eat_grass + always_reset_target = TRUE + hunt_cooldown = 15 SECONDS + +/datum/ai_behavior/hunt_target/eat_grass/target_caught(mob/living/hunter, atom/hunted) + var/static/list/possible_emotes = list("eats the grass!", "munches down the grass!", "chews on the grass!") + hunter.custom_emote(EMOTE_VISIBLE, pick(possible_emotes)) + +/// subtree to go around playing with other deers +/datum/ai_planning_subtree/play_with_friends + +/datum/ai_planning_subtree/play_with_friends/select_behaviors(datum/ai_controller/controller, seconds_per_tick) + var/static/list/emote_list = list("plays with", "dances with", "celebrates with") + var/static/list/friend_types = typecacheof(list(/mob/living/basic/deer)) + if(controller.blackboard_key_exists(BB_DEER_PLAYFRIEND)) + controller.queue_behavior(/datum/ai_behavior/emote_on_target, BB_DEER_PLAYFRIEND, emote_list) + if(SPT_PROB(3, seconds_per_tick)) + controller.queue_behavior(/datum/ai_behavior/find_hunt_target/valid_deer, BB_DEER_PLAYFRIEND, friend_types) + return SUBTREE_RETURN_FINISH_PLANNING + +/datum/ai_behavior/emote_on_target/deer_play + +/datum/ai_behavior/emote_on_target/deer_play/run_emote(mob/living/living_pawn, atom/target, list/emote_list) + . = ..() + living_pawn.spin(spintime = 4, speed = 1) + +/datum/ai_behavior/find_hunt_target/valid_deer/valid_dinner(mob/living/source, mob/living/deer, radius, datum/ai_controller/controller, seconds_per_tick) + if(deer.stat == DEAD) + return FALSE + if(!can_see(source, deer, radius)) + return FALSE + deer.ai_controller?.set_blackboard_key(BB_DEER_PLAYFRIEND, source) + return can_see(source, deer, radius) + +/// subtree to mark trees as territories +/datum/ai_planning_subtree/find_and_hunt_target/mark_territory + target_key = BB_DEER_TREE_TARGET + finding_behavior = /datum/ai_behavior/find_hunt_target + hunting_behavior = /datum/ai_behavior/hunt_target/mark_territory + hunt_targets = list(/obj/structure/flora/tree) + hunt_range = 7 + hunt_chance = 75 + +/datum/ai_behavior/hunt_target/mark_territory + always_reset_target = TRUE + hunt_cooldown = 15 SECONDS + +/datum/ai_behavior/hunt_target/mark_territory/target_caught(mob/living/hunter, atom/hunted) + hunter.custom_emote(EMOTE_VISIBLE, "marks [hunted] with its hooves!") + hunter.ai_controller.set_blackboard_key(BB_DEER_TREEHOME, hunted) + +/datum/ai_planning_subtree/find_and_hunt_target/mark_territory/select_behaviors(datum/ai_controller/controller, seconds_per_tick) + if(controller.blackboard_key_exists(BB_DEER_TREEHOME)) // already found our home, abort! + return + return ..() + +/datum/ai_planning_subtree/rest_at_home/select_behaviors(datum/ai_controller/controller, seconds_per_tick) + if(controller.blackboard[BB_DEER_RESTING] > world.time) // we're resting for now, nothing more to do + return SUBTREE_RETURN_FINISH_PLANNING + if(!controller.blackboard_key_exists(BB_DEER_TREEHOME) || controller.blackboard[BB_DEER_NEXT_REST_TIMER] > world.time) + return + controller.queue_behavior(/datum/ai_behavior/return_home, BB_DEER_TREEHOME) + +/datum/ai_behavior/return_home + required_distance = 0 + behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION + /// minimum time till next rest + var/minimum_time = 2 MINUTES + /// maximum time till next rest + var/maximum_time = 4 MINUTES + +/datum/ai_behavior/return_home/setup(datum/ai_controller/controller, target_key) + . = ..() + var/atom/target = controller.blackboard[target_key] + if(QDELETED(target)) + return FALSE + var/list/possible_turfs = get_adjacent_open_turfs(target) + shuffle_inplace(possible_turfs) + for(var/turf/possible_turf as anything in possible_turfs) + if(!is_blocked_turf(possible_turf)) + set_movement_target(controller, possible_turf) + return TRUE + return FALSE + +/datum/ai_behavior/return_home/perform(seconds_per_tick, datum/ai_controller/controller, target_key) + var/mob/living/living_pawn = controller.pawn + var/static/list/possible_emotes = list("rests its legs...", "yawns and naps...", "curls up and rests...") + living_pawn.custom_emote(EMOTE_VISIBLE, pick(possible_emotes)) + controller.set_blackboard_key(BB_DEER_RESTING, world.time + 15 SECONDS) + controller.set_blackboard_key(BB_DEER_NEXT_REST_TIMER, world.time + rand(minimum_time, maximum_time)) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED diff --git a/code/modules/mob/living/basic/pig.dm b/code/modules/mob/living/basic/farm_animals/pig.dm similarity index 100% rename from code/modules/mob/living/basic/pig.dm rename to code/modules/mob/living/basic/farm_animals/pig.dm diff --git a/code/modules/mob/living/simple_animal/friendly/deer.dm b/code/modules/mob/living/simple_animal/friendly/deer.dm deleted file mode 100644 index 4e79d504a84..00000000000 --- a/code/modules/mob/living/simple_animal/friendly/deer.dm +++ /dev/null @@ -1,23 +0,0 @@ -/mob/living/simple_animal/deer - name = "deer" - desc = "A strong, brave deer." - icon_state = "deer" - icon_living = "deer" - icon_dead = "deer_dead" - speak = list("snorts") - speak_emote = list("snorts") - emote_see = list("shakes its head") - faction = list("neutral", "jungle") - speak_chance = 1 - turns_per_move = 5 - see_in_dark = 0 //I'm so funny - butcher_results = list(/obj/item/food/meat = 4) - response_help = "pets" - response_disarm = "gently pushes aside" - response_harm = "kicks" - mob_biotypes = MOB_ORGANIC | MOB_BEAST - gold_core_spawnable = FRIENDLY_SPAWN - -/mob/living/simple_animal/deer/Initialize(mapload) - . = ..() - AddElement(/datum/element/wears_collar) diff --git a/paradise.dme b/paradise.dme index dd96fcc6338..73f4e0c9ce2 100644 --- a/paradise.dme +++ b/paradise.dme @@ -41,6 +41,7 @@ #include "code\__DEFINES\asset_defines.dm" #include "code\__DEFINES\atmospherics_defines.dm" #include "code\__DEFINES\atom_states.dm" +#include "code\__DEFINES\basic_mob_defines.dm" #include "code\__DEFINES\basic_mob_flags.dm" #include "code\__DEFINES\bio_chip_defines.dm" #include "code\__DEFINES\bots.dm" @@ -450,9 +451,11 @@ #include "code\datums\ai\ai_planning_subtree.dm" #include "code\datums\ai\basic_mobs\basic_controller.dm" #include "code\datums\ai\basic_mobs\basic_ai_behaviors\basic_attacking.dm" +#include "code\datums\ai\basic_mobs\basic_ai_behaviors\emote_with_target.dm" #include "code\datums\ai\basic_mobs\basic_ai_behaviors\interact_with_target.dm" #include "code\datums\ai\basic_mobs\basic_ai_behaviors\nearest_targeting.dm" #include "code\datums\ai\basic_mobs\basic_ai_behaviors\run_away_from_target.dm" +#include "code\datums\ai\basic_mobs\basic_ai_behaviors\stop_and_stare.dm" #include "code\datums\ai\basic_mobs\basic_ai_behaviors\targeting.dm" #include "code\datums\ai\basic_mobs\basic_ai_behaviors\tipped_reaction.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\find_food.dm" @@ -460,14 +463,17 @@ #include "code\datums\ai\basic_mobs\basic_subtrees\random_speech.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\simple_attack_target.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\simple_find_nearest_target_to_flee.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\tip_reaction.dm" +#include "code\datums\ai\basic_mobs\hunting_behavior\hunting_behaviors.dm" #include "code\datums\ai\generic\find_and_set.dm" #include "code\datums\ai\generic\generic_behaviors.dm" #include "code\datums\ai\idle_behaviors\idle_behavior.dm" #include "code\datums\ai\idle_behaviors\idle_random_walk.dm" #include "code\datums\ai\movement\ai_movement.dm" #include "code\datums\ai\movement\basic_avoidance.dm" +#include "code\datums\ai\movement\complete_stop.dm" #include "code\datums\ai\targeting_strategy\basic_targeting_strategy.dm" #include "code\datums\ai\targeting_strategy\targeting_strategy.dm" #include "code\datums\cache\air_alarm.dm" @@ -2351,8 +2357,10 @@ #include "code\modules\mob\living\stat_states.dm" #include "code\modules\mob\living\taste.dm" #include "code\modules\mob\living\basic\basic_mob.dm" -#include "code\modules\mob\living\basic\cow.dm" -#include "code\modules\mob\living\basic\pig.dm" +#include "code\modules\mob\living\basic\farm_animals\cow.dm" +#include "code\modules\mob\living\basic\farm_animals\deer.dm" +#include "code\modules\mob\living\basic\farm_animals\deer_ai.dm" +#include "code\modules\mob\living\basic\farm_animals\pig.dm" #include "code\modules\mob\living\brain\brain_death.dm" #include "code\modules\mob\living\brain\brain_emote.dm" #include "code\modules\mob\living\brain\brain_life.dm" @@ -2533,7 +2541,6 @@ #include "code\modules\mob\living\simple_animal\friendly\cockroach.dm" #include "code\modules\mob\living\simple_animal\friendly\corgi_stripping.dm" #include "code\modules\mob\living\simple_animal\friendly\crab.dm" -#include "code\modules\mob\living\simple_animal\friendly\deer.dm" #include "code\modules\mob\living\simple_animal\friendly\diona_nymph.dm" #include "code\modules\mob\living\simple_animal\friendly\dog.dm" #include "code\modules\mob\living\simple_animal\friendly\farm_animals.dm" diff --git a/tools/UpdatePaths/Scripts/29270_deer.txt b/tools/UpdatePaths/Scripts/29270_deer.txt new file mode 100644 index 00000000000..4dc50f3b5a5 --- /dev/null +++ b/tools/UpdatePaths/Scripts/29270_deer.txt @@ -0,0 +1 @@ +/mob/living/simple_animal/deer/@SUBTYPES : /mob/living/basic/deer/@SUBTYPES{@OLD}