diff --git a/code/__DEFINES/dcs/signals.dm b/code/__DEFINES/dcs/signals.dm index 3535d4f5f03..200eea530c8 100644 --- a/code/__DEFINES/dcs/signals.dm +++ b/code/__DEFINES/dcs/signals.dm @@ -1223,7 +1223,7 @@ #define COMPONENT_SKIP_ATTACK (1<<1) ///from base of atom/attack_ghost(): (mob/dead/observer/ghost) #define COMSIG_ATOM_ATTACK_GHOST "atom_attack_ghost" -///from base of atom/attack_hand(): (mob/user) +///from base of atom/attack_hand(): (mob/user, list/modifiers) #define COMSIG_ATOM_ATTACK_HAND "atom_attack_hand" ///from base of atom/attack_paw(): (mob/user) #define COMSIG_ATOM_ATTACK_PAW "atom_attack_paw" diff --git a/code/__DEFINES/robots.dm b/code/__DEFINES/robots.dm index 9233f458c32..fa8b919ddbc 100644 --- a/code/__DEFINES/robots.dm +++ b/code/__DEFINES/robots.dm @@ -28,7 +28,7 @@ #define BOT_WAIT_FOR_NAV 16 // waiting for nav computation #define BOT_NO_ROUTE 17 // no destination beacon found (or no route) #define BOT_SHOWERSTANCE 18 // cleaning unhygienic humans -#define BOT_TIPPED 19 // someone tipped a medibot over ;_; +#define BOT_TIPPED 19 // someone tipped a bot over ;_; //Bot types #define SEC_BOT (1<<0) // Secutritrons (Beepsky) and ED-209s diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm index 3302b03b2cb..86f5c3e6216 100644 --- a/code/__DEFINES/traits.dm +++ b/code/__DEFINES/traits.dm @@ -603,8 +603,8 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai #define SOULSTONE_TRAIT "soulstone" /// Trait applied to slimes by low temperature #define SLIME_COLD "slime-cold" -/// Trait applied to bots by being tipped over -#define BOT_TIPPED_OVER "bot-tipped-over" +/// Trait applied to mobs by being tipped over +#define TIPPED_OVER "tipped-over" /// Trait applied to PAIs by being folded #define PAI_FOLDED "pai-folded" /// Trait applied to brain mobs when they lack external aid for locomotion, such as being inside a mech. diff --git a/code/datums/components/tippable.dm b/code/datums/components/tippable.dm new file mode 100644 index 00000000000..191523011c6 --- /dev/null +++ b/code/datums/components/tippable.dm @@ -0,0 +1,207 @@ +/* + * Tippable component. For making mobs able to be tipped, like cows and medibots. + */ +/datum/component/tippable + /// Time it takes to tip the mob. Can be 0, for instant tipping. + var/tip_time = 3 SECONDS + /// Time it takes to untip the mob. Can also be 0, for instant untip. + var/untip_time = 1 SECONDS + /// Time it takes for the mob to right itself. Can be 0 for instant self-righting, or null, to never self-right. + var/self_right_time = 60 SECONDS + /// Whether the mob is currently tipped. + var/is_tipped = FALSE + /// Callback to additional behavior before being tipped (on try_tip). Return anything from this callback to cancel the tip. + var/datum/callback/pre_tipped_callback + /// Callback to additional behavior after successfully tipping the mob. + var/datum/callback/post_tipped_callback + /// Callback to additional behavior after being untipped. + var/datum/callback/post_untipped_callback + +/datum/component/tippable/Initialize( + tip_time = 3 SECONDS, + untip_time = 1 SECONDS, + self_right_time = 60 SECONDS, + datum/callback/pre_tipped_callback, + datum/callback/post_tipped_callback, + datum/callback/post_untipped_callback) + + if(!isliving(parent)) + return COMPONENT_INCOMPATIBLE + + src.tip_time = tip_time + src.untip_time = untip_time + src.self_right_time = self_right_time + src.pre_tipped_callback = pre_tipped_callback + src.post_tipped_callback = post_tipped_callback + src.post_untipped_callback = post_untipped_callback + +/datum/component/tippable/RegisterWithParent() + RegisterSignal(parent, COMSIG_ATOM_ATTACK_HAND, .proc/interact_with_tippable) + +/datum/component/tippable/UnregisterFromParent() + UnregisterSignal(parent, COMSIG_ATOM_ATTACK_HAND) + +/datum/component/tippable/Destroy() + if(pre_tipped_callback) + QDEL_NULL(pre_tipped_callback) + if(post_tipped_callback) + QDEL_NULL(post_tipped_callback) + if(post_untipped_callback) + QDEL_NULL(post_untipped_callback) + return ..() + +/* + * Attempt to interact with [source], either tipping it or helping it up. + * + * source - the mob being tipped over + * user - the mob interacting with source + * modifiers - list of on click modifiers (we only tip mobs over using right click!) + */ +/datum/component/tippable/proc/interact_with_tippable(mob/living/source, mob/user, modifiers) + SIGNAL_HANDLER + + var/mob/living/living_user = user + if(DOING_INTERACTION_WITH_TARGET(user, source)) + return + if(istype(living_user) && !living_user.combat_mode) + return + + if(is_tipped) + INVOKE_ASYNC(src, .proc/try_untip, source, user) + else if(LAZYACCESS(modifiers, RIGHT_CLICK)) + INVOKE_ASYNC(src, .proc/try_tip, source, user) + + return COMPONENT_CANCEL_ATTACK_CHAIN + +/* + * Try to tip over [tipped_mob]. + * If the mob is dead, or optional callback returns a value, or our do-after fails, we don't tip the mob. + * Otherwise, upon completing of the do_after, tip over the mob. + * + * tipped_mob - the mob being tipped over + * tipper - the mob tipping the tipped_mob + */ +/datum/component/tippable/proc/try_tip(mob/living/tipped_mob, mob/tipper) + if(tipped_mob.stat != CONSCIOUS) + return + + if(pre_tipped_callback?.Invoke(tipper)) + return + + if(tip_time > 0) + to_chat(tipper, span_warning("You begin tipping over [tipped_mob]...")) + tipped_mob.visible_message( + span_warning("[tipper] begins tipping over [tipped_mob]."), + span_userdanger("[tipper] begins tipping you over!"), + ignored_mobs = tipper + ) + + if(!do_after(tipper, tip_time, target = tipped_mob)) + to_chat(tipper, span_danger("You fail to tip over [tipped_mob].")) + return + do_tip(tipped_mob, tipper) + +/* + * Actually tip over the mob, setting it to tipped. + * Also invoking any callbacks we have, with the tipper as the argument, + * and set a timer to right our self-right our tipped mob if we can. + * + * tipped_mob - the mob who was tipped + * tipper - the mob who tipped the tipped_mob + */ +/datum/component/tippable/proc/do_tip(mob/living/tipped_mob, mob/tipper) + if(QDELETED(tipped_mob)) + CRASH("Tippable component: do_tip() called with QDELETED tipped_mob!") + + to_chat(tipper, span_warning("You tip over [tipped_mob].")) + tipped_mob.visible_message( + span_warning("[tipper] tips over [tipped_mob]."), + span_userdanger("You are tipped over by [tipper]!"), + ignored_mobs = tipper + ) + + set_tipped_status(tipped_mob, TRUE) + post_tipped_callback?.Invoke(tipper) + if(isnull(self_right_time)) + return + else if(self_right_time <= 0) + right_self(tipped_mob) + else + addtimer(CALLBACK(src, .proc/right_self, tipped_mob), self_right_time) + +/* + * Try to untip a mob that has been tipped. + * After a do-after is completed, we untip the mob. + * + * tipped_mob - the mob who is tipped + * untipper - the mob who is untipping the tipped_mob + */ +/datum/component/tippable/proc/try_untip(mob/living/tipped_mob, mob/untipper) + if(untip_time > 0) + to_chat(untipper, span_notice("You begin righting [tipped_mob]...")) + tipped_mob.visible_message( + span_notice("[untipper] begins righting [tipped_mob]."), + span_notice("[untipper] begins righting you."), + ignored_mobs = untipper + ) + + if(!do_after(untipper, untip_time, target = tipped_mob)) + to_chat(untipper, span_warning("You fail to right [tipped_mob].")) + return + + do_untip(tipped_mob, untipper) + +/* + * Actually untip over the mob, setting it to untipped. + * Also invoke any untip callbacks we have, with the untipper as the argument. + * + * tipped_mob - the mob who was tipped + * tipper - the mob who tipped the tipped_mob + */ +/datum/component/tippable/proc/do_untip(mob/living/tipped_mob, mob/untipper) + if(QDELETED(tipped_mob)) + return + + to_chat(untipper, span_notice("You right [tipped_mob].")) + tipped_mob.visible_message( + span_notice("[untipper] rights [tipped_mob]."), + span_notice("You are righted by [untipper]!"), + ignored_mobs = untipper + ) + + set_tipped_status(tipped_mob, FALSE) + post_untipped_callback?.Invoke(untipper) + +/* + * Proc called after a timer to have a tipped mob un-tip itself after a certain length of time. + * Sets our mob to untipped and invokes the untipped callback without any arguments if we have one. + * + * tipped_mob - the mob who was tipped, and is freeing itself + */ +/datum/component/tippable/proc/right_self(mob/living/tipped_mob) + if(!is_tipped || QDELETED(tipped_mob)) + return + + set_tipped_status(tipped_mob, FALSE) + post_untipped_callback?.Invoke() + + tipped_mob.visible_message( + span_notice("[tipped_mob] rights itself."), + span_notice("You right yourself.") + ) + +/* + * Toggles our tipped status between tipped or untipped (TRUE or FALSE) + * also handles rotating our mob and adding immobilization traits + * + * tipped_mob - the mob we're setting to tipped or untipped + * new_status - the tipped status we're setting the mob to - TRUE for tipped, FALSE for untipped + */ +/datum/component/tippable/proc/set_tipped_status(mob/living/tipped_mob, new_status = FALSE) + is_tipped = new_status + if(is_tipped) + tipped_mob.transform = turn(tipped_mob.transform, 180) + ADD_TRAIT(tipped_mob, TRAIT_IMMOBILIZED, TIPPED_OVER) + else + tipped_mob.transform = turn(tipped_mob.transform, -180) + REMOVE_TRAIT(tipped_mob, TRAIT_IMMOBILIZED, TIPPED_OVER) diff --git a/code/modules/mob/living/carbon/carbon_defense.dm b/code/modules/mob/living/carbon/carbon_defense.dm index e9c1d809193..02a093d75f6 100644 --- a/code/modules/mob/living/carbon/carbon_defense.dm +++ b/code/modules/mob/living/carbon/carbon_defense.dm @@ -146,7 +146,7 @@ //ATTACK HAND IGNORING PARENT RETURN VALUE /mob/living/carbon/attack_hand(mob/living/carbon/human/user, list/modifiers) - if(SEND_SIGNAL(src, COMSIG_ATOM_ATTACK_HAND, user) & COMPONENT_CANCEL_ATTACK_CHAIN) + if(SEND_SIGNAL(src, COMSIG_ATOM_ATTACK_HAND, user, modifiers) & COMPONENT_CANCEL_ATTACK_CHAIN) . = TRUE for(var/thing in diseases) var/datum/disease/D = thing diff --git a/code/modules/mob/living/silicon/silicon_defense.dm b/code/modules/mob/living/silicon/silicon_defense.dm index d0721b1394f..ce9a2a1c426 100644 --- a/code/modules/mob/living/silicon/silicon_defense.dm +++ b/code/modules/mob/living/silicon/silicon_defense.dm @@ -62,7 +62,7 @@ //ATTACK HAND IGNORING PARENT RETURN VALUE /mob/living/silicon/attack_hand(mob/living/carbon/human/user, list/modifiers) . = FALSE - if(SEND_SIGNAL(src, COMSIG_ATOM_ATTACK_HAND, user) & COMPONENT_CANCEL_ATTACK_CHAIN) + if(SEND_SIGNAL(src, COMSIG_ATOM_ATTACK_HAND, user, modifiers) & COMPONENT_CANCEL_ATTACK_CHAIN) . = TRUE if(has_buckled_mobs() && !user.combat_mode) user_unbuckle_mob(buckled_mobs[1], user) diff --git a/code/modules/mob/living/simple_animal/bot/medbot.dm b/code/modules/mob/living/simple_animal/bot/medbot.dm index 8fcbd24779b..fa2b68f4b9d 100644 --- a/code/modules/mob/living/simple_animal/bot/medbot.dm +++ b/code/modules/mob/living/simple_animal/bot/medbot.dm @@ -33,43 +33,43 @@ window_name = "Automatic Medical Unit v1.1" data_hud_type = DATA_HUD_MEDICAL_ADVANCED path_image_color = "#DDDDFF" -/// drop determining variable + /// drop determining variable var/healthanalyzer = /obj/item/healthanalyzer -/// drop determining variable + /// drop determining variable var/firstaid = /obj/item/storage/firstaid -///based off medkit_X skins in aibots.dmi for your selection; X goes here IE medskin_tox means skin var should be "tox" + ///based off medkit_X skins in aibots.dmi for your selection; X goes here IE medskin_tox means skin var should be "tox" var/skin var/mob/living/carbon/patient var/mob/living/carbon/oldpatient var/oldloc var/last_found = 0 -/// Don't spam the "HEY I'M COMING" messages + /// Don't spam the "HEY I'M COMING" messages var/last_newpatient_speak = 0 -/// How much healing do we do at a time? + /// How much healing do we do at a time? var/heal_amount = 2.5 -/// Start healing when they have this much damage in a category + /// Start healing when they have this much damage in a category var/heal_threshold = 10 -/// What damage type does this bot support. Because the default is brute, if the medkit is brute-oriented there is a slight bonus to healing. set to "all" for it to heal any of the 4 base damage types + /// What damage type does this bot support. Because the default is brute, if the medkit is brute-oriented there is a slight bonus to healing. set to "all" for it to heal any of the 4 base damage types var/damagetype_healer = BRUTE -/// If active, the bot will transmit a critical patient alert to MedHUD users. + /// If active, the bot will transmit a critical patient alert to MedHUD users. var/declare_crit = TRUE -/// Prevents spam of critical patient alerts. + /// Prevents spam of critical patient alerts. var/declare_cooldown = FALSE -/// If enabled, the Medibot will not move automatically. + /// If enabled, the Medibot will not move automatically. var/stationary_mode = FALSE -/// silences the medbot if TRUE + /// silences the medbot if TRUE var/shut_up = FALSE -/// techweb linked to the medbot + /// techweb linked to the medbot var/datum/techweb/linked_techweb -///Is the medbot currently tending wounds + ///Is the medbot currently tending wounds var/tending = FALSE -///How panicked we are about being tipped over (why would you do this?) + ///How panicked we are about being tipped over (why would you do this?) var/tipped_status = MEDBOT_PANIC_NONE -///The name we got when we were tipped + ///The name we got when we were tipped var/tipper_name -///The last time we were tipped/righted and said a voice line, to avoid spam - var/last_tipping_action_voice = 0 + ///Cooldown to track last time we were tipped/righted and said a voice line, to avoid spam + COOLDOWN_DECLARE(last_tipping_action_voice) /mob/living/simple_animal/bot/medbot/mysterious name = "\improper Mysterious Medibot" @@ -119,6 +119,13 @@ if(damagetype_healer == "all") return + AddComponent(/datum/component/tippable, \ + tip_time = 3 SECONDS, \ + untip_time = 3 SECONDS, \ + self_right_time = 3.5 MINUTES, \ + pre_tipped_callback = CALLBACK(src, .proc/pre_tip_over), \ + post_tipped_callback = CALLBACK(src, .proc/after_tip_over), \ + post_untipped_callback = CALLBACK(src, .proc/after_righted)) /mob/living/simple_animal/bot/medbot/bot_reset() ..() @@ -244,37 +251,60 @@ else return -/mob/living/simple_animal/bot/medbot/proc/tip_over(mob/user) - ADD_TRAIT(src, TRAIT_IMMOBILIZED, BOT_TIPPED_OVER) - playsound(src, 'sound/machines/warning-buzzer.ogg', 50) - user.visible_message(span_danger("[user] tips over [src]!"), span_danger("You tip [src] over!")) +/* + * Proc used in a callback for before this medibot is tipped by the tippable component. + * + * user - the mob who is tipping us over + */ +/mob/living/simple_animal/bot/medbot/proc/pre_tip_over(mob/user) + if(!COOLDOWN_FINISHED(src, last_tipping_action_voice)) + return + + COOLDOWN_START(src, last_tipping_action_voice, 15 SECONDS) // message for tipping happens when we start interacting, message for righting comes after finishing + var/static/list/messagevoice = list( + "Hey, wait..." = 'sound/voice/medbot/hey_wait.ogg', + "Please don't..." = 'sound/voice/medbot/please_dont.ogg', + "I trusted you..." = 'sound/voice/medbot/i_trusted_you.ogg', + "Nooo..." = 'sound/voice/medbot/nooo.ogg', + "Oh fuck-" = 'sound/voice/medbot/oh_fuck.ogg', + ) + var/message = pick(messagevoice) + speak(message) + playsound(src, messagevoice[message], 70, FALSE) + +/* + * Proc used in a callback for after this medibot is tipped by the tippable component. + * + * user - the mob who tipped us over + */ +/mob/living/simple_animal/bot/medbot/proc/after_tip_over(mob/user) mode = BOT_TIPPED - var/matrix/mat = transform - transform = mat.Turn(180) tipper_name = user.name + playsound(src, 'sound/machines/warning-buzzer.ogg', 50) -/mob/living/simple_animal/bot/medbot/proc/set_right(mob/user) - REMOVE_TRAIT(src, TRAIT_IMMOBILIZED, BOT_TIPPED_OVER) +/* + * Proc used in a callback for after this medibot is righted, either by themselves or by a mob, by the tippable component. + * + * user - the mob who righted us. Can be null. + */ +/mob/living/simple_animal/bot/medbot/proc/after_righted(mob/user) var/list/messagevoice - if(user) - user.visible_message(span_notice("[user] sets [src] right-side up!"), span_green("You set [src] right-side up!")) if(user.name == tipper_name) messagevoice = list("I forgive you." = 'sound/voice/medbot/forgive.ogg') else messagevoice = list("Thank you!" = 'sound/voice/medbot/thank_you.ogg', "You are a good person." = 'sound/voice/medbot/youre_good.ogg') else - visible_message(span_notice("[src] manages to wriggle enough to right itself.")) messagevoice = list("Fuck you." = 'sound/voice/medbot/fuck_you.ogg', "Your behavior has been reported, have a nice day." = 'sound/voice/medbot/reported.ogg') tipper_name = null - if(world.time > last_tipping_action_voice + 15 SECONDS) - last_tipping_action_voice = world.time + + if(COOLDOWN_FINISHED(src, last_tipping_action_voice)) + COOLDOWN_START(src, last_tipping_action_voice, 15 SECONDS) var/message = pick(messagevoice) speak(message) playsound(src, messagevoice[message], 70) tipped_status = MEDBOT_PANIC_NONE mode = BOT_IDLE - transform = matrix() /// if someone tipped us over, check whether we should ask for help or just right ourselves eventually /mob/living/simple_animal/bot/medbot/proc/handle_panic() @@ -294,7 +324,6 @@ messagevoice = list("Is this the end?" = 'sound/voice/medbot/is_this_the_end.ogg', "Nooo!" = 'sound/voice/medbot/nooo.ogg') if(MEDBOT_PANIC_END) speak("PSYCH ALERT: Crewmember [tipper_name] recorded displaying antisocial tendencies torturing bots in [get_area(src)]. Please schedule psych evaluation.", radio_channel) - set_right() // strong independent medbot if(prob(tipped_status)) do_jitter_animation(tipped_status * 0.1) @@ -452,31 +481,6 @@ if(damagetype_healer == "all" && treat_me_for.len) return TRUE -/mob/living/simple_animal/bot/medbot/attack_hand(mob/living/carbon/human/user, list/modifiers) - if(DOING_INTERACTION_WITH_TARGET(user, src)) - to_chat(user, span_warning("You're already interacting with [src].")) - return - - if(LAZYACCESS(modifiers, RIGHT_CLICK) && mode != BOT_TIPPED) - user.visible_message(span_danger("[user] begins tipping over [src]."), span_warning("You begin tipping over [src]...")) - - if(world.time > last_tipping_action_voice + 15 SECONDS) - last_tipping_action_voice = world.time // message for tipping happens when we start interacting, message for righting comes after finishing - var/list/messagevoice = list("Hey, wait..." = 'sound/voice/medbot/hey_wait.ogg',"Please don't..." = 'sound/voice/medbot/please_dont.ogg',"I trusted you..." = 'sound/voice/medbot/i_trusted_you.ogg', "Nooo..." = 'sound/voice/medbot/nooo.ogg', "Oh fuck-" = 'sound/voice/medbot/oh_fuck.ogg') - var/message = pick(messagevoice) - speak(message) - playsound(src, messagevoice[message], 70, FALSE) - - if(do_after(user, 3 SECONDS, target=src)) - tip_over(user) - - else if(!user.combat_mode && mode == BOT_TIPPED) - user.visible_message(span_notice("[user] begins righting [src]."), span_notice("You begin righting [src]...")) - if(do_after(user, 3 SECONDS, target=src)) - set_right(user) - else - ..() - /mob/living/simple_animal/bot/medbot/UnarmedAttack(atom/A, proximity_flag, list/modifiers) if(HAS_TRAIT(src, TRAIT_HANDS_BLOCKED)) return 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 5409370dc51..34c7d7257c7 100644 --- a/code/modules/mob/living/simple_animal/friendly/farm_animals.dm +++ b/code/modules/mob/living/simple_animal/friendly/farm_animals.dm @@ -133,6 +133,11 @@ /mob/living/simple_animal/cow/Initialize() AddComponent(/datum/component/udder) + AddComponent(/datum/component/tippable, \ + tip_time = 0.5 SECONDS, \ + untip_time = 0.5 SECONDS, \ + self_right_time = rand(25 SECONDS, 50 SECONDS), \ + post_tipped_callback = CALLBACK(src, .proc/after_cow_tipped)) AddElement(/datum/element/pet_bonus, "moos happily!") add_cell_sample() make_tameable() @@ -150,34 +155,43 @@ buckle_lying = 0 AddElement(/datum/element/ridable, /datum/component/riding/creature/cow) -/mob/living/simple_animal/cow/attack_hand(mob/living/carbon/user, list/modifiers) - if(!stat && LAZYACCESS(modifiers, RIGHT_CLICK) && icon_state != icon_dead) - user.visible_message(span_warning("[user] tips over [src]."), - span_notice("You tip over [src].")) - to_chat(src, span_userdanger("You are tipped over by [user]!")) - Paralyze(60, ignore_canstun = TRUE) - icon_state = icon_dead - addtimer(CALLBACK(src, .proc/cow_tipped, user), rand(20,50)) +/* + * Proc called via callback after the cow is tipped by the tippable component. + * Begins a timer for us pleading for help. + * + * tipper - the mob who tipped us + */ +/mob/living/simple_animal/cow/proc/after_cow_tipped(mob/living/carbon/tipper) + addtimer(CALLBACK(src, .proc/look_for_help, tipper), rand(10 SECONDS, 20 SECONDS)) - else - ..() +/* + * Find a mob in a short radius around us (prioritizing the person who originally tipped us) + * and either look at them for help, or give up. No actual mechanical difference between the two. + * + * tipper - the mob who originally tipped us + */ +/mob/living/simple_animal/cow/proc/look_for_help(mob/living/carbon/tipper) + // visible part of the visible message + var/seen_message = "" + // self part of the visible message + var/self_message = "" + // the mob we're looking to for aid + var/mob/living/carbon/savior + // look for someone in a radius around us for help. If our original tipper is in range, prioritize them + for(var/mob/living/carbon/potential_aid in oview(3, get_turf(src))) + if(potential_aid == tipper) + savior = tipper + break + savior = potential_aid -/mob/living/simple_animal/cow/proc/cow_tipped(mob/living/carbon/M) - if(QDELETED(M) || stat) - return - icon_state = icon_living - var/external - var/internal - if(prob(75)) - var/text = pick("imploringly.", "pleadingly.", - "with a resigned expression.") - external = "[src] looks at [M] [text]" - internal = "You look at [M] [text]" + if(prob(75) && savior) + var/text = pick("imploringly", "pleadingly", "with a resigned expression") + seen_message = "[src] looks at [savior] [text]." + self_message = "You look at [savior] [text]." else - external = "[src] seems resigned to its fate." - internal = "You resign yourself to your fate." - visible_message(span_notice("[external]"), - span_revennotice("[internal]")) + seen_message = "[src] seems resigned to its fate." + self_message = "You resign yourself to your fate." + visible_message(span_notice("[seen_message]"), span_notice("[self_message]")) ///Wisdom cow, gives XP to a random skill and speaks wisdoms /mob/living/simple_animal/cow/wisdom diff --git a/tgstation.dme b/tgstation.dme index 4f7d7ad24c4..c75834878b5 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -578,6 +578,7 @@ #include "code\datums\components\technoshy.dm" #include "code\datums\components\tether.dm" #include "code\datums\components\thermite.dm" +#include "code\datums\components\tippable.dm" #include "code\datums\components\trapdoor.dm" #include "code\datums\components\twohanded.dm" #include "code\datums\components\udder.dm"