diff --git a/code/__DEFINES/ai/ai_blackboard.dm b/code/__DEFINES/ai/ai_blackboard.dm index a4b4e5a15ea..1b94abc95c1 100644 --- a/code/__DEFINES/ai/ai_blackboard.dm +++ b/code/__DEFINES/ai/ai_blackboard.dm @@ -19,6 +19,14 @@ ///can this mob heal? #define BB_BASIC_MOB_HEALER "BB_basic_mob_healer" +//stealing +///chance we steal something +#define BB_STEAL_CHANCE "steal_chance" +///chance we develop a guilty concious and leave our stolen item behind +#define BB_GUILTY_CONSCIOUS_CHANCE "guilty_concious_rate" +///the item we will steal +#define BB_ITEM_TO_STEAL "item_to_steal" + ///the owner we will try to play with #define BB_OWNER_TARGET "BB_owner_target" ///the list of interactions we can have with the owner diff --git a/code/controllers/subsystem/minor_mapping.dm b/code/controllers/subsystem/minor_mapping.dm index 93abdcc9d10..d0e741109cf 100644 --- a/code/controllers/subsystem/minor_mapping.dm +++ b/code/controllers/subsystem/minor_mapping.dm @@ -9,8 +9,9 @@ SUBSYSTEM_DEF(minor_mapping) flags = SS_NO_FIRE ///a list of vermin we pick from to spawn. var/list/vermin_chances = list( - /mob/living/basic/mouse = 80, - /mob/living/basic/snail = 18, + /mob/living/basic/mouse = 72, + /mob/living/basic/snail = 16, + /mob/living/basic/stoat = 10, /mob/living/basic/regal_rat/controlled = 2, ) diff --git a/code/datums/ai/basic_mobs/basic_subtrees/drag_items.dm b/code/datums/ai/basic_mobs/basic_subtrees/drag_items.dm new file mode 100644 index 00000000000..f0017a67c00 --- /dev/null +++ b/code/datums/ai/basic_mobs/basic_subtrees/drag_items.dm @@ -0,0 +1,60 @@ + +///simple behavior to make mobs randomly drag things around +/datum/ai_planning_subtree/steal_items/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + var/mob/living/living_pawn = controller.pawn + if(living_pawn.pulling) + if(prob(controller.blackboard[BB_GUILTY_CONSCIOUS_CHANCE])) + controller.queue_behavior(/datum/ai_behavior/stop_dragging) + return + if(!controller.blackboard[BB_STEAL_CHANCE]) + return + if(!controller.blackboard_key_exists(BB_ITEM_TO_STEAL)) + controller.queue_behavior(/datum/ai_behavior/find_and_set/find_stealable, /obj/item, BB_ITEM_TO_STEAL) + return + controller.queue_behavior(/datum/ai_behavior/drag_target) + return SUBTREE_RETURN_FINISH_PLANNING + +/datum/ai_behavior/find_and_set/find_stealable + behavior_flags = AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION + action_cooldown = 2 MINUTES + +/datum/ai_behavior/find_and_set/find_stealable/search_tactic(datum/ai_controller/controller, locate_path, search_range) + var/mob/living/living_pawn = controller.pawn + + var/list/possible_items = shuffle_inplace(oview(search_range, controller.pawn)) + for(var/obj/item/possible_item in possible_items) + if(possible_item.pulledby || possible_item.anchored) + continue + if(can_see(living_pawn, possible_item)) + return possible_item + + +/datum/ai_behavior/stop_dragging + behavior_flags = AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION + +/datum/ai_behavior/stop_dragging/perform(seconds_per_tick, datum/ai_controller/controller, target_key) + var/mob/living/living_pawn = controller.pawn + living_pawn.stop_pulling() + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + +/datum/ai_behavior/drag_target + behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION | AI_BEHAVIOR_REQUIRE_REACH + +/datum/ai_behavior/drag_target/setup(datum/ai_controller/controller, target_key) + . = ..() + var/atom/target = controller.blackboard[target_key] + if(QDELETED(target)) + return FALSE + set_movement_target(controller, target) + +/datum/ai_behavior/drag_target/perform(seconds_per_tick, datum/ai_controller/controller, target_key) + var/atom/movable/target = controller.blackboard[target_key] + if(QDELETED(target) || target.anchored || target.pulledby) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED + var/mob/living/our_mob = controller.pawn + our_mob.start_pulling(target) + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED + +/datum/ai_behavior/drag_target/finish_action(datum/ai_controller/controller, succeeded, target_key) + . = ..() + controller.clear_blackboard_key(target_key) diff --git a/code/modules/mob/living/basic/bots/honkbots/honkbot_ai.dm b/code/modules/mob/living/basic/bots/honkbots/honkbot_ai.dm index 7801d64a92a..3ae1c1958fb 100644 --- a/code/modules/mob/living/basic/bots/honkbots/honkbot_ai.dm +++ b/code/modules/mob/living/basic/bots/honkbots/honkbot_ai.dm @@ -214,29 +214,6 @@ controller.clear_blackboard_key(slip_target) controller.clear_blackboard_key(slippery_target) -/datum/ai_behavior/drag_target - behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION | AI_BEHAVIOR_REQUIRE_REACH - -/datum/ai_behavior/drag_target/setup(datum/ai_controller/controller, target_key) - . = ..() - var/atom/target = controller.blackboard[target_key] - if(QDELETED(target)) - return FALSE - set_movement_target(controller, target) - -/datum/ai_behavior/drag_target/perform(seconds_per_tick, datum/ai_controller/controller, target_key) - var/atom/movable/target = controller.blackboard[target_key] - if(QDELETED(target) || target.anchored || target.pulledby) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED - var/mob/living/our_mob = controller.pawn - our_mob.start_pulling(target) - return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED - -/datum/ai_behavior/drag_target/finish_action(datum/ai_controller/controller, succeeded, target_key) - . = ..() - if(!succeeded) - controller.clear_blackboard_key(target_key) - /datum/ai_planning_subtree/use_mob_ability/random_honk ability_key = BB_HONK_ABILITY diff --git a/code/modules/mob/living/basic/stoats/stoat.dm b/code/modules/mob/living/basic/stoats/stoat.dm new file mode 100644 index 00000000000..43fa3e3be7e --- /dev/null +++ b/code/modules/mob/living/basic/stoats/stoat.dm @@ -0,0 +1,51 @@ +/mob/living/basic/stoat + name = "stoat" + desc = "A natural born vermin exterminator... a janitor's best friend." + icon_state = "stoat" + icon_living = "stoat" + icon_dead = "stoat_dead" + base_icon_state = "stoat" + icon = 'icons/mob/simple/pets.dmi' + butcher_results = list(/obj/item/food/meat/slab = 1) + mob_biotypes = MOB_ORGANIC + mob_size = MOB_SIZE_SMALL + health = 40 + maxHealth = 40 + melee_damage_lower = 6 + melee_damage_upper = 9 + verb_say = "chips" + verb_ask = "chips curiously" + verb_exclaim = "chips loudly" + verb_yell = "chips loudly" + faction = list(FACTION_NEUTRAL) + ai_controller = /datum/ai_controller/basic_controller/stoat + ///some commands we obey + var/static/list/pet_commands = list( + /datum/pet_command/idle, + /datum/pet_command/move, + /datum/pet_command/free, + /datum/pet_command/attack, + /datum/pet_command/follow, + /datum/pet_command/fetch, + ) + +/mob/living/basic/stoat/Initialize(mapload) + . = ..() + ADD_TRAIT(src, TRAIT_VENTCRAWLER_ALWAYS, INNATE_TRAIT) + AddElement(/datum/element/tiny_mob_hunter) + var/static/list/eatable_food = list( + /obj/item/food/deadmouse, + /obj/item/food/egg, + ) + AddComponent(/datum/component/tameable, food_types = eatable_food, tame_chance = 70, bonus_tame_chance = 0) + ai_controller.set_blackboard_key(BB_BASIC_FOODS, typecacheof(eatable_food)) + AddElement(/datum/element/wears_collar) + AddComponent(/datum/component/obeys_commands, pet_commands) + + var/static/list/display_emote = list( + BB_EMOTE_SAY = list("Chirp chirp chirp!"), + BB_EMOTE_SEE = list("sweeps its tail!", "jumps around!", "licks its fur!"), + BB_SPEAK_CHANCE = 2, + BB_EMOTE_SOUND = list('sound/mobs/non-humanoids/stoat/stoat_sounds.ogg'), + ) + ai_controller.set_blackboard_key(BB_BASIC_MOB_SPEAK_LINES, display_emote) diff --git a/code/modules/mob/living/basic/stoats/stoat_ai.dm b/code/modules/mob/living/basic/stoats/stoat_ai.dm new file mode 100644 index 00000000000..3fe40eda422 --- /dev/null +++ b/code/modules/mob/living/basic/stoats/stoat_ai.dm @@ -0,0 +1,16 @@ +/datum/ai_controller/basic_controller/stoat + blackboard = list( + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic/of_size/smaller, + BB_PET_TARGETING_STRATEGY = /datum/targeting_strategy/basic/not_friends, + BB_GUILTY_CONSCIOUS_CHANCE = 5, + BB_STEAL_CHANCE = 2, + ) + ai_movement = /datum/ai_movement/basic_avoidance + idle_behavior = /datum/idle_behavior/idle_random_walk + planning_subtrees = list( + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/random_speech/blackboard, + /datum/ai_planning_subtree/steal_items, + /datum/ai_planning_subtree/basic_melee_attack_subtree, + /datum/ai_planning_subtree/find_food, + ) diff --git a/code/modules/recycling/disposal/bin.dm b/code/modules/recycling/disposal/bin.dm index d741f8ba460..e7825d147e8 100644 --- a/code/modules/recycling/disposal/bin.dm +++ b/code/modules/recycling/disposal/bin.dm @@ -1,5 +1,8 @@ // Disposal bin and Delivery chute. - +GLOBAL_VAR_INIT(animals_spawned, 0) +#define CONTAINS_ANIMAL_CHANCE 5 +#define MAXIMUM_ANIMAL_SPAWNS 1 +#define ANIMAL_SHAKE_CHANCE 10 #define SEND_PRESSURE (0.05*ONE_ATMOSPHERE) /obj/machinery/disposal @@ -32,6 +35,12 @@ var/last_sound = 0 /// The stored disposal construction pipe var/obj/structure/disposalconstruct/stored + ///a weighted list of all the possible animals we can have + var/static/list/weighted_animal_list = list( + /mob/living/basic/stoat = 1, + ) + /// do we contain an animal? + var/contained_animal /datum/armor/machinery_disposal melee = 25 @@ -67,6 +76,8 @@ ) AddElement(/datum/element/connect_loc, loc_connections) ADD_TRAIT(src, TRAIT_COMBAT_MODE_SKIP_INTERACTION, INNATE_TRAIT) + if(mapload) + spawn_contained_animal() return INITIALIZE_HINT_LATELOAD //we need turfs to have air /// Checks if there a connecting trunk diposal pipe under the disposal @@ -505,10 +516,17 @@ else return ..() +/obj/machinery/disposal/bin/Entered(atom/movable/arrived, atom/old_loc, list/atom/old_locs) + . = ..() + if(contained_animal) + release_animal() + /obj/machinery/disposal/bin/flush() ..() full_pressure = FALSE pressure_charging = TRUE + if(contained_animal) + release_animal() update_appearance() /obj/machinery/disposal/bin/update_overlays() @@ -548,6 +566,10 @@ //timed process //charge the gas reservoir and perform flush if ready /obj/machinery/disposal/bin/process(seconds_per_tick) + + if(contained_animal) + Shake(duration = 2 SECONDS) + if(machine_stat & BROKEN) //nothing can happen if broken return @@ -698,4 +720,20 @@ to_chat(user, span_notice("You sweep the pile of garbage into [src].")) playsound(broom.loc, 'sound/items/weapons/thudswoosh.ogg', 30, TRUE, -1) +/obj/machinery/disposal/proc/spawn_contained_animal() + if(!prob(CONTAINS_ANIMAL_CHANCE) || GLOB.animals_spawned >= MAXIMUM_ANIMAL_SPAWNS) + return + contained_animal = pick_weight(weighted_animal_list) + GLOB.animals_spawned++ + +/obj/machinery/disposal/bin/proc/release_animal() + var/list/open_turfs = get_adjacent_open_turfs(src) + var/turf/final_turf = length(open_turfs) ? pick(open_turfs) : drop_location() + var/mob/living/startled_animal = new contained_animal(final_turf) + visible_message(span_notice("A startled [startled_animal] jumps out of [src]")) + + #undef SEND_PRESSURE +#undef CONTAINS_ANIMAL_CHANCE +#undef ANIMAL_SHAKE_CHANCE +#undef MAXIMUM_ANIMAL_SPAWNS diff --git a/icons/mob/simple/pets.dmi b/icons/mob/simple/pets.dmi index ba1a98e3f95..64a9f3038f7 100644 Binary files a/icons/mob/simple/pets.dmi and b/icons/mob/simple/pets.dmi differ diff --git a/sound/attributions.txt b/sound/attributions.txt index 2a08140df95..767b5ec624a 100644 --- a/sound/attributions.txt +++ b/sound/attributions.txt @@ -232,3 +232,5 @@ sound/effect/plasticflaps.ogg -- door_plastic_tapes.wav by estupe -- https://fre sound/effects/siren.ogg -- siren.wav by IFartInUrGeneralDirection -- https://freesound.org/s/46092/ -- License: Attribution 4.0 + +sound/mobs/non-humanoids/stoats/stoat_sound.ogg -- https://suche.tierstimmenarchiv.de/ diff --git a/sound/mobs/non-humanoids/stoat/stoat_sounds.ogg b/sound/mobs/non-humanoids/stoat/stoat_sounds.ogg new file mode 100644 index 00000000000..09d0c733aed Binary files /dev/null and b/sound/mobs/non-humanoids/stoat/stoat_sounds.ogg differ diff --git a/tgstation.dme b/tgstation.dme index cdd38c254e3..34aa3cf08b1 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -952,6 +952,7 @@ #include "code\datums\ai\basic_mobs\basic_subtrees\call_reinforcements.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\capricious_retaliate.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\climb_tree.dm" +#include "code\datums\ai\basic_mobs\basic_subtrees\drag_items.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\express_happiness.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\find_food.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\find_paper_and_write.dm" @@ -5283,6 +5284,8 @@ #include "code\modules\mob\living\basic\space_fauna\wumborian_fugu\inflation.dm" #include "code\modules\mob\living\basic\space_fauna\wumborian_fugu\wumborian_ai.dm" #include "code\modules\mob\living\basic\space_fauna\wumborian_fugu\wumborian_fugu.dm" +#include "code\modules\mob\living\basic\stoats\stoat.dm" +#include "code\modules\mob\living\basic\stoats\stoat_ai.dm" #include "code\modules\mob\living\basic\trader\trader.dm" #include "code\modules\mob\living\basic\trader\trader_actions.dm" #include "code\modules\mob\living\basic\trader\trader_ai.dm"