diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm index 0eb855dfc50..d060c569b18 100644 --- a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm +++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm @@ -96,3 +96,6 @@ #define COMSIG_LIVING_MOB_PAINTED "living_mob_painted" ///From mob/living/proc/wabbajack_act #define COMSIG_LIVING_WABBAJACKED "living_wabbajacked" + +/// From /datum/component/mob_harvest: (amount_ready) +#define COMSIG_LIVING_HARVEST_UPDATE "living_harvest_update" 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 2bca31b4551..2298a1b061b 100644 --- a/code/datums/ai/basic_mobs/basic_subtrees/speech_subtree.dm +++ b/code/datums/ai/basic_mobs/basic_subtrees/speech_subtree.dm @@ -42,6 +42,12 @@ speech_chance = 15 emote_hear = list("flutters.") +/datum/ai_planning_subtree/random_speech/sheep + speech_chance = 5 + speak = list("baaa","baaaAAAAAH!","baaah") + emote_hear = list("bleats.") + emote_see = list("shakes her head.", "stares into the distance.") + /datum/ai_planning_subtree/random_speech/cow speech_chance = 1 speak = list("moo?","moo","MOOOOOO") diff --git a/code/datums/components/mob_harvest.dm b/code/datums/components/mob_harvest.dm new file mode 100644 index 00000000000..ac3f64a3aad --- /dev/null +++ b/code/datums/components/mob_harvest.dm @@ -0,0 +1,130 @@ +/** + * Harvesting component. Useful if you want to be able to harvest items from living mobs. + * + * Used currently on sheep. + */ +/datum/component/mob_harvest + ///item used to harvest + var/obj/item/harvest_tool = /obj/item/razor + ///item used to reduce wait between items + var/obj/item/fed_item = /obj/item/food/grown/grass + ///typepath of the item you want to harvest + var/produced_item_typepath = /obj/item/food/bait + ///stand-in name for the item + var/produced_item_desc = "funny worm" + ///how much is ready to harvest + var/amount_ready = 1 + ///max amount that can be stored between harvests + var/max_ready = 1 + ///time between item creation + var/item_generation_wait = 10 SECONDS + ///tracked time + var/item_generation_time = 0 + ///time to reduce when fed + var/item_reduction_time = 2 SECONDS + ///how long it takes to harvest from the mob + var/item_harvest_time = 5 SECONDS + ///typepath of harvest sound + var/item_harvest_sound = 'sound/items/welder2.ogg' + +//harvest_type, produced_item_typepath and speedup_type are typepaths, not reference +/datum/component/mob_harvest/Initialize(harvest_tool, fed_item, produced_item_typepath, produced_item_desc, max_ready, item_generation_wait, item_reduction_time, item_harvest_time, item_harvest_sound) + if(!isliving(parent)) + return COMPONENT_INCOMPATIBLE + src.harvest_tool = harvest_tool + src.fed_item = fed_item + src.produced_item_typepath = produced_item_typepath + src.produced_item_desc = produced_item_desc + src.max_ready = max_ready + src.item_generation_wait = item_generation_wait + src.item_reduction_time = item_reduction_time + src.item_harvest_time = item_harvest_time + item_generation_time = item_generation_wait + START_PROCESSING(SSobj, src) + +/datum/component/mob_harvest/vv_edit_var(var_name, var_value) + var/amount_changed + if(var_name == NAMEOF(src, max_ready)) + var_value = max(0, var_value) //no negatives allowed + if(amount_ready != min(amount_ready, var_value)) //check to max sure max_ready isn't lower than the amount ready. + amount_ready = var_value + amount_changed = TRUE + if(var_name == NAMEOF(src, amount_ready) && var_value != amount_ready) + amount_changed = TRUE + . = ..() + if(amount_changed) + SEND_SIGNAL(parent, COMSIG_LIVING_HARVEST_UPDATE, amount_ready) + +/datum/component/mob_harvest/process(delta_time) + ///only track time if we aren't dead and have room for more items + var/mob/living/harvest_mob = parent + if(harvest_mob.stat != DEAD && amount_ready < max_ready) + item_generation_time -= delta_time + if(item_generation_time <= 0) + item_generation_time = item_generation_wait + amount_ready++ + SEND_SIGNAL(parent, COMSIG_LIVING_HARVEST_UPDATE, amount_ready) + +/datum/component/mob_harvest/RegisterWithParent() + RegisterSignal(parent, COMSIG_PARENT_EXAMINE, .proc/on_examine) + RegisterSignal(parent, COMSIG_PARENT_ATTACKBY, .proc/on_attackby) + +/datum/component/mob_harvest/UnregisterFromParent() + STOP_PROCESSING(SSobj, src) + UnregisterSignal(parent, list(COMSIG_PARENT_EXAMINE, COMSIG_PARENT_ATTACKBY)) + +///signal called on parent being examined +/datum/component/mob_harvest/proc/on_examine(datum/source, mob/user, list/examine_list) + SIGNAL_HANDLER + + if(amount_ready < 1) + examine_list += span_notice("[parent] seems like they could use a bit more time.") + if(amount_ready > 1) + examine_list += span_notice("[parent] looks like they can be harvested about [amount_ready] times.") + if(amount_ready == 1) + examine_list += span_notice("[parent] looks ready to be harvested.") + +///signal called on parent being attacked with an item +/datum/component/mob_harvest/proc/on_attackby(datum/source, obj/item/used_item, mob/user) + SIGNAL_HANDLER + + if(istype(used_item, harvest_tool)) + INVOKE_ASYNC(src, .proc/harvest_item, user) + if(istype(used_item, fed_item)) + remove_wait_time(user) + qdel(used_item) + return COMPONENT_NO_AFTERATTACK + +/** + * Proc called from attacking the component parent with the correct item, reduces wait time between items + * + * Arguments: + * * mob/user - who is trying to do this + */ +/datum/component/mob_harvest/proc/remove_wait_time(mob/user) + if(amount_ready >= max_ready) + to_chat(user, span_warning("[parent] looks too full to keep feeding!")) + return + item_generation_time -= item_reduction_time + to_chat(user, span_notice("You feed [parent].")) + return + +/** + * Proc called from attacking the component parent with the correct item, handles creating the item + * + * Arguments: + * * mob/user - who is trying to do this + */ +/datum/component/mob_harvest/proc/harvest_item(mob/user) + if(amount_ready < 1) + to_chat(user, span_warning("[parent] doesn't seem to have enough [produced_item_desc] to harvest.")) + return + to_chat(user, span_notice("You start to harvest [produced_item_desc] from [parent]...")) + if(do_after(user, item_harvest_time, target = parent)) + playsound(parent, item_harvest_sound, 20, TRUE) + to_chat(user, span_notice("You harvest some [produced_item_desc] from [parent].")) + amount_ready-- + SEND_SIGNAL(parent, COMSIG_LIVING_HARVEST_UPDATE, amount_ready) + new produced_item_typepath(get_turf(parent)) + return + diff --git a/code/game/objects/items/stacks/sheets/sheet_types.dm b/code/game/objects/items/stacks/sheets/sheet_types.dm index 961ca917ad3..7a4a8ffdd73 100644 --- a/code/game/objects/items/stacks/sheets/sheet_types.dm +++ b/code/game/objects/items/stacks/sheets/sheet_types.dm @@ -528,6 +528,15 @@ GLOBAL_LIST_INIT(durathread_recipes, list ( \ loom_result = /obj/item/stack/sheet/durathread grind_results = list() +/obj/item/stack/sheet/cotton/wool + name = "raw wool bundle" + desc = "A bundle of raw wool ready to be spun on the loom." + singular_name = "raw wool ball" + icon_state = "sheet-wool" + merge_type = /obj/item/stack/sheet/cotton/wool + loom_result = /obj/item/stack/sheet/cloth + grind_results = list() + /* * Cardboard */ diff --git a/code/modules/antagonists/cult/runes.dm b/code/modules/antagonists/cult/runes.dm index 7e4a72c1183..167f02d0024 100644 --- a/code/modules/antagonists/cult/runes.dm +++ b/code/modules/antagonists/cult/runes.dm @@ -303,6 +303,17 @@ structure_check() searches for nearby cultist structures required for the invoca log_game("Offer rune with [sacrificial] on it failed - tried sacrificing pAI.") return FALSE + if(istype(sacrificial, /mob/living/basic/sheep)) + var/mob/living/basic/sheep/sacrificial_lamb = sacrificial + if(sacrificial_lamb.cult_converted) + for(var/cultists in invokers) + to_chat(cultists, span_cultitalic("[sacrificial] has already been sacrificed!")) + return FALSE + for(var/cultists in invokers) + to_chat(cultists, span_cultitalic("This feels a bit too cliché, don't you think?")) + sacrificial_lamb.cult_time() + return + var/big_sac = FALSE if((((ishuman(sacrificial) || iscyborg(sacrificial)) && sacrificial.stat != DEAD) || C.cult_team.is_sacrifice_target(sacrificial.mind)) && length(invokers) < 3) for(var/M in invokers) diff --git a/code/modules/cargo/packs.dm b/code/modules/cargo/packs.dm index 86424ac4620..ecea5fe4e40 100644 --- a/code/modules/cargo/packs.dm +++ b/code/modules/cargo/packs.dm @@ -2185,6 +2185,14 @@ contains = list(/mob/living/basic/cow) crate_name = "cow crate" +/datum/supply_pack/critter/sheep + name = "Sheep Crate" + desc = "The sheep goes BAAAA!" + cost = CARGO_CRATE_VALUE * 6 + access_view = ACCESS_HYDROPONICS + contains = list(/mob/living/basic/sheep) + crate_name = "sheep crate" + /datum/supply_pack/critter/pig name = "Pig Crate" desc = "The pig goes oink!" diff --git a/code/modules/mob/living/basic/farm_animals/sheep.dm b/code/modules/mob/living/basic/farm_animals/sheep.dm new file mode 100644 index 00000000000..e97f4874c48 --- /dev/null +++ b/code/modules/mob/living/basic/farm_animals/sheep.dm @@ -0,0 +1,77 @@ +/mob/living/basic/sheep + name = "sheep" + desc = "Known for their soft wool and use in sacrifical rituals. Big fan of grass." + icon = 'icons/mob/sheep.dmi' + icon_state = "sheep" + icon_dead = "sheep_dead" + gender = FEMALE + mob_biotypes = MOB_ORGANIC | MOB_BEAST + speak_emote = list("baas","bleats") + speed = 1.1 + see_in_dark = 6 + butcher_results = list(/obj/item/food/meat/slab = 3) + response_help_continuous = "pets" + response_help_simple = "pet" + response_disarm_continuous = "gently pushes aside" + response_disarm_simple = "gently push aside" + response_harm_continuous = "kicks" + response_harm_simple = "kick" + attack_verb_continuous = "kicks" + attack_verb_simple = "kick" + attack_sound = 'sound/weapons/punch1.ogg' + attack_vis_effect = ATTACK_EFFECT_KICK + health = 50 + maxHealth = 50 + gold_core_spawnable = FRIENDLY_SPAWN + blood_volume = BLOOD_VOLUME_NORMAL + ai_controller = /datum/ai_controller/basic_controller/sheep + var/cult_converted //were we sacrificed by cultists? + +/mob/living/basic/sheep/Initialize(mapload) + . = ..() + AddComponent(/datum/component/mob_harvest, /obj/item/razor, /obj/item/food/grown/grass, /obj/item/stack/sheet/cotton/wool, "soft wool", 10, 3 MINUTES, 30 SECONDS, 5 SECONDS) + RegisterSignal(src, COMSIG_LIVING_HARVEST_UPDATE, .proc/update_harvest_icon) + update_appearance(UPDATE_ICON) + +/mob/living/basic/sheep/proc/update_harvest_icon() + SIGNAL_HANDLER + update_appearance(UPDATE_ICON) + +/mob/living/basic/sheep/update_icon_state() + . = ..() + var/datum/component/mob_harvest/harvest_comp = GetComponent(/datum/component/mob_harvest) + icon_state = "[initial(icon_state)][harvest_comp.amount_ready < 1 ? "_harvested" : null]" + +/mob/living/basic/sheep/update_overlays() + . = ..() + if(stat == DEAD) + return + if(cult_converted) + . += "hat" + +/mob/living/basic/sheep/proc/cult_time() + if(cult_converted) + return + cult_converted = TRUE + say("BAAAAAAAAH!") + update_appearance(UPDATE_ICON) + +/mob/living/basic/sheep/vv_edit_var(vname, vval) + if(vname == NAMEOF(src, cult_converted)) + if(vval == cult_converted) + return FALSE + if(vval) + cult_time() + ..() + if(!cult_converted) + update_appearance(UPDATE_ICON) + return TRUE + return ..() + +/datum/ai_controller/basic_controller/sheep + 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/sheep + ) diff --git a/icons/mob/sheep.dmi b/icons/mob/sheep.dmi new file mode 100644 index 00000000000..690dd4a1368 Binary files /dev/null and b/icons/mob/sheep.dmi differ diff --git a/icons/obj/stack_objects.dmi b/icons/obj/stack_objects.dmi index 0639e3ebc05..30a4edf1abd 100644 Binary files a/icons/obj/stack_objects.dmi and b/icons/obj/stack_objects.dmi differ diff --git a/tgstation.dme b/tgstation.dme index 674813c4931..57eb0baf37e 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -885,6 +885,7 @@ #include "code\datums\components\mind_linker.dm" #include "code\datums\components\mirage_border.dm" #include "code\datums\components\mirv.dm" +#include "code\datums\components\mob_harvest.dm" #include "code\datums\components\multiple_lives.dm" #include "code\datums\components\ntnet_interface.dm" #include "code\datums\components\omen.dm" @@ -3468,6 +3469,7 @@ #include "code\modules\mob\living\basic\health_adjustment.dm" #include "code\modules\mob\living\basic\farm_animals\cows.dm" #include "code\modules\mob\living\basic\farm_animals\pig.dm" +#include "code\modules\mob\living\basic\farm_animals\sheep.dm" #include "code\modules\mob\living\basic\lavaland\bileworm\_bileworm.dm" #include "code\modules\mob\living\basic\lavaland\bileworm\bileworm_actions.dm" #include "code\modules\mob\living\basic\lavaland\bileworm\bileworm_ai.dm"