From 747d8111cac72d8f4b4e508ca1dd052f95ed7269 Mon Sep 17 00:00:00 2001 From: Luc <89928798+lewcc@users.noreply.github.com> Date: Tue, 14 Jun 2022 15:06:24 -0700 Subject: [PATCH] Datumized Emotes (#17544) * Add raw tg emote datums * Some more initial implementation * First (big) commit * More reworks, most emotes seem to work but cooldowns are weird * Add the remaining emote species * Add simple mobs * Update defines, add more comments * Remove unnecessary intentional calls * Fix some bugs, add more functionality - Silicon mobs/bots can't use all the living emotes - Sound volume can now be specified for emotes - Added a define for emotes to not go thru runechat - Reorganized some emotes - Some human emotes that need breath will gasp - IPCs can beep boop - Restore power warn for robits - Sneezing and coughing have sound effects * Fixes emote species separation not working in *help * Reorganize emotes based on what's available on paradise * mouthful mode * Update implants to use emote signals * Update species whitelist to use a typecache * Cleanups * More small changes - Remove old extraneous typecache - Reconfigure drask emotes - Make all mime emotes visible (since they're silent) * Remove old emote functions * Update emote sounds, volume * Rework more emotes into EMOTE_VISIBLE * *rumble* * More slight testing and cleanup - Drop audio emote cooldown from 10 to 5 seconds - Change clapping behaivor slightly, putting it into run_emote - Update some emote flags here and there - Fix up johnny, though remind me why this one exists? * Fix indentation, missing typecast * fix some returns * More review * Rename emote files * Add'l review * Even more emote fixes! - Move defines out into an emote define file - Integrate audio and general emote cooldowns - change some 1 and 2 into visible/audible - Try to fix monkey screech * Add ability for admins to mute users' emotes. * Rename cooldowns, add general mob emote cooldown. * Fix flip not always geting the right message * Add some emote target handling, docs * Fix admin rights, indentation * Update emote.dm set default mode to ANY * General implant fixes - The way implant triggers are handled is now improved, with separate checks for death and emotes instead of just checking deathgasp. - Implants can choose to be triggered for the first death or for every death. - Voice of God play dead now activates sad trombone read: voice of god can now no longer lowtiergod nukies * Bunch of other changes and bugfixes - Adds number flag for behavior - Reworks how muzzling/vocalizing emotes works - Breaks out sound effects - Drops cooldown to 1.5s * Fix people being able to snore/nightmare while awake. * Bump paralysis on living emotes * First review pass * Clean up implants (while we're here) and fix compile errors * in living error * More outstanding review fixes * use more isx() checks * Add pre-emote signal, try_run_emote() * Prevent silicons from playing their deathgasp multiple times * Add emote postfix behavior for adding parameters to non-message params. Also adds a signal to intercept emote actions. * Fix linter complaints * Remove new player checks on GLOB.dead_mob_list * Fix species emotes not being distinguished, remove weird legacy code * monkey ball * better docs == better code * Fix audio cooldown, silicon emotes * Fix ghost emotes (don't worry they're staying) * Restore spin to 2 seconds * oh johnny boy * Make fainting last two seconds instead of .2 seconds * Remove extra highfive message * Tick friendly emotes * Fix up friendly emotes, ghost emotes * Add some emote message safeguards, prevent flip from showing twice * Fix ghost vision message format * Fix hands_use_check on non-carbon mobs * Bring emotes in line with say re. oxyloss * Add option for death implants to not trigger on gib * Try adding some unit testing * Include emote unit tests * More attempts at unit testing * More attempts at unit testing? * forget it this is fine * Housekeeping * Little bit more * Remove extra bolding from ghostsight emotes * More sanity checks, fix snore * Remove stack trace when unintentional emote fails * Update code/modules/mob/living/silicon/silicon_emote.dm oops Co-authored-by: AffectedArc07 <25063394+AffectedArc07@users.noreply.github.com> * More implant cleanups * Oh I missed this * Silence wagging, more review fixes * Oops 2 * ensure everything can swear * High-five fixes and status effect stuff - Adds new on_timeout() function that gets triggered when a status effect expires - Fixes up passing arguments to status effects - Ensures high-five explosions don't kill the wizards * More review stuff - Add a stat to text to make things easier - Make it harder to delete emotes - BIG DANGER - pose can no longer be set while unconscious * Remove extra high five logic, godmode changes * forgor * Fix gasp not working * Update docs for good measure * Some review and custom emote fixes * Fixes death alarms being broken * Better handle stat_allowed, more review comments. * ..() conventions * This is why unit tests are nice * Remove drone snowflake emote stuff * Linting * No more flipping on the ground * Snap doesn't require hands free * Does it make complete sense? no, but it's Fun * Apply suggestions from code review Co-authored-by: AffectedArc07 <25063394+AffectedArc07@users.noreply.github.com> * Fix revenant messages, power warning * epic webedit fail, laugh at this user Co-authored-by: AffectedArc07 <25063394+AffectedArc07@users.noreply.github.com> --- code/__DEFINES/admin.dm | 3 +- code/__DEFINES/dcs/signals.dm | 21 +- code/__DEFINES/emotes.dm | 73 ++ code/__DEFINES/mobs.dm | 3 - code/__HELPERS/global_lists.dm | 21 + code/__HELPERS/traits.dm | 1 + code/_globalvars/lists/mobs.dm | 2 + code/_globalvars/traits.dm | 1 + code/datums/emote.dm | 620 ++++++++++ code/datums/spell.dm | 2 +- code/datums/status_effects/neutral.dm | 20 +- code/datums/status_effects/status_effect.dm | 4 +- code/game/gamemodes/blob/overmind.dm | 3 - code/game/gamemodes/miniantags/borer/borer.dm | 3 - .../gamemodes/miniantags/revenant/revenant.dm | 7 +- code/game/mecha/mecha.dm | 2 +- .../objects/items/weapons/implants/implant.dm | 168 ++- .../weapons/implants/implant_abductor.dm | 2 +- .../items/weapons/implants/implant_chem.dm | 9 +- .../items/weapons/implants/implant_clown.dm | 15 +- .../weapons/implants/implant_death_alarm.dm | 19 +- .../weapons/implants/implant_explosive.dm | 12 +- .../weapons/implants/implant_krav_maga.dm | 2 +- .../weapons/implants/implant_mindshield.dm | 2 +- .../items/weapons/implants/implant_misc.dm | 2 +- .../items/weapons/implants/implant_track.dm | 2 +- code/game/objects/obj_defense.dm | 2 +- code/modules/admin/admin.dm | 1 + code/modules/admin/verbs/randomverbs.dm | 23 +- code/modules/awaymissions/exile.dm | 2 +- code/modules/flufftext/Dreaming.dm | 3 +- code/modules/mob/dead/observer/say.dm | 22 - code/modules/mob/emote.dm | 150 --- .../mob/living/carbon/alien/humanoid/emote.dm | 141 --- .../carbon/alien/humanoid/humanoid_emote.dm | 32 + .../mob/living/carbon/alien/larva/emote.dm | 126 -- .../mob/living/carbon/brain/brain_emote.dm | 67 ++ code/modules/mob/living/carbon/brain/emote.dm | 51 - .../modules/mob/living/carbon/carbon_emote.dm | 190 +++ code/modules/mob/living/carbon/human/death.dm | 2 - code/modules/mob/living/carbon/human/emote.dm | 1053 ----------------- code/modules/mob/living/carbon/human/human.dm | 18 + .../mob/living/carbon/human/human_emote.dm | 686 +++++++++++ .../mob/living/carbon/human/human_organs.dm | 6 +- code/modules/mob/living/death.dm | 2 +- code/modules/mob/living/living_defense.dm | 2 +- code/modules/mob/living/living_emote.dm | 434 +++++++ code/modules/mob/living/say.dm | 40 +- code/modules/mob/living/silicon/death.dm | 2 +- code/modules/mob/living/silicon/emote.dm | 81 -- .../modules/mob/living/silicon/robot/death.dm | 3 - .../living/silicon/robot/drone/drone_say.dm | 6 - .../modules/mob/living/silicon/robot/emote.dm | 183 --- .../modules/mob/living/silicon/robot/robot.dm | 16 + code/modules/mob/living/silicon/say.dm | 6 +- .../mob/living/silicon/silicon_emote.dm | 97 ++ .../mob/living/simple_animal/bot/bot.dm | 1 + .../mob/living/simple_animal/bot/emote.dm | 74 -- .../mob/living/simple_animal/friendly/cat.dm | 49 +- .../living/simple_animal/friendly/diona.dm | 26 - .../mob/living/simple_animal/friendly/dog.dm | 44 +- .../simple_animal/friendly/friendly_emote.dm | 93 ++ .../living/simple_animal/friendly/mouse.dm | 27 +- .../living/simple_animal/hostile/hellhound.dm | 4 +- .../living/simple_animal/hostile/hostile.dm | 2 +- .../simple_animal/hostile/megafauna/drake.dm | 2 +- .../mob/living/simple_animal/parrot.dm | 12 +- .../mob/living/simple_animal/simple_animal.dm | 26 +- .../mob/living/simple_animal/slime/emote.dm | 105 -- .../living/simple_animal/slime/slime_emote.dm | 66 ++ code/modules/mob/mob.dm | 26 +- code/modules/mob/mob_defines.dm | 14 +- code/modules/mob/mob_emote.dm | 192 +++ code/modules/mob/mob_helpers.dm | 45 + code/modules/mob/say.dm | 20 +- code/modules/surgery/organs/blood.dm | 2 +- code/modules/surgery/organs/lungs.dm | 4 +- code/modules/surgery/organs/organ_external.dm | 4 +- .../surgery/organs/subtypes/standard.dm | 12 +- code/modules/unit_tests/_unit_tests.dm | 1 + code/modules/unit_tests/emotes.dm | 29 + paradise.dme | 20 +- 82 files changed, 3035 insertions(+), 2331 deletions(-) create mode 100644 code/__DEFINES/emotes.dm create mode 100644 code/datums/emote.dm delete mode 100644 code/modules/mob/emote.dm delete mode 100644 code/modules/mob/living/carbon/alien/humanoid/emote.dm create mode 100644 code/modules/mob/living/carbon/alien/humanoid/humanoid_emote.dm delete mode 100644 code/modules/mob/living/carbon/alien/larva/emote.dm create mode 100644 code/modules/mob/living/carbon/brain/brain_emote.dm delete mode 100644 code/modules/mob/living/carbon/brain/emote.dm create mode 100644 code/modules/mob/living/carbon/carbon_emote.dm delete mode 100644 code/modules/mob/living/carbon/human/emote.dm create mode 100644 code/modules/mob/living/carbon/human/human_emote.dm create mode 100644 code/modules/mob/living/living_emote.dm delete mode 100644 code/modules/mob/living/silicon/emote.dm delete mode 100644 code/modules/mob/living/silicon/robot/emote.dm create mode 100644 code/modules/mob/living/silicon/silicon_emote.dm delete mode 100644 code/modules/mob/living/simple_animal/bot/emote.dm create mode 100644 code/modules/mob/living/simple_animal/friendly/friendly_emote.dm delete mode 100644 code/modules/mob/living/simple_animal/slime/emote.dm create mode 100644 code/modules/mob/living/simple_animal/slime/slime_emote.dm create mode 100644 code/modules/mob/mob_emote.dm create mode 100644 code/modules/unit_tests/emotes.dm diff --git a/code/__DEFINES/admin.dm b/code/__DEFINES/admin.dm index 879d2562d0e..9b5fe96f674 100644 --- a/code/__DEFINES/admin.dm +++ b/code/__DEFINES/admin.dm @@ -6,7 +6,8 @@ #define MUTE_PRAY 4 #define MUTE_ADMINHELP 8 #define MUTE_DEADCHAT 16 -#define MUTE_ALL 31 +#define MUTE_EMOTE 32 +#define MUTE_ALL 63 //Number of identical messages required to get the spam-prevention automute thing to trigger warnings and automutes #define SPAM_TRIGGER_WARNING 5 diff --git a/code/__DEFINES/dcs/signals.dm b/code/__DEFINES/dcs/signals.dm index 1b57121586b..3f80482533b 100644 --- a/code/__DEFINES/dcs/signals.dm +++ b/code/__DEFINES/dcs/signals.dm @@ -335,12 +335,31 @@ ///from /mob/say_dead(): (mob/speaker, message) #define COMSIG_MOB_DEADSAY "mob_deadsay" #define MOB_DEADSAY_SIGNAL_INTERCEPT (1<<0) -///from /mob/living/emote(): () + +/// Signal fired when an emote is used but before it's executed. +///from /datum/emote/proc/try_run_emote(): (key, intentional) +#define COMSIG_MOB_PREEMOTE "mob_preemote" + // Use these to block execution of emotes from components. + /// Return this to block an emote and let the user know the emote is unusable. + #define COMPONENT_BLOCK_EMOTE_UNUSABLE (1<<0) + /// Return this to block an emote silently. + #define COMPONENT_BLOCK_EMOTE_SILENT (1<<1) +/// General signal fired when a mob does any old emote +///from /datum/emote/proc/run_emote(): (key, intentional) #define COMSIG_MOB_EMOTE "mob_emote" +/// Specific signal used to track when a specific emote is used. +/// From /datum/emote/run_emote(): (P, key, m_type, message, intentional) +#define COMSIG_MOB_EMOTED(emote_key) "mob_emoted_[emote_key]" +/// From /datum/emote/select_param(): (target, key, intentional) +#define COMSIG_MOB_EMOTE_AT "mob_emote_at" + #define COMPONENT_BLOCK_EMOTE_ACTION (1<<2) + ///from base of mob/swap_hand(): (obj/item) #define COMSIG_MOB_SWAP_HANDS "mob_swap_hands" #define COMPONENT_BLOCK_SWAP (1<<0) + + // /mob/living signals ///from base of mob/living/resist() (/mob/living) diff --git a/code/__DEFINES/emotes.dm b/code/__DEFINES/emotes.dm new file mode 100644 index 00000000000..64180cd0eac --- /dev/null +++ b/code/__DEFINES/emotes.dm @@ -0,0 +1,73 @@ +/** + * General defines used for emotes + */ + +// Emote types. +// These determine how the emote is treated when not directly visible (or audible). +// These are also used in some cases for visible/audible messages. + +/// Emote is visible. These emotes will be runechatted. +#define EMOTE_VISIBLE (1<<0) +/// Emote is audible (in character). +#define EMOTE_AUDIBLE (1<<1) +/// Emote makes a sound. These emotes will specifically not be runechatted. +#define EMOTE_SOUND (1<<2) +/// Regardless of its existing flags, an emote with this flag will not be sent to runechat. +#define EMOTE_FORCE_NO_RUNECHAT (1<<3) +/// This emote uses the mouth, and so should be blocked if the user is muzzled or can't breathe (for humans). +#define EMOTE_MOUTH (1<<4) + +// User audio cooldown system. +// This is a value stored on the user and represents their current ability to perform audio emotes. + +/// The user is not on emote cooldown, and is ready to emote whenever. +#define EMOTE_READY 0 +/// The user can spam emotes to their heart's content. +#define EMOTE_INFINITE 1 +/// The user cannot emote as they have been blocked by an admin. +#define EMOTE_ADMIN_BLOCKED 2 +/// The user cannot emote until their cooldown expires. +#define EMOTE_ON_COOLDOWN 3 + +/// Marker to separate an emote key from its parameters in user input. +#define EMOTE_PARAM_SEPARATOR "-" + +/// Default cooldown for normal (non-audio) emotes. +#define DEFAULT_EMOTE_COOLDOWN (1.5 SECONDS) + +// Each mob can only play an emote with audio every AUDIO_EMOTE_COOLDOWN seconds, unless a given emote overrides its own audio cooldown. + +/// Default cooldown for audio that comes from emotes. +#define AUDIO_EMOTE_COOLDOWN (5 SECONDS) + +// Emote parameter types +// If nothing is passed in for a target, this can determine the possible target. + +// Our emote code will search around the user to find a matching target to use, based on the second set of defines. +// This first set of defines denotes the behavior if a match can't be found. + +/// If this is set and a valid target is not found, the emote will not execute. +#define EMOTE_TARGET_BHVR_MUST_MATCH 1 +/// If this is set and a valid target is not found, the emote will just ignore the parameter entirely. +#define EMOTE_TARGET_BHVR_DEFAULT_TO_BASE 2 +/// If this is set and a valid target is not found, the emote will work with the params that it has. +#define EMOTE_TARGET_BHVR_USE_PARAMS_ANYWAY 3 +/// No matching/processing will be performed, and the target will be purely what's passed in. +#define EMOTE_TARGET_BHVR_RAW 4 +/// The emote target should be just a number. Anything else will be rejected. +#define EMOTE_TARGET_BHVR_NUM 5 + +// This set determines the type of target that we want to check for. + +/// The target will check all nearby living mobs. +#define EMOTE_TARGET_MOB (1<<0) +/// The target will check all objects nearby. +#define EMOTE_TARGET_OBJ (1<<1) +/// The target will check nearby mobs and objects. +#define EMOTE_TARGET_ANY (EMOTE_TARGET_MOB | EMOTE_TARGET_OBJ) + +/// If passed as message_param, will default to using the message's postfix. +#define EMOTE_PARAM_USE_POSTFIX 1 + +/// If returned from act_on_target(), emote execution will stop. +#define EMOTE_ACT_STOP_EXECUTION 1 diff --git a/code/__DEFINES/mobs.dm b/code/__DEFINES/mobs.dm index 66704ca2f14..698bfb9a358 100644 --- a/code/__DEFINES/mobs.dm +++ b/code/__DEFINES/mobs.dm @@ -191,9 +191,6 @@ #define TINT_BLIND 3 //Threshold of tint level to obscure vision fully #define EYE_SHINE_THRESHOLD 6 //dark_view threshold past which a humanoid's eyes will 'shine' in the dark. -#define EMOTE_VISUAL 1 //A mob emote is visual -#define EMOTE_SOUND 2 //A mob emote is sound - #define STATUS_UPDATE_HEALTH 1 #define STATUS_UPDATE_STAT 2 #define STATUS_UPDATE_STAMINA 8 diff --git a/code/__HELPERS/global_lists.dm b/code/__HELPERS/global_lists.dm index 013ab6a4fef..0d7d6113d8c 100644 --- a/code/__HELPERS/global_lists.dm +++ b/code/__HELPERS/global_lists.dm @@ -164,6 +164,8 @@ GLOB.karma_packages[KP.database_id] = KP + GLOB.emote_list = init_emote_list() + /* // Uncomment to debug chemical reaction list. /client/verb/debug_chemical_list() @@ -197,3 +199,22 @@ if(assoc) //value gotten L["[assoc]"] = D //put in association return L + + +/proc/init_emote_list() + . = list() + for(var/path in subtypesof(/datum/emote)) + var/datum/emote/E = new path() + if(E.key) + if(!.[E.key]) + .[E.key] = list(E) + else + .[E.key] += E + else if(E.message) //Assuming all non-base emotes have this + stack_trace("Keyless emote: [E.type]") + + if(E.key_third_person) //This one is optional + if(!.[E.key_third_person]) + .[E.key_third_person] = list(E) + else + .[E.key_third_person] |= E diff --git a/code/__HELPERS/traits.dm b/code/__HELPERS/traits.dm index 9e9ba078cc5..792b96cc3a6 100644 --- a/code/__HELPERS/traits.dm +++ b/code/__HELPERS/traits.dm @@ -167,6 +167,7 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai #define TRAIT_MESON_VISION "meson_vision" #define TRAIT_FLASH_PROTECTION "flash_protection" #define TRAIT_NIGHT_VISION "night_vision" +#define TRAIT_EMOTE_MUTE "emote_mute" #define TRAIT_NO_BONES "no_bones" #define TRAIT_STURDY_LIMBS "sturdy_limbs" diff --git a/code/_globalvars/lists/mobs.dm b/code/_globalvars/lists/mobs.dm index 17444f66207..61d970091b4 100644 --- a/code/_globalvars/lists/mobs.dm +++ b/code/_globalvars/lists/mobs.dm @@ -33,3 +33,5 @@ GLOBAL_LIST_EMPTY(sec_hud_users) GLOBAL_LIST_EMPTY(antag_hud_users) GLOBAL_LIST_EMPTY(surgeries_list) GLOBAL_LIST_EMPTY(hear_radio_list) //Mobs that hear the radio even if there's no client + +GLOBAL_LIST_EMPTY(emote_list) diff --git a/code/_globalvars/traits.dm b/code/_globalvars/traits.dm index 8f20a290658..27c37f51e93 100644 --- a/code/_globalvars/traits.dm +++ b/code/_globalvars/traits.dm @@ -65,6 +65,7 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_NOEXAMINE" = TRAIT_NOEXAMINE, "TRAIT_NOPAIN" = TRAIT_NOPAIN, "TRAIT_FORCE_DOORS" = TRAIT_FORCE_DOORS, + "TRAIT_EMOTE_MUTE" = TRAIT_EMOTE_MUTE, ), /obj/item = list( "TRAIT_SHOW_WIRE_INFO" = TRAIT_SHOW_WIRE_INFO, diff --git a/code/datums/emote.dm b/code/datums/emote.dm new file mode 100644 index 00000000000..13c9a836b22 --- /dev/null +++ b/code/datums/emote.dm @@ -0,0 +1,620 @@ + +// Defines are in code\__DEFINES\emotes.dm + +/// Sentinel for emote stats. +/// If this is set for max stat, then its value will be ignored. +#define DEFAULT_MAX_STAT_ALLOWED "defaultstat" + +/** + * # Emote + * + * Most of the text that's not someone talking is based off of this. + * + */ +/datum/emote + /// What calls the emote. + var/key = "" + /// This will also call the emote. + var/key_third_person = "" + /// Message displayed when emote is used. + var/message = "" + /// Message displayed if the user is a mime. + var/message_mime = "" + /// Message displayed if the user is a grown alien. + var/message_alien = "" + /// Message displayed if the user is an alien larva. + var/message_larva = "" + /// Message displayed if the user is a robot. + var/message_robot = "" + /// Message displayed if the user is an AI. + var/message_AI = "" + /// Message displayed if the user is a monkey. + var/message_monkey = "" + /// Message to display if the user is a simple_animal. + var/message_simple = "" + /// Message to display if the user is a spooky observer ghost. + var/message_observer = "" + /// Sounds emitted when the user is muzzled. Generally used like "[user] makes a pick(muzzled_noises) noise!" + var/muzzled_noises = list("strong", "weak") + /// Message with %t at the end to allow adding params to the message, like for mobs doing an emote relatively to something else. + /// Set this to EMOTE_PARAM_USE_POSTFIX to just use the postfix. + var/message_param = "" + /// Message postfix with %t used when we don't want to use message_param for our targeting. Used for things like message_monkey or message_mime. + /// Punctuation from the message will be stripped when this is applied, so make sure it's punctuated as well. + var/message_postfix = "" + /// Description appended to the emote name describing what the target should be, like for help commands. + var/param_desc = "target" + /// Whether the emote is visible or audible. + var/emote_type = EMOTE_VISIBLE + /// Checks if the mob can use its hands before performing the emote. + var/hands_use_check = FALSE + /// If the emote type is EMOTE_MOUTH but should still bypass a muzzle. + var/muzzle_ignore = FALSE + /// Types that are allowed to use that emote. + var/list/mob_type_allowed_typecache = /mob + /// Types that are NOT allowed to use that emote. + var/list/mob_type_blacklist_typecache + /// Types that can use this emote regardless of their state. + var/list/mob_type_ignore_stat_typecache + /// Species types which the emote will be exclusively available to. Should be subclasses of /datum/species + var/species_type_whitelist_typecache + /// If we get a target, how do we want to treat it? + var/target_behavior = EMOTE_TARGET_BHVR_USE_PARAMS_ANYWAY + /// If our target behavior isn't to ignore, what should we look for with targets? + var/emote_target_type = EMOTE_TARGET_ANY + + // Stat_allowed is the "lower threshold" for stat, and basically represents how alive you have to be to use it. + // on the other hand, max_stat_allowed is the "upper threshold" representing how 'dead' you can be while still using the emote. + // see stat.dm for the stats that you can actually use here. + + // Typical use case might be setting unintentional_stat_allowed to UNCONSCIOUS to allow a mob to gasp whether or not they're conscious. + // A use case for max_stat alone would be in case you'd want someone to be able to do something while unconscious or dead. Unlikely, but the option will still be there. + // A use case for both at once would be fixing it somewhere in the middle, like only allowing mobs to snore while they're unconscious. + + // (worth noting: this is flexible on purpose in case we ever increase the amount of life stats). + + /// How conscious do you need to be to use this emote intentionally? + var/stat_allowed = CONSCIOUS + /// How unconscious/dead can you be while still being able to use this emote intentionally? + /// If this is set to DEFAULT_STAT_ALLOWED, it'll behave as if it isn't set. + var/max_stat_allowed = DEFAULT_MAX_STAT_ALLOWED + /// How conscious do you need to be to have this emote forced out of you? + var/unintentional_stat_allowed = CONSCIOUS + /// Same as above, how unconscious/dead do you need to be to have this emote forced out of you? + /// If this is set to DEFAULT_STAT_ALLOWED, it'll behave as if it isn't set. + var/max_unintentional_stat_allowed = DEFAULT_MAX_STAT_ALLOWED + /// Sound to play when emote is called. If you want to adjust this dynamically, see get_sound(). + var/sound + /// Whether or not to vary the sound of the emote. + var/vary = FALSE + /// Whether or not to adjust the frequency of the emote sound based on age. + var/age_based = FALSE + /// If true, this emote will only make a sound effect when called unintentionally. + var/only_forced_audio = FALSE + /// Whether or not the emote can even be called at all if it's not intentional + var/only_unintentional = FALSE + /// The cooldown between the uses of the emote. + var/cooldown = DEFAULT_EMOTE_COOLDOWN + /// How long is the cooldown on the audio of the emote, if it has one? + var/audio_cooldown = AUDIO_EMOTE_COOLDOWN + /// How loud is the audio emote? + var/volume = 50 + +/datum/emote/New() + if(message_param && !param_desc) + CRASH("emote [src] was given a message parameter without a description.") + if(ispath(mob_type_allowed_typecache)) + switch(mob_type_allowed_typecache) + if(/mob) + mob_type_allowed_typecache = GLOB.typecache_mob + if(/mob/living) + mob_type_allowed_typecache = GLOB.typecache_living + else + mob_type_allowed_typecache = typecacheof(mob_type_allowed_typecache) + else + mob_type_allowed_typecache = typecacheof(mob_type_allowed_typecache) + mob_type_blacklist_typecache = typecacheof(mob_type_blacklist_typecache) + mob_type_ignore_stat_typecache = typecacheof(mob_type_ignore_stat_typecache) + species_type_whitelist_typecache = typecacheof(species_type_whitelist_typecache) + +/datum/emote/Destroy(force) + if(force) + return ..() + else + // if you're deleting an emote something has gone wrong + return QDEL_HINT_LETMELIVE + +/** + * Handles the modifications and execution of emotes. + * + * In general, what this does: + * - Checks if the user can run the emote at all + * - Checks and applies the message parameter, if it exists + * - Replaces pronouns with a mob's specific pronouns + * - Checks for and plays sound if the emote supports it + * - Sends the emote to users + * - Runechats the emote + * + * You most likely want to use try_run_emote() anywhere you would otherwise call this directly, + * as that will incorporate can_run_emote() checking as well. + * + * Arguments: + * * user - Person that is trying to send the emote. + * * params - Parameters added after the emote. + * * type_override - Override to the current emote_type. + * * intentional - Bool that says whether the emote was forced (FALSE) or not (TRUE). + * + * Returns TRUE if it was able to run the emote, FALSE otherwise. + */ +/datum/emote/proc/run_emote(mob/user, params, type_override, intentional = FALSE) + . = TRUE + var/msg = select_message_type(user, message, intentional) + if(params && message_param) + // In this case, we did make some changes to the message that will be used, and we want to add the postfix on with the new parameters. + // This is applicable to things like mimes, who this lets have a target on their canned emote responses. + // Note that we only do this if we would otherwise have a message param, meaning there should be some target by default. + // If we're using EMOTE_PARAM_USE_POSTFIX, we don't want to bother specifying a message_param and just want to use the postfix for everything. + if(message_param == EMOTE_PARAM_USE_POSTFIX || (msg != message && message_postfix)) + if(!message_postfix) + CRASH("Emote was specified to use postfix but message_postfix is empty.") + msg = select_param(user, params, "[remove_ending_punctuation(msg)] [message_postfix]", msg) + else if(msg == message) + // In this case, we're not making any substitutions in select_message_type, but we do have some params we want to sub in. + msg = select_param(user, params, message_param, message) + + // If this got propogated up, jump out. + if(msg == EMOTE_ACT_STOP_EXECUTION) + return TRUE + + if(isnull(msg)) + to_chat(user, "'[params]' isn't a valid parameter for [key].") + return TRUE + + msg = replace_pronoun(user, msg) + + var/suppressed = FALSE + + // Keep em quiet if they can't speak + if(!can_vocalize_emotes(user) && (emote_type & (EMOTE_MOUTH | EMOTE_AUDIBLE) || emote_type & (EMOTE_MOUTH | EMOTE_SOUND))) + var/noise_emitted = pick(muzzled_noises) + suppressed = TRUE + msg = "makes \a [noise_emitted] noise." + + var/tmp_sound = get_sound(user) + var/sound_volume = get_volume(user) + // If our sound emote is forced by code, don't worry about cooldowns at all. + if(tmp_sound && should_play_sound(user, intentional) && sound_volume > 0) + if(!intentional || user.start_audio_emote_cooldown(audio_cooldown)) + play_sound_effect(user, intentional, tmp_sound, sound_volume) + + if(msg) + if(isobserver(user)) + log_ghostemote(msg, user) + else + log_emote(msg, user) + + var/displayed_msg = "[user] [msg]" + + var/user_turf = get_turf(user) + if(user.client && !isobserver(user)) + for(var/mob/ghost as anything in GLOB.dead_mob_list) + if(!ghost.client) + continue + if((ghost.client.prefs.toggles & PREFTOGGLE_CHAT_GHOSTSIGHT) && !(ghost in viewers(user_turf, null))) + ghost.show_message("[user] ([ghost_follow_link(user, ghost)]) [msg]") + + if(isobserver(user)) + for(var/mob/dead/observer/ghost in viewers(user)) + ghost.show_message("[displayed_msg]", EMOTE_VISIBLE) + + else if(emote_type & EMOTE_VISIBLE || user.mind?.miming) + user.audible_message(displayed_msg, deaf_message = "You see how [user] [msg]") + else + user.visible_message(displayed_msg, blind_message = "You hear how [user] [msg]") + + if(!(emote_type & (EMOTE_FORCE_NO_RUNECHAT | EMOTE_SOUND) || suppressed) && !isobserver(user)) + runechat_emote(user, msg) + + SEND_SIGNAL(user, COMSIG_MOB_EMOTED(key), src, key, emote_type, message, intentional) + SEND_SIGNAL(user, COMSIG_MOB_EMOTE, key, intentional) + +/** + * Try to run an emote, checking can_run_emote once before executing the emote itself. + * + * * user - User of the emote + * * params - Params of the emote to be passed to run_emote + * * type_override - emote type to override the existing one with, if given. + * * intentional - Whether or not the emote was triggered intentionally (if false, the emote was forced by code). + * + * Returns TRUE if the emote was able to be run (or failed successfully), or FALSE if the emote is unusable. + */ +/datum/emote/proc/try_run_emote(mob/user, params, type_override, intentional = FALSE) + if(!can_run_emote(user, intentional = intentional)) + return FALSE + + // You can use this signal to block execution of emotes from components/other sources. + var/sig_res = SEND_SIGNAL(user, COMSIG_MOB_PREEMOTE, key, intentional) + switch(sig_res) + if(COMPONENT_BLOCK_EMOTE_UNUSABLE) + return FALSE + if(COMPONENT_BLOCK_EMOTE_SILENT) + return TRUE + + . = run_emote(user, params, type_override, intentional) + + // safeguard in case these get modified + message = initial(message) + message_param = initial(message_param) + +/** + * Play the sound effect in an emote. + * If you want to change the way the playsound call works, override this. + * * user - The user of the emote. + * * intentional - Whether or not the emote was triggered intentionally. + * * sound_path - Filesystem path to the audio clip to play. + * * sound_volume - Volume at which to play the audio clip. + */ +/datum/emote/proc/play_sound_effect(mob/user, intentional, sound_path, sound_volume) + if(age_based && ishuman(user)) + var/mob/living/carbon/human/H = user + playsound(user.loc, sound_path, sound_volume, vary, frequency = H.get_age_pitch()) + else + playsound(user.loc, sound_path, sound_volume, vary) + +/** + * Send an emote to runechat for all (listening) users in the vicinity. + * + * * user - The user of the emote. + * * text - The text of the emote. + */ +/datum/emote/proc/runechat_emote(mob/user, text) + var/runechat_text = text + if(length(text) > 100) + runechat_text = "[copytext(text, 1, 101)]..." + var/list/can_see = get_mobs_in_view(1, user) //Allows silicon & mmi mobs carried around to see the emotes of the person carrying them around. + can_see |= viewers(user, null) + for(var/mob/O in can_see) + if(O.status_flags & PASSEMOTES) + for(var/obj/item/holder/H in O.contents) + H.show_message(text, EMOTE_VISIBLE) + + for(var/mob/living/M in O.contents) + M.show_message(text, EMOTE_VISIBLE) + + if(O.client?.prefs.toggles2 & PREFTOGGLE_2_RUNECHAT) + O.create_chat_message(user, runechat_text, symbol = RUNECHAT_SYMBOL_EMOTE) + +/** + * Check whether or not an emote can be used due to a cooldown. + * This applies to per-emote cooldowns, preventing individual emotes from being used (intentionally) too frequently. + * This also checks audio cooldowns, so that intentional uses of audio emotes across the mob are time-constrained. + * + * Arguments: + * * user - Person that is trying to send the emote. + * * intentional - Bool that says whether the emote was forced (FALSE) or not (TRUE). + * + * Returns FALSE if the cooldown is not over, TRUE if the cooldown is over. + */ +/datum/emote/proc/check_cooldown(mob/user, intentional) + if(!intentional) + return TRUE + // if our emote would play sound but another audio emote is on cooldown, prevent this emote from being used. + // Note that this only applies to intentional emotes + if(get_sound(user) && should_play_sound(user, intentional) && !user.can_use_audio_emote()) + return FALSE + var/cooldown_in_use + if(!isnull(user.emote_cooldown_override)) + // if the user has a a cooldown override in place, apply that instead. + cooldown_in_use = user.emote_cooldown_override + else + cooldown_in_use = cooldown + // Check cooldown on a per-emote basis. + if(user.emotes_used && user.emotes_used[src] + cooldown_in_use > world.time) + return FALSE + if(!user.emotes_used) + user.emotes_used = list() + user.emotes_used[src] = world.time + return TRUE + +/** + * To get the sound that the emote plays, for special sound interactions depending on the mob. + * + * Arguments: + * * user - Person that is trying to send the emote. + * + * Returns the sound that will be made while sending the emote. + */ +/datum/emote/proc/get_sound(mob/living/user) + return sound //by default just return this var. + +/** + * Get the volume of the audio emote to play. + * + * Override this if you want to dynamically change the volume of an emote. + * + * Arguments: + * * user - Person that is trying to send the emote. + * + * Returns the volume level for an emote's audio component. + */ +/datum/emote/proc/get_volume(mob/living/user) + return volume + +/** + * Replace pronouns in the inputed string with the user's proper pronouns. + * + * Specifically replaces they/them/their pronouns with the user's pronouns, as well as %s (like theirs) + * + * Arguments: + * * user - Person that is trying to send the emote. + * * msg - The string to modify. + * + * Returns the modified msg string. + */ +/datum/emote/proc/replace_pronoun(mob/user, msg) + if(findtext(msg, "their")) + msg = replacetext(msg, "their", user.p_their()) + if(findtext(msg, "them")) + msg = replacetext(msg, "them", user.p_them()) + if(findtext(msg, "they")) + msg = replacetext(msg, "they", user.p_they()) + if(findtext(msg, "%s")) + msg = replacetext(msg, "%s", user.p_s()) + return msg + +/** + * Selects the message type to override the message with. + * + * Arguments: + * * user - Person that is trying to send the emote. + * * msg - The string to modify. + * * intentional - Bool that says whether the emote was forced (FALSE) or not (TRUE). + * + * Returns the new message, or msg directly, if no change was needed. + */ +/datum/emote/proc/select_message_type(mob/user, msg, intentional) + . = msg + if(user.mind && user.mind.miming && message_mime) + . = message_mime + if(isalienadult(user) && message_alien) + . = message_alien + else if(islarva(user) && message_larva) + . = message_larva + else if(issilicon(user) && message_robot) + . = message_robot + else if(isAI(user) && message_AI) + . = message_AI + else if(ismonkeybasic(user) && message_monkey) + . = message_monkey + else if(isanimal(user) && message_simple) + . = message_simple + else if(isobserver(user) && message_observer) + . = message_observer + +/** + * Replaces the %t in the message in message_param by params. + * + * The behavior of this proc is particularly dependent on `target_behavior` and `emote_target_type`. + * If target_behavior is EMOTE_TARGET_BHVR_RAW, we ignore any sort of target searching. + * Otherwise, we try to find a target in view to call this emote on based on emote_target_type. + * + * + * If you want to call something on the target object itself while it's still in scope, override act_on_target(). + * + * + * Arguments: + * * user - Person that is trying to send the emote. + * * params - Parameters added after the emote. + * * substitution_str - String to substitute the target into. + * * base_message - If passed, the original message before any sort of modification occurred. Useful when dealing with non-standard message types. + * + * Returns the modified string, or null if the given parameter is invalid. May also return EMOTE_ACT_STOP_EXECUTION if acting on the target should stop emote execution. + */ +/datum/emote/proc/select_param(mob/user, params, substitution, base_message) + + if(target_behavior == EMOTE_TARGET_BHVR_RAW) + return replacetext(substitution, "%t", params) + + if(target_behavior == EMOTE_TARGET_BHVR_NUM) + if(!isnum(text2num(params))) + return null + act_on_target(user, text2num(params)) + return replacetext(substitution, "%t", params) + + var/full_target = find_target(user, params, emote_target_type) + if(full_target) + // If we find an actual target obj/item/whatever, see if we'd want to perform some action on it and jump out + // Fire off a signal first to see if our interaction should be stopped for some reason + if(!(SEND_SIGNAL(user, COMSIG_MOB_EMOTE_AT, full_target, key) & COMPONENT_BLOCK_EMOTE_ACTION)) + if(act_on_target(user, full_target) == EMOTE_ACT_STOP_EXECUTION) + return EMOTE_ACT_STOP_EXECUTION + return replacetext(substitution, "%t", full_target) + + // no target found, contingency plans + switch(target_behavior) + if(EMOTE_TARGET_BHVR_MUST_MATCH) + return null + if(EMOTE_TARGET_BHVR_DEFAULT_TO_BASE) + return base_message + if(EMOTE_TARGET_BHVR_USE_PARAMS_ANYWAY) + return replacetext(substitution, "%t", params) + + CRASH("Emote tried to select_param with invalid target behavior.") + +/** + * Perform an action on the target of an emote, if one was found. + * + * This gets called in select_param if a valid object target was found, and should let you interact with the + * object being targeted while it's still in scope. + * + * * user - Person who is triggering the emote. + * * Target - The target of the emote itself. + */ +/datum/emote/proc/act_on_target(mob/user, target) + return + +/** + * Check to see if the user is allowed to run the emote. + * + * + * Arguments: + * * user - Person that is trying to send the emote. + * * status_check - Bool that says whether we should check their stat or not. + * * intentional - Bool that says whether the emote was forced (FALSE) or not (TRUE). + * + * Returns a bool about whether or not the user can run the emote. + */ +/datum/emote/proc/can_run_emote(mob/user, status_check = TRUE, intentional = FALSE) + . = TRUE + if(!is_type_in_typecache(user, mob_type_allowed_typecache)) + return FALSE + if(is_type_in_typecache(user, mob_type_blacklist_typecache)) + return FALSE + + if(ishuman(user)) + var/mob/living/carbon/human/H = user + if(species_type_whitelist_typecache && H.dna && !is_type_in_typecache(H.dna.species, species_type_whitelist_typecache)) + return FALSE + + if(intentional && only_unintentional) + return FALSE + + if(check_mute(user.client?.ckey, MUTE_EMOTE)) + to_chat(src, "You cannot send emotes (muted).") + return FALSE + + if(status_check && !is_type_in_typecache(user, mob_type_ignore_stat_typecache)) + var/intentional_stat_check = (intentional && (user.stat <= stat_allowed && (max_stat_allowed == DEFAULT_MAX_STAT_ALLOWED || user.stat >= max_stat_allowed))) + var/unintentional_stat_check = (!intentional && (user.stat <= unintentional_stat_allowed && (max_unintentional_stat_allowed == DEFAULT_MAX_STAT_ALLOWED || user.stat >= max_unintentional_stat_allowed))) + if(!intentional_stat_check && !unintentional_stat_check) + var/stat = stat_to_text(user.stat) + if(!intentional) + return FALSE + + if(stat) + to_chat(user, "You cannot [key] while [stat]!") + return FALSE + if(HAS_TRAIT(src, TRAIT_FAKEDEATH)) + // Don't let people blow their cover by mistake + return FALSE + if(hands_use_check && !user.can_use_hands() && (iscarbon(user))) + if(!intentional) + return FALSE + to_chat(user, "You cannot use your hands to [key] right now!") + return FALSE + + if(isliving(user)) + var/mob/living/sender = user + if(HAS_TRAIT(sender, TRAIT_EMOTE_MUTE) && intentional) + return FALSE + else + // deadchat handling + if(check_mute(user.client?.ckey, MUTE_DEADCHAT)) + to_chat(src, "You cannot send deadchat emotes (muted).") + return FALSE + if(!(user.client?.prefs.toggles & PREFTOGGLE_CHAT_DEAD)) + to_chat(src, "You have deadchat muted.") + return FALSE + if(!check_rights(R_ADMIN, FALSE, user)) + if(!GLOB.dsay_enabled) + to_chat(src, "Deadchat is globally muted") + return FALSE + +/** + * Find a target for the emote based on the message parameter fragment passed in. + * + * * user - The user looking for a target. + * * fragment - The mesage parameter or fragment of text they're using to try to find a target. + * * emote_target_type - Define denoting the type of target to use when searching. + * + * Returns a matched target, or null if a specific match couldn't be made. + */ +/datum/emote/proc/find_target(mob/user, fragment, emote_target_type) + var/target = null + + fragment = lowertext(fragment) + + if(emote_target_type & EMOTE_TARGET_MOB) + for(var/mob/living/M in view(user.client)) + if(findtext(lowertext(M.name), fragment)) + target = M + break + + if(!target && emote_target_type & EMOTE_TARGET_OBJ) + for(var/obj/thing in view(user.client)) + if(findtext(lowertext(thing.name), fragment)) + target = thing + break + + return target + +/** + * Return whether a user should be able to vocalize emotes or not, due to a mask or inability to speak. + * If this returns false, any mouth emotes will be replaced with muzzled noises. + */ +/datum/emote/proc/can_vocalize_emotes(mob/user) + if(user.mind?.miming) + // mimes get special treatment; though they can't really "vocalize" we don't want to replace their message. + return TRUE + if(!muzzle_ignore && !user.can_speak()) + return FALSE + + return TRUE + + +/** + * Check to see if the user should play a sound when performing the emote. + * + * Arguments: + * * user - Person that is doing the emote. + * * intentional - Bool that says whether the emote was forced (FALSE) or not (TRUE). + * + * Returns a bool about whether or not the user should play a sound when performing the emote. + */ +/datum/emote/proc/should_play_sound(mob/user, intentional = FALSE) + if(only_forced_audio && intentional) + return FALSE + if((emote_type & EMOTE_MOUTH) && !can_vocalize_emotes(user)) + return FALSE + return TRUE + +/datum/emote/proc/remove_ending_punctuation(msg) + var/static/list/end_punctuation = list(".", "?", "!") + if(copytext(msg, -1) in end_punctuation) + msg = copytext(msg, 1, -1) + return msg + +/** +* Allows the intrepid coder to send a basic emote +* Takes text as input, sends it out to those who need to know after some light parsing +* If you need something more complex, make it into a datum emote +* Arguments: +* * text - The text to send out +* +* Returns TRUE if it was able to run the emote, FALSE otherwise. +*/ +/mob/proc/manual_emote(text) //Just override the song and dance + . = TRUE + if(stat != CONSCIOUS) + return FALSE + + if(!text) + CRASH("Someone passed nothing to manual_emote(), fix it") + + + log_emote(text, src) + + var/ghost_text = "[src] [text]" + + var/origin_turf = get_turf(src) + if(client) + for(var/mob/ghost as anything in GLOB.dead_mob_list) + if(!ghost.client) + continue + if(ghost.client.prefs.toggles & PREFTOGGLE_CHAT_GHOSTSIGHT && !(ghost in viewers(origin_turf, null))) + ghost.show_message("[ghost_follow_link(src, ghost)] [ghost_text]") + + visible_message(text) + + +#undef DEFAULT_MAX_STAT_ALLOWED diff --git a/code/datums/spell.dm b/code/datums/spell.dm index decf6e9221c..b78b6effc51 100644 --- a/code/datums/spell.dm +++ b/code/datums/spell.dm @@ -206,7 +206,7 @@ GLOBAL_LIST_INIT(spells, typesof(/obj/effect/proc_holder/spell)) switch(invocation_type) if("shout") if(!user.IsVocal()) - user.custom_emote(1, "makes frantic gestures!") + user.custom_emote(EMOTE_VISIBLE, "makes frantic gestures!") else if(prob(50))//Auto-mute? Fuck that noise user.say(invocation) diff --git a/code/datums/status_effects/neutral.dm b/code/datums/status_effects/neutral.dm index 44ee85129bf..93563d3ca44 100644 --- a/code/datums/status_effects/neutral.dm +++ b/code/datums/status_effects/neutral.dm @@ -40,11 +40,25 @@ /datum/status_effect/high_five id = "high_five" - duration = 40 + duration = 5 SECONDS alert_type = null -/datum/status_effect/high_five/on_remove() - owner.visible_message("[owner] was left hanging....") +/datum/status_effect/high_five/proc/get_missed_message() + var/list/missed_highfive_messages = list( + "it looks like [owner.p_they()] [owner.p_were()] left hanging...", + "seeming to wave at nobody in particular.", + "moving [owner.p_their()] hand directly to [owner.p_their()] forehead in shame.", + "fully committing and high-fiving empty space.", + "high-fiving [owner.p_their()] other hand shamefully before wiping away a tear.", + "going for a handshake, then a fistbump, before pulling [owner.p_their()] hand back...? What [owner.p_are()] [owner.p_they()] doing?" + ) + + return pick(missed_highfive_messages) + +/datum/status_effect/high_five/on_timeout() + // show some emotionally damaging failure messages + // high risk, high reward + owner.visible_message("[owner] awkwardly lowers [owner.p_their()] hand, [get_missed_message()]") /datum/status_effect/charging id = "charging" diff --git a/code/datums/status_effects/status_effect.dm b/code/datums/status_effects/status_effect.dm index b9077bcb876..17c3be0fcaa 100644 --- a/code/datums/status_effects/status_effect.dm +++ b/code/datums/status_effects/status_effect.dm @@ -55,12 +55,14 @@ tick() tick_interval = world.time + initial(tick_interval) if(duration != -1 && duration < world.time) + on_timeout() qdel(src) /datum/status_effect/proc/on_apply() //Called whenever the buff is applied; returning FALSE will cause it to autoremove itself. return TRUE /datum/status_effect/proc/tick() //Called every tick. /datum/status_effect/proc/on_remove() //Called whenever the buff expires or is removed; do note that at the point this is called, it is out of the owner's status_effects but owner is not yet null +/datum/status_effect/proc/on_timeout() // Called specifically whenever the status effect expires. /datum/status_effect/proc/be_replaced() //Called instead of on_remove when a status effect is replaced by itself or when a status effect with on_remove_on_mob_delete = FALSE has its mob deleted owner.clear_alert(id) LAZYREMOVE(owner.status_effects, src) @@ -127,7 +129,7 @@ if(status_effects) var/datum/status_effect/S1 = effect for(var/datum/status_effect/S in status_effects) - if(initial(S1.id) == S.id && S.before_remove(arguments)) + if(initial(S1.id) == S.id && S.before_remove(arglist(arguments))) qdel(S) . = TRUE diff --git a/code/game/gamemodes/blob/overmind.dm b/code/game/gamemodes/blob/overmind.dm index b3c19cd2896..35fe730c6f7 100644 --- a/code/game/gamemodes/blob/overmind.dm +++ b/code/game/gamemodes/blob/overmind.dm @@ -101,9 +101,6 @@ if(isovermind(M) || isobserver(M) || istype((M), /mob/living/simple_animal/hostile/blob/blobbernaut)) M.show_message(rendered, 2) -/mob/camera/blob/emote(act, m_type = 1, message = null, force) - return - /mob/camera/blob/blob_act(obj/structure/blob/B) return diff --git a/code/game/gamemodes/miniantags/borer/borer.dm b/code/game/gamemodes/miniantags/borer/borer.dm index 3592daec83c..e015b78b734 100644 --- a/code/game/gamemodes/miniantags/borer/borer.dm +++ b/code/game/gamemodes/miniantags/borer/borer.dm @@ -32,9 +32,6 @@ return FALSE return B.host.say_understands(other, speaking) -/mob/living/captive_brain/emote(act, m_type = 1, message = null, force) - return - /mob/living/captive_brain/resist() var/mob/living/simple_animal/borer/B = loc diff --git a/code/game/gamemodes/miniantags/revenant/revenant.dm b/code/game/gamemodes/miniantags/revenant/revenant.dm index 4cb0b93cbdb..02a4d5c64ae 100644 --- a/code/game/gamemodes/miniantags/revenant/revenant.dm +++ b/code/game/gamemodes/miniantags/revenant/revenant.dm @@ -115,11 +115,14 @@ if(!message) return log_say(message, src) + if(copytext(message, 1, 2) == "*") + return emote(copytext(message, 2), intentional = TRUE) + + var/rendered for(var/mob/M in GLOB.mob_list) - var/rendered = "[src] [(isobserver(M) ? ("([ghost_follow_link(src, ghost=M)])") : "")] says, \"[message]\"" + rendered = "[src] [(isobserver(M) ? ("([ghost_follow_link(src, ghost=M)])") : "")] says, \"[message]\"" if(istype(M, /mob/living/simple_animal/revenant) || isobserver(M)) to_chat(M, rendered) - return /mob/living/simple_animal/revenant/Stat() ..() diff --git a/code/game/mecha/mecha.dm b/code/game/mecha/mecha.dm index 67cb8eecb9c..28e5576ce48 100644 --- a/code/game/mecha/mecha.dm +++ b/code/game/mecha/mecha.dm @@ -540,7 +540,7 @@ /obj/mecha/attack_animal(mob/living/simple_animal/user) log_message("Attack by simple animal. Attacker - [user].") if(!user.melee_damage_upper && !user.obj_damage) - user.custom_emote(1, "[user.friendly] [src].") + user.custom_emote(EMOTE_VISIBLE, "[user.friendly] [src].") return FALSE else var/play_soundeffect = 1 diff --git a/code/game/objects/items/weapons/implants/implant.dm b/code/game/objects/items/weapons/implants/implant.dm index b0c09f4f68c..0bcba0d3fa4 100644 --- a/code/game/objects/items/weapons/implants/implant.dm +++ b/code/game/objects/items/weapons/implants/implant.dm @@ -1,3 +1,28 @@ +/// If used, an implant will trigger when an emote is intentionally used. +#define IMPLANT_EMOTE_TRIGGER_INTENTIONAL (1<<0) +/// If used, an implant will trigger when an emote is forced/unintentionally used. +#define IMPLANT_EMOTE_TRIGGER_UNINTENTIONAL (1<<1) +/// If used, an implant will always trigger when the user makes an emote. +#define IMPLANT_EMOTE_TRIGGER_ALWAYS (IMPLANT_EMOTE_TRIGGER_UNINTENTIONAL | IMPLANT_EMOTE_TRIGGER_INTENTIONAL) +/// If used, an implant will trigger on the user's first death. +#define IMPLANT_TRIGGER_DEATH_ONCE (1<<2) +/// If used, an implant will trigger any time a user dies. +#define IMPLANT_TRIGGER_DEATH_ANY (1<<3) +/// If used, an implant will NOT trigger on death when a user is gibbed. +#define IMPLANT_TRIGGER_NOT_WHEN_GIBBED (1<<4) + +// Defines related to the way that the implant is activated. This is the value for implant.activated +/// The implant is passively active (like a mindshield) +#define IMPLANT_ACTIVATED_PASSIVE 0 +/// The implant is activated manually by a trigger +#define IMPLANT_ACTIVATED_ACTIVE 1 + +/** + * # Implants + * + * Code for implants that can be inserted into a person and have some sort of passive or triggered action. + * + */ /obj/item/implant name = "implant" icon = 'icons/obj/implants.dmi' @@ -5,31 +30,121 @@ origin_tech = "materials=2;biotech=3;programming=2" actions_types = list(/datum/action/item_action/hands_free/activate) - var/activated = 1 //1 for implant types that can be activated, 0 for ones that are "always on" like mindshield implants + /// How the implant is activated. + var/activated = IMPLANT_ACTIVATED_ACTIVE + /// Whether the implant is implanted. Null if it's never been inserted, TRUE if it's currently inside someone, or FALSE if it's been removed. var/implanted = null + /// Who the implant is inside of. var/mob/living/imp_in = null item_color = "b" - var/allow_multiple = 0 + /// Whether multiple implants of this same type can be inserted into someone. + var/allow_multiple = FALSE + /// Amount of times that the implant can be triggered by the user. If the implant can't be used, it can't be inserted. var/uses = -1 - flags = DROPDEL + flags = DROPDEL // By default, don't let implants be harvestable. + /// List of emote keys that activate this implant when used. + var/list/trigger_emotes + /// What type of action will trigger this emote. Bitfield of IMPLANT_EMOTE_* defines. + var/trigger_causes + /// Whether this implant has already triggered on death or not, to prevent it firing multiple times. + var/has_triggered_on_death = FALSE -/obj/item/implant/proc/trigger(emote, mob/source, force) +/obj/item/implant/proc/unregister_emotes() + if(imp_in && LAZYLEN(trigger_emotes)) + for(var/emote in trigger_emotes) + UnregisterSignal(imp_in, COMSIG_MOB_EMOTED(emote)) + +/** + * Set the emote that will trigger the implant. + * * user - User who is trying to associate the implant to themselves. + * * emote_key - Key of the emote that should trigger the implant. + * * on_implant - Whether this proc is being called during the implantation of the implant. + * * silent - If true, the user won't get any to_chat messages if an implantation fails. + */ +/obj/item/implant/proc/set_trigger(mob/user, emote_key, on_implant = FALSE, silent = TRUE) + if(imp_in != user) + return FALSE + + if(!emote_key) + return FALSE + + if(LAZYIN(trigger_emotes, emote_key) && !on_implant) + if(!silent) + to_chat(user, " You've already registered [emote_key]!") + return FALSE + + if(emote_key == "me" || emote_key == "custom") + if(!silent) + to_chat(user, " You can't trigger [src] with a custom emote.") + return FALSE + + if(!(emote_key in user.usable_emote_keys(trigger_causes & IMPLANT_EMOTE_TRIGGER_INTENTIONAL))) + if(!silent) + to_chat(user, " You can't trigger [src] with that emote! Try *help to see emotes you can use.") + return FALSE + + if(!(emote_key in user.usable_emote_keys(trigger_causes & IMPLANT_EMOTE_TRIGGER_UNINTENTIONAL))) + CRASH("User was given an implant for an unintentional emote that they can't use.") + + LAZYADD(trigger_emotes, emote_key) + RegisterSignal(user, COMSIG_MOB_EMOTED(emote_key), .proc/on_emote) + +/obj/item/implant/proc/on_emote(mob/living/user, datum/emote/fired_emote, key, emote_type, message, intentional) + SIGNAL_HANDLER + + if(!implanted || !imp_in) + return + + if(!(intentional && (trigger_causes & IMPLANT_EMOTE_TRIGGER_INTENTIONAL)) && !(!intentional && (trigger_causes & IMPLANT_EMOTE_TRIGGER_UNINTENTIONAL))) + return + + add_attack_logs(user, user, "[intentional ? "intentionally" : "unintentionally"] [src] was [intentional ? "intentionally" : "unintentionally"] triggered with the emote [fired_emote].") + emote_trigger(key, user, intentional) + +/obj/item/implant/proc/on_death(mob/source, gibbed) + SIGNAL_HANDLER + + if(!implanted || !imp_in) + return + + if(gibbed && (trigger_causes & IMPLANT_TRIGGER_NOT_WHEN_GIBBED)) + return + + // This should help avoid infinite recursion for things like dust that call death() + if(has_triggered_on_death && (trigger_causes & IMPLANT_TRIGGER_DEATH_ONCE)) + return + + has_triggered_on_death = TRUE + + add_attack_logs(source, source, "had their [src] implant triggered on [gibbed ? "gib" : "death"].") + death_trigger(source, gibbed) + +/obj/item/implant/proc/emote_trigger(emote, mob/source, force) return -/obj/item/implant/proc/activate() +/obj/item/implant/proc/death_trigger(mob/source, gibbed) + return + +/obj/item/implant/proc/activate(cause) return /obj/item/implant/ui_action_click() activate("action_button") - -//What does the implant do upon injection? -//return 1 if the implant injects -//return -1 if the implant fails to inject -//return 0 if there is no room for implant +/** + * Try to implant ourselves into a mob. + * + * * source - The person the implant is being administered to. + * * user - The person who is doing the implanting. + * + * Returns + * 1 if the implant injects successfully + * -1 if the implant fails to inject + * 0 if there's no room for the implant. + */ /obj/item/implant/proc/implant(mob/source, mob/user) - var/obj/item/implant/imp_e = locate(src.type) in source + var/obj/item/implant/imp_e = locate(type) in source if(!allow_multiple && imp_e && imp_e != src) if(imp_e.uses < initial(imp_e.uses)*2) if(uses == -1) @@ -42,13 +157,23 @@ return 0 - src.loc = source + loc = source imp_in = source - implanted = 1 + implanted = TRUE + if(trigger_emotes) + if(!(trigger_causes & IMPLANT_EMOTE_TRIGGER_INTENTIONAL | IMPLANT_EMOTE_TRIGGER_UNINTENTIONAL)) + CRASH("Implant [src] has trigger emotes defined but no trigger cause with which to use them!") + if(!activated && (trigger_causes & IMPLANT_EMOTE_TRIGGER_INTENTIONAL)) + CRASH("Implant [src] has intentional emote triggers on a passive implant") + // If you can't activate the implant manually, you shouldn't be able to deliberately activate it with an emote + for(var/emote in trigger_emotes) + set_trigger(source, emote, TRUE, TRUE) if(activated) for(var/X in actions) var/datum/action/A = X A.Grant(source) + if(trigger_causes & (IMPLANT_TRIGGER_DEATH_ONCE | IMPLANT_TRIGGER_DEATH_ANY)) + RegisterSignal(source, COMSIG_MOB_DEATH, .proc/on_death) if(ishuman(source)) var/mob/living/carbon/human/H = source H.sec_hud_set_implants() @@ -58,10 +183,14 @@ return 1 +/** + * Clean up when an implant is removed. + * * source - the user who the implant was removed from. + */ /obj/item/implant/proc/removed(mob/source) - src.loc = null + loc = null imp_in = null - implanted = 0 + implanted = FALSE for(var/X in actions) var/datum/action/A = X @@ -71,7 +200,12 @@ var/mob/living/carbon/human/H = source H.sec_hud_set_implants() - return 1 + if(trigger_causes & (IMPLANT_TRIGGER_DEATH_ONCE | IMPLANT_TRIGGER_DEATH_ANY)) + UnregisterSignal(source, COMSIG_MOB_DEATH) + + unregister_emotes() + + return TRUE /obj/item/implant/Destroy() if(imp_in) @@ -83,5 +217,5 @@ return "No information available" /obj/item/implant/dropped(mob/user) - . = 1 + . = TRUE ..() diff --git a/code/game/objects/items/weapons/implants/implant_abductor.dm b/code/game/objects/items/weapons/implants/implant_abductor.dm index be448130aae..da3d3b491cf 100644 --- a/code/game/objects/items/weapons/implants/implant_abductor.dm +++ b/code/game/objects/items/weapons/implants/implant_abductor.dm @@ -3,7 +3,7 @@ desc = "Returns you to the mothership." icon = 'icons/obj/abductor.dmi' icon_state = "implant" - activated = 1 + activated = IMPLANT_ACTIVATED_ACTIVE origin_tech = "materials=2;biotech=7;magnets=4;bluespace=4;abductor=5" var/obj/machinery/abductor/pad/home var/cooldown = 30 diff --git a/code/game/objects/items/weapons/implants/implant_chem.dm b/code/game/objects/items/weapons/implants/implant_chem.dm index f1c4dd4126b..a75b3c400f0 100644 --- a/code/game/objects/items/weapons/implants/implant_chem.dm +++ b/code/game/objects/items/weapons/implants/implant_chem.dm @@ -4,6 +4,7 @@ icon_state = "reagents" origin_tech = "materials=3;biotech=4" container_type = OPENCONTAINER + trigger_causes = IMPLANT_TRIGGER_DEATH_ANY /obj/item/implant/chem/get_data() var/dat = {"Implant Specifications:
@@ -30,12 +31,8 @@ GLOB.tracked_implants -= src return ..() - - - -/obj/item/implant/chem/trigger(emote, mob/source, force) - if(force && emote == "deathgasp") - activate(reagents.total_volume) +/obj/item/implant/chem/death_trigger(mob/victim, gibbed) + activate(reagents.total_volume) /obj/item/implant/chem/activate(cause) if(!cause || !imp_in) return 0 diff --git a/code/game/objects/items/weapons/implants/implant_clown.dm b/code/game/objects/items/weapons/implants/implant_clown.dm index bb5170fced6..d250c094301 100644 --- a/code/game/objects/items/weapons/implants/implant_clown.dm +++ b/code/game/objects/items/weapons/implants/implant_clown.dm @@ -1,6 +1,9 @@ /obj/item/implant/sad_trombone name = "sad trombone implant" activated = FALSE + trigger_emotes = list("deathgasp") + // If something forces the clown to fake death, it's pretty funny to still see the sad trombone played + trigger_causes = IMPLANT_EMOTE_TRIGGER_UNINTENTIONAL | IMPLANT_TRIGGER_DEATH_ANY /obj/item/implant/sad_trombone/get_data() var/dat = {"Implant Specifications:
@@ -9,9 +12,15 @@ "} return dat -/obj/item/implant/sad_trombone/trigger(emote, mob/source, force) - if(force && emote == "deathgasp") - playsound(loc, 'sound/misc/sadtrombone.ogg', 50, FALSE) +/obj/item/implant/sad_trombone/emote_trigger(emote, mob/source, force) + activate(emote) + +/obj/item/implant/sad_trombone/death_trigger(mob/user, gibbed) + activate(gibbed) + + +/obj/item/implant/sad_trombone/activate() + playsound(loc, 'sound/misc/sadtrombone.ogg', 50, FALSE) /obj/item/implanter/sad_trombone name = "implanter (sad trombone)" diff --git a/code/game/objects/items/weapons/implants/implant_death_alarm.dm b/code/game/objects/items/weapons/implants/implant_death_alarm.dm index ee9390ed2a7..d1f4fd3dd3f 100644 --- a/code/game/objects/items/weapons/implants/implant_death_alarm.dm +++ b/code/game/objects/items/weapons/implants/implant_death_alarm.dm @@ -2,8 +2,9 @@ name = "death alarm implant" desc = "An alarm which monitors host vital signs and transmits a radio message upon death." var/mobname = "Will Robinson" - activated = 0 + activated = IMPLANT_ACTIVATED_PASSIVE var/static/list/stealth_areas = typecacheof(list(/area/syndicate_mothership, /area/shuttle/syndicate_elite)) + trigger_causes = IMPLANT_TRIGGER_DEATH_ANY /obj/item/implant/death_alarm/get_data() var/dat = {"Implant Specifications:
@@ -17,9 +18,10 @@ Integrity: Implant will occasionally be degraded by the body's immune system and thus will occasionally malfunction."} return dat -/obj/item/implant/death_alarm/Destroy() - UnregisterSignal(imp_in, COMSIG_MOB_DEATH) - return ..() +/obj/item/implant/death_alarm/implant(mob/target) + . = ..() + if(.) + mobname = target.real_name /obj/item/implant/death_alarm/activate(cause) // Death signal sends name followed by the gibbed / not gibbed check var/mob/M = imp_in @@ -48,14 +50,7 @@ /obj/item/implant/death_alarm/emp_act(severity) //for some reason alarms stop going off in case they are emp'd, even without this activate("emp") //let's shout that this dude is dead -/obj/item/implant/death_alarm/implant(mob/target) - if(..()) - mobname = target.real_name - RegisterSignal(target, COMSIG_MOB_DEATH, /obj/item/implant/death_alarm.proc/check_gibbed_activate) - return 1 - return 0 - -/obj/item/implant/death_alarm/proc/check_gibbed_activate(datum/source, gibbed) +/obj/item/implant/death_alarm/death_trigger(mob/source, gibbed) if(gibbed) activate("gib") else diff --git a/code/game/objects/items/weapons/implants/implant_explosive.dm b/code/game/objects/items/weapons/implants/implant_explosive.dm index d6bf2e83d4a..505d542a834 100644 --- a/code/game/objects/items/weapons/implants/implant_explosive.dm +++ b/code/game/objects/items/weapons/implants/implant_explosive.dm @@ -9,6 +9,7 @@ var/medium = 0.8 var/heavy = 0.4 var/delay = 7 + trigger_causes = IMPLANT_TRIGGER_DEATH_ONCE // Not surviving that /obj/item/implant/explosive/get_data() var/dat = {"Implant Specifications:
@@ -22,9 +23,8 @@ "} return dat -/obj/item/implant/explosive/trigger(emote, mob/source, force) - if(force && emote == "deathgasp") - activate("death") +/obj/item/implant/explosive/death_trigger(mob/source, gibbed) + activate("death") /obj/item/implant/explosive/activate(cause) if(!cause || !imp_in) @@ -142,6 +142,7 @@ icon = 'icons/effects/blood.dmi' icon_state = "remains" actions_types = list(/datum/action/item_action/hands_free/activate/always) + trigger_causes = IMPLANT_TRIGGER_DEATH_ONCE | IMPLANT_TRIGGER_NOT_WHEN_GIBBED /obj/item/implant/dust/get_data() var/dat = {"Implant Specifications:
@@ -155,9 +156,8 @@ "} return dat -/obj/item/implant/dust/trigger(emote, mob/source, force) - if(force && emote == "deathgasp") - activate("death") +/obj/item/implant/dust/death_trigger(emote, mob/source, force) + activate("death") /obj/item/implant/dust/activate(cause) if(!cause || !imp_in || cause == "emp") diff --git a/code/game/objects/items/weapons/implants/implant_krav_maga.dm b/code/game/objects/items/weapons/implants/implant_krav_maga.dm index 9c33f43950a..7890f234676 100644 --- a/code/game/objects/items/weapons/implants/implant_krav_maga.dm +++ b/code/game/objects/items/weapons/implants/implant_krav_maga.dm @@ -3,7 +3,7 @@ desc = "Teaches you the arts of Krav Maga in 5 short instructional videos beamed directly into your eyeballs." icon = 'icons/obj/wizard.dmi' icon_state ="scroll2" - activated = 1 + activated = IMPLANT_ACTIVATED_ACTIVE origin_tech = "materials=2;biotech=4;combat=5;syndicate=4" var/datum/martial_art/krav_maga/style = new diff --git a/code/game/objects/items/weapons/implants/implant_mindshield.dm b/code/game/objects/items/weapons/implants/implant_mindshield.dm index f1c61dae3d3..312f8f78bac 100644 --- a/code/game/objects/items/weapons/implants/implant_mindshield.dm +++ b/code/game/objects/items/weapons/implants/implant_mindshield.dm @@ -2,7 +2,7 @@ name = "mindshield implant" desc = "Stops people messing with your mind." origin_tech = "materials=2;biotech=4;programming=4" - activated = 0 + activated = IMPLANT_ACTIVATED_PASSIVE /obj/item/implant/mindshield/get_data() var/dat = {"Implant Specifications:
diff --git a/code/game/objects/items/weapons/implants/implant_misc.dm b/code/game/objects/items/weapons/implants/implant_misc.dm index 675b453f92d..67a8cde10e8 100644 --- a/code/game/objects/items/weapons/implants/implant_misc.dm +++ b/code/game/objects/items/weapons/implants/implant_misc.dm @@ -3,7 +3,7 @@ desc = "Lets you shoot your guns" icon_state = "auth" origin_tech = "magnets=2;programming=7;biotech=5;syndicate=5" - activated = 0 + activated = IMPLANT_ACTIVATED_PASSIVE /obj/item/implant/weapons_auth/get_data() var/dat = {"Implant Specifications:
diff --git a/code/game/objects/items/weapons/implants/implant_track.dm b/code/game/objects/items/weapons/implants/implant_track.dm index ba24101f552..f5b9d0a4747 100644 --- a/code/game/objects/items/weapons/implants/implant_track.dm +++ b/code/game/objects/items/weapons/implants/implant_track.dm @@ -1,7 +1,7 @@ /obj/item/implant/tracking name = "tracking implant" desc = "Track with this." - activated = 0 + activated = IMPLANT_ACTIVATED_PASSIVE origin_tech = "materials=2;magnets=2;programming=2;biotech=2" var/id = 1 diff --git a/code/game/objects/obj_defense.dm b/code/game/objects/obj_defense.dm index 7333c9f75b9..a61e302ddc5 100644 --- a/code/game/objects/obj_defense.dm +++ b/code/game/objects/obj_defense.dm @@ -105,7 +105,7 @@ /obj/attack_animal(mob/living/simple_animal/M) if((M.a_intent == INTENT_HELP && M.ckey) || (!M.melee_damage_upper && !M.obj_damage)) - M.custom_emote(1, "[M.friendly] [src].") + M.custom_emote(EMOTE_VISIBLE, "[M.friendly] [src].") return 0 else var/play_soundeffect = 1 diff --git a/code/modules/admin/admin.dm b/code/modules/admin/admin.dm index db6910a7bde..557e49e2e6b 100644 --- a/code/modules/admin/admin.dm +++ b/code/modules/admin/admin.dm @@ -141,6 +141,7 @@ GLOBAL_VAR_INIT(nologevent, 0) PRAY | ADMINHELP | DEADCHAT] + EMOTE] (toggle all) "} diff --git a/code/modules/admin/verbs/randomverbs.dm b/code/modules/admin/verbs/randomverbs.dm index 1fcc11f5865..489c6ecee76 100644 --- a/code/modules/admin/verbs/randomverbs.dm +++ b/code/modules/admin/verbs/randomverbs.dm @@ -222,13 +222,22 @@ var/mute_string switch(mute_type) - if(MUTE_IC) mute_string = "IC (say and emote)" - if(MUTE_OOC) mute_string = "OOC" - if(MUTE_PRAY) mute_string = "pray" - if(MUTE_ADMINHELP) mute_string = "adminhelp, admin PM and ASAY" - if(MUTE_DEADCHAT) mute_string = "deadchat and DSAY" - if(MUTE_ALL) mute_string = "everything" - else return + if(MUTE_IC) + mute_string = "IC (say and emote)" + if(MUTE_OOC) + mute_string = "OOC" + if(MUTE_PRAY) + mute_string = "pray" + if(MUTE_ADMINHELP) + mute_string = "adminhelp, admin PM and ASAY" + if(MUTE_DEADCHAT) + mute_string = "deadchat and DSAY" + if(MUTE_EMOTE) + mute_string = "emote" + if(MUTE_ALL) + mute_string = "everything" + else + return if(automute) muteunmute = "auto-muted" diff --git a/code/modules/awaymissions/exile.dm b/code/modules/awaymissions/exile.dm index 0b8a777237c..606dd850b41 100644 --- a/code/modules/awaymissions/exile.dm +++ b/code/modules/awaymissions/exile.dm @@ -5,7 +5,7 @@ name = "exile implant" desc = "Prevents you from returning from away missions" origin_tech = "materials=2;biotech=3;magnets=2;bluespace=3" - activated = 0 + activated = IMPLANT_ACTIVATED_PASSIVE /obj/item/implant/exile/get_data() var/dat = {"Implant Specifications:
diff --git a/code/modules/flufftext/Dreaming.dm b/code/modules/flufftext/Dreaming.dm index 4cdb2f88f07..b01abc9ebf9 100644 --- a/code/modules/flufftext/Dreaming.dm +++ b/code/modules/flufftext/Dreaming.dm @@ -43,8 +43,7 @@ nightmare() if(ishuman(src)) if(prob(10)) - custom_emote(1,"writhes in [p_their()] sleep.") - dir = pick(GLOB.cardinal) + emote("nightmare") /mob/living/carbon/proc/experience_dream(dream_image, isNightmare) dreaming-- diff --git a/code/modules/mob/dead/observer/say.dm b/code/modules/mob/dead/observer/say.dm index a7dc2c86a9f..850f2585e0f 100644 --- a/code/modules/mob/dead/observer/say.dm +++ b/code/modules/mob/dead/observer/say.dm @@ -6,28 +6,6 @@ return say_dead(message) - -/mob/dead/observer/emote(act, type, message, force) - message = sanitize(copytext(message, 1, MAX_MESSAGE_LEN)) - - if(!message) - return - - if(act != "me") - return - - log_ghostemote(message, src) - - if(src.client) - if(check_mute(client.ckey, MUTE_DEADCHAT)) - to_chat(src, "You cannot emote in deadchat (muted).") - return - - if(src.client.handle_spam_prevention(message, MUTE_DEADCHAT)) - return - - . = src.emote_dead(message) - /mob/dead/observer/handle_track(message, verb = "says", mob/speaker = null, speaker_name, atom/follow_target, hard_to_hear) return "[speaker_name] ([ghost_follow_link(follow_target, ghost=src)])" diff --git a/code/modules/mob/emote.dm b/code/modules/mob/emote.dm deleted file mode 100644 index ae6cd715cec..00000000000 --- a/code/modules/mob/emote.dm +++ /dev/null @@ -1,150 +0,0 @@ -#define EMOTE_COOLDOWN 20 //Time in deciseconds that the cooldown lasts - -//Emote Cooldown System (it's so simple!) -/mob/proc/handle_emote_CD(cooldown = EMOTE_COOLDOWN) - if(emote_cd == 3) //Spam those emotes - return FALSE - if(emote_cd == 2) // Cooldown emotes were disabled by an admin, prevent use - return TRUE - if(emote_cd == 1) // Already on CD, prevent use - return TRUE - - emote_cd = TRUE // Starting cooldown - spawn(cooldown) - if(emote_cd == 2) - return // Don't reset if cooldown emotes were disabled by an admin during the cooldown - emote_cd = FALSE // Cooldown complete, ready for more! - return FALSE // Proceed with emote - -//--FalseIncarnate - -/mob/proc/handle_emote_param(target, not_self, vicinity, return_mob) //Only returns not null if the target param is valid. - var/view_vicinity = vicinity ? vicinity : null //not_self means we'll only return if target is valid and not us - if(target) //vicinity is the distance passed to the view proc. - for(var/mob/A in view(view_vicinity, null)) //if set, return_mob will cause this proc to return the mob instead of just its name if the target is valid. - if(target == A.name && (!not_self || (not_self && target != name))) - if(return_mob) - return A - else - return target - -// All mobs should have custom emote, really.. -/mob/proc/custom_emote(m_type=EMOTE_VISUAL, message = null) - if(stat || !use_me && usr == src) - if(usr) - to_chat(usr, "You are unable to emote.") - return - var/muzzled = is_muzzled() - if(muzzled) - var/obj/item/clothing/mask/muzzle/M = wear_mask - if(m_type == EMOTE_SOUND && M.mute >= MUZZLE_MUTE_MUFFLE) - return //Not all muzzles block sound - if(m_type == EMOTE_SOUND && !can_speak()) - return - - var/input - if(!message) - input = sanitize(copytext(input(src,"Choose an emote to display.") as text|null,1,MAX_MESSAGE_LEN)) - else - input = message - if(input) - message = "[src] [input]" - else - return - - - if(message) - log_emote(message, src) - if(isliving(src)) //isliving because these are defined on the mob/living level not mob - var/mob/living/L = src - L.say_log += "EMOTE: [input]" //say log too so it is easier on admins instead of having to merge the two with timestamps etc - L.emote_log += input //emote only log if an admin wants to search just for emotes they don't have to sift through the say - create_log(EMOTE_LOG, input) // TODO after #13047: Include the channel - // Hearing gasp and such every five seconds is not good emotes were not global for a reason. - // Maybe some people are okay with that. - for(var/mob/M in GLOB.player_list) - if(!M.client) - continue //skip monkeys and leavers - - if(isnewplayer(M)) - continue - - if(findtext(message, " snores.")) //Because we have so many sleeping people. - break - - if(isobserver(M) && M.get_preference(PREFTOGGLE_CHAT_GHOSTSIGHT) && !(M in viewers(src, null)) && client) // The client check makes sure people with ghost sight don't get spammed by simple mobs emoting. - M.show_message(message) - - // Type 1 (Visual) emotes are sent to anyone in view of the item - if(m_type & EMOTE_VISUAL) - var/runechat_text = input - if(length(input) > 100) - runechat_text = "[copytext(input, 1, 101)]..." - var/list/can_see = get_mobs_in_view(1,src) //Allows silicon & mmi mobs carried around to see the emotes of the person carrying them around. - can_see |= viewers(src,null) - for(var/mob/O in can_see) - - if(O.status_flags & PASSEMOTES) - - for(var/obj/item/holder/H in O.contents) - H.show_message(message, m_type) - - for(var/mob/living/M in O.contents) - M.show_message(message, m_type) - - O.show_message(message, m_type) - if(O.client?.prefs.toggles2 & PREFTOGGLE_2_RUNECHAT) - O.create_chat_message(src, runechat_text, symbol = RUNECHAT_SYMBOL_EMOTE) - - // Type 2 (Audible) emotes are sent to anyone in hear range - // of the *LOCATION* -- this is important for pAIs to be heard - else if(m_type & EMOTE_SOUND) - for(var/mob/O in get_mobs_in_view(7,src)) - - if(O.status_flags & PASSEMOTES) - - for(var/obj/item/holder/H in O.contents) - H.show_message(message, m_type) - - for(var/mob/living/M in O.contents) - M.show_message(message, m_type) - - O.show_message(message, m_type) - -/mob/proc/emote_dead(message) - if(check_mute(client.ckey, MUTE_DEADCHAT)) - to_chat(src, "You cannot send deadchat emotes (muted).") - return - - if(!(client.prefs.toggles & PREFTOGGLE_CHAT_DEAD)) - to_chat(src, "You have deadchat muted.") - return - - if(!src.client.holder) - if(!GLOB.dsay_enabled) - to_chat(src, "Deadchat is globally muted") - return - - - var/input - if(!message) - input = sanitize(copytext(input(src, "Choose an emote to display.") as text|null, 1, MAX_MESSAGE_LEN)) - else - input = message - - if(input) - message = "DEAD: [src] [message]" - else - return - - - if(message) - for(var/mob/M in GLOB.player_list) - if(isnewplayer(M)) - continue - - if(check_rights(R_ADMIN|R_MOD, 0, M) && M.get_preference(PREFTOGGLE_CHAT_DEAD)) // Show the emote to admins/mods - to_chat(M, message) - - else if(M.stat == DEAD && M.get_preference(PREFTOGGLE_CHAT_DEAD)) // Show the emote to regular ghosts with deadchat toggled on - M.show_message(message, 2) diff --git a/code/modules/mob/living/carbon/alien/humanoid/emote.dm b/code/modules/mob/living/carbon/alien/humanoid/emote.dm deleted file mode 100644 index da833de9fce..00000000000 --- a/code/modules/mob/living/carbon/alien/humanoid/emote.dm +++ /dev/null @@ -1,141 +0,0 @@ -/mob/living/carbon/alien/humanoid/emote(act, m_type = 1, message = null, force) - var/param = null - if(findtext(act, "-", 1, null)) - var/t1 = findtext(act, "-", 1, null) - param = copytext(act, t1 + 1, length(act) + 1) - act = copytext(act, 1, t1) - -// if(findtext(act,"s",-1) && !findtext(act,"_",-2))//Removes ending s's unless they are prefixed with a '_' -// act = copytext(act,1,length(act)) //seriously who the fuck wrote this - var/muzzled = is_muzzled() - - var/on_CD = 0 - act = lowertext(act) - - //cooldown system handled by /code/modules/mob/emotes.dm - switch(act) - if("roar") - on_CD = handle_emote_CD() - if("deathgasp") - on_CD = handle_emote_CD() - if("hiss") - on_CD = handle_emote_CD() - if("gnarl") - on_CD = handle_emote_CD() - if("flip") - on_CD = handle_emote_CD() - - if(!force && on_CD == 1) - return - - switch(act) - - - - if("sign") - if(!restrained()) - var/num = null - if(text2num(param)) - num = "the number [text2num(param)]" - if(num) - message = "\The [src] signs [num]." - m_type = 1 - if("burp") - if(!muzzled) - message = "\The [src] burps." - m_type = 2 - if("deathgasp") - message = "\The [src] lets out a waning guttural screech, green blood bubbling from its maw..." - m_type = 2 - if("scratch") - if(!restrained()) - message = "\The [src] scratches." - m_type = 1 - if("whimper") - if(!muzzled) - message = "\The [src] whimpers." - m_type = 2 - if("roar") - if(!muzzled) - message = "\The [src] roars." - m_type = 2 - if("hiss") - if(!muzzled) - message = "\The [src] hisses." - m_type = 2 - if("tail") - message = "\The [src] waves its tail." - m_type = 1 - if("gasp") - message = "\The [src] gasps." - m_type = 2 - if("shiver") - message = "\The [src] shivers." - m_type = 2 - if("drool") - message = "\The [src] drools." - m_type = 1 - if("scretch") - if(!muzzled) - message = "\The [src] scretches." - m_type = 2 - if("choke") - message = "\The [src] chokes." - m_type = 2 - if("moan") - message = "\The [src] moans!" - m_type = 2 - if("nod") - message = "\The [src] nods its head." - m_type = 1 - if("sit") - message = "\The [src] sits down." - m_type = 1 - if("sway") - message = "\The [src] sways around dizzily." - m_type = 1 - if("sulk") - message = "\The [src] sulks down sadly." - m_type = 1 - if("twitch") - message = "\The [src] twitches violently." - m_type = 1 - if("dance") - if(!restrained()) - message = "\The [src] dances around happily." - m_type = 1 - if("roll") - if(!restrained()) - message = "\The [src] rolls." - m_type = 1 - if("shake") - message = "\The [src] shakes its head." - m_type = 1 - if("gnarl") - if(!muzzled) - message = "\The [src] gnarls and shows its teeth.." - m_type = 2 - if("jump") - message = "\The [src] jumps!" - m_type = 1 - if("collapse") - Paralyse(4 SECONDS) - message = "\The [src] collapses!" - m_type = 2 - if("flip") - m_type = 1 - message = "\The [src] does a flip!" - SpinAnimation(5,1) - if("help") - to_chat(src, "burp, flip, deathgasp, choke, collapse, dance, drool, gasp, shiver, gnarl, jump, moan, nod, roar, roll, scratch,\nscretch, shake, sign-#, sit, sulk, sway, tail, twitch, whimper") - - if(!stat) - if(act == "roar") - playsound(src.loc, 'sound/voice/hiss5.ogg', 50, 1, 1) - if(act == "deathgasp") - playsound(src.loc, 'sound/voice/hiss6.ogg', 80, 1, 1) - if(act == "hiss") - playsound(src.loc, 'sound/voice/hiss1.ogg', 30, 1, 1) - if(act == "gnarl") - playsound(src.loc, 'sound/voice/hiss4.ogg', 30, 1, 1) - ..() diff --git a/code/modules/mob/living/carbon/alien/humanoid/humanoid_emote.dm b/code/modules/mob/living/carbon/alien/humanoid/humanoid_emote.dm new file mode 100644 index 00000000000..86182cae1e2 --- /dev/null +++ b/code/modules/mob/living/carbon/alien/humanoid/humanoid_emote.dm @@ -0,0 +1,32 @@ +/** + * Emotes usable by humanoid xenomorphs. + */ +/datum/emote/living/carbon/alien/humanoid + mob_type_allowed_typecache = list(/mob/living/carbon/alien/humanoid) + +/datum/emote/living/carbon/alien/humanoid/roar + key = "roar" + key_third_person = "roars" + message = "roars!" + message_param = "roars at %t!" + emote_type = EMOTE_SOUND | EMOTE_MOUTH + sound = "sound/voice/hiss5.ogg" + volume = 80 + +/datum/emote/living/carbon/alien/humanoid/hiss + key = "hiss" + key_third_person = "hisses" + message = "hisses!" + message_param = "hisses at %t!" + emote_type = EMOTE_SOUND | EMOTE_MOUTH + sound = "sound/voice/hiss1.ogg" + volume = 30 + +/datum/emote/living/carbon/alien/humanoid/gnarl + key = "gnarl" + key_third_person = "gnarls" + message = "gnarls and shows its teeth." + message_param = "gnarls and flashes its teeth at %t." + sound = "sound/voice/hiss4.ogg" + emote_type = EMOTE_SOUND | EMOTE_MOUTH + volume = 30 diff --git a/code/modules/mob/living/carbon/alien/larva/emote.dm b/code/modules/mob/living/carbon/alien/larva/emote.dm deleted file mode 100644 index 8ccb01460bf..00000000000 --- a/code/modules/mob/living/carbon/alien/larva/emote.dm +++ /dev/null @@ -1,126 +0,0 @@ -/mob/living/carbon/alien/larva/emote(act, m_type = 1, message = null, force) - var/param = null - if(findtext(act, "-", 1, null)) - var/t1 = findtext(act, "-", 1, null) - param = copytext(act, t1 + 1, length(act) + 1) - act = copytext(act, 1, t1) - - if(findtext(act,"s",-1) && !findtext(act,"_",-2))//Removes ending s's unless they are prefixed with a '_' - act = copytext(act,1,length(act)) - var/muzzled = is_muzzled() - act = lowertext(act) - switch(act) - if("me") - if(HAS_TRAIT(src, TRAIT_MUTE)) - return - if(src.client) - if(check_mute(client.ckey, MUTE_IC)) - to_chat(src, "You cannot send IC messages (muted).") - return - if(src.client.handle_spam_prevention(message, MUTE_IC)) - return - if(stat) - return - if(!(message)) - return - return custom_emote(m_type, message) - - if("custom") - return custom_emote(m_type, message) - if("sign") - if(!src.restrained()) - message = text("The alien signs[].", (text2num(param) ? text(" the number []", text2num(param)) : null)) - m_type = 1 - if("burp") - if(!muzzled) - message = "[src] burps." - m_type = 2 - if("scratch") - if(!src.restrained()) - message = "[src] scratches." - m_type = 1 - if("whimper") - if(!muzzled) - message = "[src] whimpers." - m_type = 2 -// if("roar") -// if(!muzzled) -// message = "[src] roars." Commenting out since larva shouldn't roar /N -// m_type = 2 - if("tail") - message = "[src] waves its tail." - m_type = 1 - if("gasp") - message = "[src] gasps." - m_type = 2 - if("shiver") - message = "[src] shivers." - m_type = 2 - if("drool") - message = "[src] drools." - m_type = 1 - if("scretch") - if(!muzzled) - message = "[src] scretches." - m_type = 2 - if("choke") - message = "[src] chokes." - m_type = 2 - if("moan") - message = "[src] moans!" - m_type = 2 - if("nod") - message = "[src] nods its head." - m_type = 1 -// if("sit") -// message = "[src] sits down." //Larvan can't sit down, /N -// m_type = 1 - if("sway") - message = "[src] sways around dizzily." - m_type = 1 - if("sulk") - message = "[src] sulks down sadly." - m_type = 1 - if("twitch") - message = "[src] twitches violently." - m_type = 1 - if("dance") - if(!src.restrained()) - message = "[src] dances around happily." - m_type = 1 - if("roll") - if(!src.restrained()) - message = "[src] rolls." - m_type = 1 - if("shake") - message = "[src] shakes its head." - m_type = 1 - if("gnarl") - if(!muzzled) - message = "[src] gnarls and shows its teeth.." - m_type = 2 - if("jump") - message = "[src] jumps!" - m_type = 1 - if("hiss_") - message = "[src] hisses softly." - m_type = 1 - if("collapse") - Paralyse(4 SECONDS) - message = text("[] collapses!", src) - m_type = 2 - if("help") - to_chat(src, "burp, choke, collapse, dance, drool, gasp, shiver, gnarl, jump, moan, nod, roll, scratch,\nscretch, shake, sign-#, sulk, sway, tail, twitch, whimper") - else - to_chat(src, text("Invalid Emote: []", act)) - if((message && src.stat == 0)) - log_emote(message, src) - if(m_type & 1) - for(var/mob/O in viewers(src, null)) - O.show_message(message, m_type) - //Foreach goto(703) - else - for(var/mob/O in hearers(src, null)) - O.show_message(message, m_type) - //Foreach goto(746) - return diff --git a/code/modules/mob/living/carbon/brain/brain_emote.dm b/code/modules/mob/living/carbon/brain/brain_emote.dm new file mode 100644 index 00000000000..b9f1d720e0c --- /dev/null +++ b/code/modules/mob/living/carbon/brain/brain_emote.dm @@ -0,0 +1,67 @@ +/** + * Emotes usable by brains, but only while they're in MMIs. + */ +/datum/emote/living/carbon/brain + mob_type_allowed_typecache = list(/mob/living/carbon/brain) + mob_type_blacklist_typecache = null + /// The message that will be displayed to themselves, since brains can't really see their own emotes + var/self_message + +/datum/emote/living/carbon/brain/run_emote(mob/user, params, type_override, intentional) + . = ..() + if(!.) + return FALSE + + if(self_message) + to_chat(user, self_message) + +/datum/emote/living/carbon/brain/can_run_emote(mob/user, status_check, intentional) + . = ..() + if(!.) + return FALSE + + var/mob/living/carbon/brain/B = user + + if(!(B.container && istype(B.container, /obj/item/mmi))) // No MMI, no emotes + return FALSE + +// So, brains can't really see their own emotes so we'll probably just want to send an extra message + +/datum/emote/living/carbon/brain/alarm + key = "alarm" + key_third_person = "alarms" + message = "sounds an alarm." + self_message = "You sound an alarm." + +/datum/emote/living/carbon/brain/alert + key = "alert" + key_third_person = "alerts" + message = "lets out a distressed noise." + self_message = "You let out a distressed noise." + +/datum/emote/living/carbon/brain/notice + key = "notice" + message = "plays a loud tone." + self_message = "You play a loud tone." + +/datum/emote/living/carbon/brain/flash + key = "flash" + message = "starts flashing its lights quickly!" + +/datum/emote/living/carbon/brain/whistle + key = "whistle" + key_third_person = "whistles" + message = "whistles." + self_message = "You whistle." + +/datum/emote/living/carbon/brain/beep + key = "beep" + key_third_person = "beeps" + message = "beeps." + self_message = "You beep." + +/datum/emote/living/carbon/brain/boop + key = "boop" + key_third_person = "boops" + message = "boops." + self_message = "You boop." diff --git a/code/modules/mob/living/carbon/brain/emote.dm b/code/modules/mob/living/carbon/brain/emote.dm deleted file mode 100644 index 84298ee4f0f..00000000000 --- a/code/modules/mob/living/carbon/brain/emote.dm +++ /dev/null @@ -1,51 +0,0 @@ -/mob/living/carbon/brain/emote(act,m_type = 1, message = null, force) - if(!(container && istype(container, /obj/item/mmi)))//No MMI, no emotes - return - - if(findtext(act, "-", 1, null)) - var/t1 = findtext(act, "-", 1, null) - act = copytext(act, 1, t1) - - if(findtext(act,"s",-1) && !findtext(act,"_",-2))//Removes ending s's unless they are prefixed with a '_' - act = copytext(act,1,length(act)) - - if(src.stat == DEAD) - return - act = lowertext(act) - switch(act) - - if("alarm") - to_chat(src, "You sound an alarm.") - message = "\The [src] sounds an alarm." - m_type = 2 - if("alert") - to_chat(src, "You let out a distressed noise.") - message = "\The [src] lets out a distressed noise." - m_type = 2 - if("notice") - to_chat(src, "You play a loud tone.") - message = "\The [src] plays a loud tone." - m_type = 2 - if("flash") - message = "The lights on \the [src] flash quickly." - m_type = 1 - if("blink") - message = "\The [src] blinks." - m_type = 1 - if("whistle") - to_chat(src, "You whistle.") - message = "\The [src] whistles." - m_type = 2 - if("beep") - to_chat(src, "You beep.") - message = "\The [src] beeps." - m_type = 2 - if("boop") - to_chat(src, "You boop.") - message = "\The [src] boops." - m_type = 2 - if("help") - to_chat(src, "alarm, alert, notice, flash,blink, whistle, beep, boop") - - if(message && !stat) - ..() diff --git a/code/modules/mob/living/carbon/carbon_emote.dm b/code/modules/mob/living/carbon/carbon_emote.dm new file mode 100644 index 00000000000..bc908ba6a07 --- /dev/null +++ b/code/modules/mob/living/carbon/carbon_emote.dm @@ -0,0 +1,190 @@ +/datum/emote/living/carbon + mob_type_allowed_typecache = list(/mob/living/carbon) + mob_type_blacklist_typecache = list(/mob/living/carbon/brain) + +/datum/emote/living/carbon/blink + key = "blink" + key_third_person = "blinks" + message = "blinks." + +/datum/emote/living/carbon/blink_r + key = "blink_r" + message = "blinks rapidly." + +/datum/emote/living/carbon/clap + key = "clap" + key_third_person = "claps" + message = "claps." + message_mime = "claps silently." + message_param = "claps at %t." + emote_type = EMOTE_SOUND + vary = TRUE + +/datum/emote/living/carbon/clap/run_emote(mob/user, params, type_override, intentional) + var/mob/living/carbon/human/H = user + if(!H.bodyparts_by_name[BODY_ZONE_L_ARM] || !H.bodyparts_by_name[BODY_ZONE_R_ARM]) + if(!H.bodyparts_by_name[BODY_ZONE_L_ARM] && !H.bodyparts_by_name[BODY_ZONE_R_ARM]) + // no arms... + to_chat(user, "You need arms to be able to clap.") + else + // well, we've got at least one + user.visible_message("[user] makes the sound of one hand clapping.") + return TRUE + + return ..() + +/datum/emote/living/carbon/clap/get_sound(mob/living/user) + if(ishuman(user)) + var/mob/living/carbon/human/H = user + if(!H?.mind.miming) + return pick( + 'sound/misc/clap1.ogg', + 'sound/misc/clap2.ogg', + 'sound/misc/clap3.ogg', + 'sound/misc/clap4.ogg') + +/datum/emote/living/carbon/cross + key = "cross" + key_third_person = "crosses" + message = "crosses their arms." + hands_use_check = TRUE + +/datum/emote/living/carbon/chuckle + key = "chuckle" + key_third_person = "chuckles" + message = "chuckles." + message_mime = "appears to chuckle." + emote_type = EMOTE_AUDIBLE | EMOTE_MOUTH + muzzled_noises = list("joyful", "upbeat") + +/datum/emote/living/carbon/cough + key = "cough" + key_third_person = "coughs" + message = "coughs!" + message_mime = "appears to cough!" + emote_type = EMOTE_SOUND | EMOTE_MOUTH + vary = TRUE + age_based = TRUE + volume = 120 + unintentional_stat_allowed = UNCONSCIOUS + +/datum/emote/living/carbon/cough/get_sound(mob/living/user) + . = ..() + if(ishuman(user)) + var/mob/living/carbon/human/H = user + if(H.gender == FEMALE) + if(H.dna.species.female_cough_sounds) + return pick(H.dna.species.female_cough_sounds) + else + if(H.dna.species.male_cough_sounds) + return pick(H.dna.species.male_cough_sounds) + +/datum/emote/living/carbon/moan + key = "moan" + key_third_person = "moans" + message = "moans!" + message_mime = "appears to moan!" + muzzled_noises = list("pained") + emote_type = EMOTE_AUDIBLE | EMOTE_MOUTH + unintentional_stat_allowed = UNCONSCIOUS + +/datum/emote/living/carbon/giggle + key = "giggle" + key_third_person = "giggles" + message = "giggles." + message_mime = "giggles silently!" + muzzled_noises = list("bubbly") + emote_type = EMOTE_AUDIBLE | EMOTE_MOUTH + +/datum/emote/living/carbon/gurgle + key = "gurgle" + key_third_person = "gurgles" + message = "makes an uncomfortable gurgle." + muzzled_noises = list("unpleasant", "guttural") + emote_type = EMOTE_AUDIBLE | EMOTE_MOUTH + unintentional_stat_allowed = UNCONSCIOUS + +/datum/emote/living/carbon/inhale + key = "inhale" + key_third_person = "inhales" + message = "breathes in." + emote_type = EMOTE_AUDIBLE | EMOTE_MOUTH + muzzled_noises = list("breathy") + +/datum/emote/living/carbon/inhale/sharp + key = "inhale_s" + key_third_person = "inhales sharply!" + message = "takes a deep breath!" + +/datum/emote/living/carbon/kiss + key = "kiss" + key_third_person = "kisses" + message = "blows a kiss." + message_param = "blows a kiss at %t!" + muzzled_noises = list("smooching") + +/datum/emote/living/carbon/wave + key = "wave" + key_third_person = "waves" + message = "waves." + message_param = "waves at %t." + hands_use_check = TRUE + +/datum/emote/living/carbon/yawn + key = "yawn" + key_third_person = "yawns" + message = "yawns." + emote_type = EMOTE_AUDIBLE | EMOTE_MOUTH + muzzled_noises = list("tired", "lazy", "sleepy") + +/datum/emote/living/carbon/exhale + key = "exhale" + key_third_person = "exhales" + message = "breathes out." + emote_type = EMOTE_AUDIBLE | EMOTE_MOUTH + +/datum/emote/living/carbon/laugh + key = "laugh" + key_third_person = "laughs" + message = "laughs." + message_mime = "laughs silently!" + message_param = "laughs at %t." + muzzled_noises = list("happy", "joyful") + emote_type = EMOTE_AUDIBLE | EMOTE_MOUTH + +/datum/emote/living/carbon/scowl + key = "scowl" + key_third_person = "scowls" + message = "scowls." + +/datum/emote/living/groan + key = "groan" + key_third_person = "groans" + message = "groans!" + message_mime = "appears to groan!" + message_param = "groans at %t." + muzzled_noises = list("pained") + emote_type = EMOTE_AUDIBLE | EMOTE_MOUTH + unintentional_stat_allowed = UNCONSCIOUS + +/datum/emote/living/carbon/sign + key = "sign" + key_third_person = "signs" + message = "signs." + message_param = "signs the number %t." + param_desc = "number(0-10)" + // Humans get their own proc since they have fingers + mob_type_blacklist_typecache = list(/mob/living/carbon/human) + hands_use_check = TRUE + target_behavior = EMOTE_TARGET_BHVR_NUM + +/datum/emote/living/carbon/faint + key = "faint" + key_third_person = "faints" + message = "faints." + +/datum/emote/living/carbon/faint/run_emote(mob/user, params, type_override, intentional) + . = ..() + if(. && isliving(user)) + var/mob/living/L = user + L.SetSleeping(2 SECONDS) diff --git a/code/modules/mob/living/carbon/human/death.dm b/code/modules/mob/living/carbon/human/death.dm index afd93a809dd..630dae877bd 100644 --- a/code/modules/mob/living/carbon/human/death.dm +++ b/code/modules/mob/living/carbon/human/death.dm @@ -86,8 +86,6 @@ return TRUE /mob/living/carbon/human/death(gibbed) - if(can_die() && !gibbed && deathgasp_on_death) - emote("deathgasp", force = TRUE) //let the world KNOW WE ARE DEAD // Only execute the below if we successfully died . = ..(gibbed) diff --git a/code/modules/mob/living/carbon/human/emote.dm b/code/modules/mob/living/carbon/human/emote.dm deleted file mode 100644 index 21b4fd6d5ff..00000000000 --- a/code/modules/mob/living/carbon/human/emote.dm +++ /dev/null @@ -1,1053 +0,0 @@ -/mob/living/carbon/human/emote(act, m_type = 1, message = null, force) - - if((stat == DEAD) || HAS_TRAIT(src, TRAIT_FAKEDEATH)) - return // No screaming bodies - - var/param = null - if(findtext(act, "-", 1, null)) - var/t1 = findtext(act, "-", 1, null) - param = copytext(act, t1 + 1, length(act) + 1) - act = copytext(act, 1, t1) - - var/muzzled = is_muzzled() - if(muzzled) - var/obj/item/clothing/mask/muzzle/M = wear_mask - if(M.mute == MUZZLE_MUTE_NONE) - muzzled = FALSE //Not all muzzles block sound - if(!can_speak()) - muzzled = TRUE - //var/m_type = 1 - - for(var/obj/item/implant/I in src) - if(I.implanted) - I.trigger(act, src, force) - - var/miming = FALSE - if(mind) - miming = mind.miming - - //Emote Cooldown System (it's so simple!) - //handle_emote_CD() located in [code\modules\mob\emote.dm] - var/on_CD = FALSE - act = lowertext(act) - - switch(act) //This switch makes sure you have air in your lungs before you scream - if("growl", "growls", "howl", "howls", "hiss", "hisses", "scream", "screams", "sneeze", "sneezes") - if(getOxyLoss() > 35) //no screaming if you don't have enough breath to scream - on_CD = handle_emote_CD() - emote("gasp") - return - - switch(act) //This switch adds cooldowns to some emotes - if("ping", "pings", "buzz", "buzzes", "beep", "beeps", "yes", "no", "buzz2") - var/found_machine_head = FALSE - if(ismachineperson(src)) //Only Machines can beep, ping, and buzz, yes, no, and make a silly sad trombone noise. - on_CD = handle_emote_CD() //proc located in code\modules\mob\emote.dm - found_machine_head = TRUE - else - var/obj/item/organ/external/head/H = get_organ("head") // If you have a robotic head, you can make beep-boop noises - if(H && H.is_robotic()) - on_CD = handle_emote_CD() - found_machine_head = TRUE - - if(!found_machine_head) //Everyone else fails, skip the emote attempt - return //Everyone else fails, skip the emote attempt - if("drone","drones","hum","hums","rumble","rumbles") - if(isdrask(src)) //Only Drask can make whale noises - on_CD = handle_emote_CD() //proc located in code\modules\mob\emote.dm - else - return - if("howl", "howls") - if(isvulpkanin(src)) //Only Vulpkanin can howl - on_CD = handle_emote_CD(100) - else - return - if("growl", "growls") - if(isvulpkanin(src)) //Only Vulpkanin can growl - on_CD = handle_emote_CD() - else - return - if("squish", "squishes") - var/found_slime_bodypart = FALSE - - if(isslimeperson(src)) //Only Slime People can squish - on_CD = handle_emote_CD() //proc located in code\modules\mob\emote.dm' - found_slime_bodypart = TRUE - else - for(var/obj/item/organ/external/L in bodyparts) // if your limbs are squishy you can squish too! - if(istype(L.dna.species, /datum/species/slime)) - on_CD = handle_emote_CD() - found_slime_bodypart = TRUE - break - - if(!found_slime_bodypart) //Everyone else fails, skip the emote attempt - return - - if("clack", "clacks") - if(iskidan(src)) //Only Kidan can clack and rightfully so. - on_CD = handle_emote_CD(30) //proc located in code\modules\mob\emote.dm' - else //Everyone else fails, skip the emote attempt - return - - if("click", "clicks") - if(iskidan(src)) //Only Kidan can click and rightfully so. - on_CD = handle_emote_CD(30) //proc located in code\modules\mob\emote.dm' - else //Everyone else fails, skip the emote attempt - return - - if("creaks", "creak") - if(isdiona(src)) //Only Dionas can Creaks. - on_CD = handle_emote_CD() //proc located in code\modules\mob\emote.dm' - else //Everyone else fails, skip the emote attempt - return - - if("hiss", "hisses") - if(isunathi(src)) //Only Unathi can hiss. - on_CD = handle_emote_CD() //proc located in code\modules\mob\emote.dm' - else //Everyone else fails, skip the emote attempt - return - - if("quill", "quills") - if(isvox(src)) //Only Vox can rustle their quills. - on_CD = handle_emote_CD() //proc located in code\modules\mob\emote.dm' - else //Everyone else fails, skip the emote attempt - return - - if("warble", "warbles") - if(isskrell(src)) //Only Skrell can warble. - on_CD = handle_emote_CD() //proc located in code\modules\mob\emote.dm' - else //Everyone else fails, skip the emote attempt - return - if("rattle", "rattles") - if(isskeleton(src) || isplasmaman(src)) //Only Plasmamen and Skeletons can rattle. - on_CD = handle_emote_CD() //proc located in code\modules\mob\emote.dm' - else //Everyone else fails, skip the emote attempt - return - - if("flap", "flaps", "aflap", "aflaps","flutter", "flutters") - if(!ismoth(src)) - return - on_CD = handle_emote_CD() - - if("scream", "screams") - on_CD = handle_emote_CD(50) //longer cooldown - if("fart", "farts", "flip", "flips", "snap", "snaps") - on_CD = handle_emote_CD() //proc located in code\modules\mob\emote.dm - if("cough", "coughs", "highfive") - on_CD = handle_emote_CD() - if("gasp", "gasps") - on_CD = handle_emote_CD() - if("deathgasp", "deathgasps") - on_CD = handle_emote_CD(50) - if("spin", "spins") - on_CD = handle_emote_CD(50) - if("sneeze", "sneezes") - on_CD = handle_emote_CD() - if("clap", "claps") - on_CD = handle_emote_CD() - if("slap", "slaps") - on_CD = handle_emote_CD(3 SECONDS) - //Everything else, including typos of the above emotes - else - on_CD = FALSE //If it doesn't induce the cooldown, we won't check for the cooldown - - if(!force && on_CD == 1) // Check if we need to suppress the emote attempt. - return // Suppress emote, you're still cooling off. - - switch(act) //This is for actually making the emotes happen - if("me") //OKAY SO RANT TIME, THIS FUCKING HAS TO BE HERE OR A SHITLOAD OF THINGS BREAK - return custom_emote(m_type, message) //DO YOU KNOW WHY SHIT BREAKS? BECAUSE SO MUCH OLDCODE CALLS mob.emote("me",1,"whatever_the_fuck_it_wants_to_emote") - //WHO THE FUCK THOUGHT THAT WAS A GOOD FUCKING IDEA!?!? - - if("howl", "howls") - var/M = handle_emote_param(param) - if(miming) - message = "[src] acts out a howl[M ? " at [M]" : ""]!" - m_type = 1 - else - if(!muzzled) - message = "[src] howls[M ? " at [M]" : ""]!" - playsound(loc, 'sound/goonstation/voice/howl.ogg', 100, 1, 10, frequency = get_age_pitch()) - m_type = 2 - else - message = "[src] makes a very loud noise[M ? " at [M]" : ""]." - m_type = 2 - - if("growl", "growls") - var/M = handle_emote_param(param) - message = "[src] growls[M ? " at [M]" : ""]." - playsound(loc, "growls", 80, 1, frequency = get_age_pitch()) - m_type = 2 - - if("ping", "pings") - var/M = handle_emote_param(param) - - message = "[src] pings[M ? " at [M]" : ""]." - playsound(loc, 'sound/machines/ping.ogg', 50, 1, frequency = get_age_pitch()) - m_type = 2 - - if("buzz2") - var/M = handle_emote_param(param) - - message = "[src] emits an irritated buzzing sound[M ? " at [M]" : ""]." - playsound(loc, 'sound/machines/buzz-two.ogg', 50, 1, frequency = get_age_pitch()) - m_type = 2 - - if("buzz", "buzzes") - var/M = handle_emote_param(param) - - message = "[src] buzzes[M ? " at [M]" : ""]." - playsound(loc, 'sound/machines/buzz-sigh.ogg', 50, 1, frequency = get_age_pitch()) - m_type = 2 - - if("beep", "beeps") - var/M = handle_emote_param(param) - - message = "[src] beeps[M ? " at [M]" : ""]." - playsound(loc, 'sound/machines/twobeep.ogg', 50, 1, frequency = get_age_pitch()) - m_type = 2 - - if("drone", "drones", "hum", "hums", "rumble", "rumbles") - var/M = handle_emote_param(param) - - message = "[src] [M ? "drones at [M]" : "rumbles"]." - playsound(loc, 'sound/voice/drasktalk.ogg', 50, 1, frequency = get_age_pitch()) - m_type = 2 - - if("squish", "squishes") - var/M = handle_emote_param(param) - - message = "[src] squishes[M ? " at [M]" : ""]." - playsound(loc, 'sound/effects/slime_squish.ogg', 50, 1, frequency = get_age_pitch()) //Credit to DrMinky (freesound.org) for the sound. - m_type = 2 - - if("clack", "clacks") - var/M = handle_emote_param(param) - mineral_scan_pulse(get_turf(src), range = world.view) - message = "[src] clacks [p_their()] mandibles[M ? " at [M]" : ""]." - playsound(loc, 'sound/effects/Kidanclack.ogg', 50, 1, frequency = get_age_pitch()) //Credit to DrMinky (freesound.org) for the sound. - m_type = 2 - - if("click", "clicks") - var/M = handle_emote_param(param) - mineral_scan_pulse(get_turf(src), range = world.view) - message = "[src] clicks [p_their()] mandibles[M ? " at [M]" : ""]." - playsound(loc, 'sound/effects/Kidanclack2.ogg', 50, 1, frequency = get_age_pitch()) //Credit to DrMinky (freesound.org) for the sound. - m_type = 2 - - if("creaks", "creak") - var/M = handle_emote_param(param) - - message = "[src] creaks[M ? " at [M]" : ""]." - playsound(loc, 'sound/voice/dionatalk1.ogg', 50, 1, frequency = get_age_pitch()) //Credit https://www.youtube.com/watch?v=ufnvlRjsOTI [0:13 - 0:16] - m_type = 2 - - if("hiss", "hisses") - var/M = handle_emote_param(param) - - if(!muzzled) - message = "[src] hisses[M ? " at [M]" : ""]." - playsound(loc, 'sound/effects/unathihiss.ogg', 50, 1, frequency = get_age_pitch()) //Credit to Jamius (freesound.org) for the sound. - m_type = 2 - else - message = "[src] makes a weak hissing noise." - m_type = 2 - - if("quill", "quills") - var/M = handle_emote_param(param) - - message = "[src] rustles [p_their()] quills[M ? " at [M]" : ""]." - playsound(loc, 'sound/effects/voxrustle.ogg', 50, 1, frequency = get_age_pitch()) //Credit to sound-ideas (freesfx.co.uk) for the sound. - m_type = 2 - - if("warble", "warbles") - var/M = handle_emote_param(param) - - message = "[src] warbles[M ? " at [M]" : ""]." - playsound(loc, 'sound/effects/warble.ogg', 50, 1, frequency = get_age_pitch()) // Copyright CC BY 3.0 alienistcog (freesound.org) for the sound. - m_type = 2 - - if("yes") - var/M = handle_emote_param(param) - - message = "[src] emits an affirmative blip[M ? " at [M]" : ""]." - playsound(loc, 'sound/machines/synth_yes.ogg', 50, 1, frequency = get_age_pitch()) - m_type = 2 - - if("no") - var/M = handle_emote_param(param) - - message = "[src] emits a negative blip[M ? " at [M]" : ""]." - playsound(loc, 'sound/machines/synth_no.ogg', 50, 1, frequency = get_age_pitch()) - m_type = 2 - - if("rattle", "rattles") - var/M = handle_emote_param(param) - - message = "[src] rattles [p_their()] bones[M ? " at [M]" : ""]." - m_type = 1 - - if("wag", "wags") - if(istype(body_accessory, /datum/body_accessory/tail)) - if(body_accessory.try_restrictions(src)) - message = "[src] starts wagging [p_their()] tail." - start_tail_wagging() - else if(dna.species.bodyflags & TAIL_WAGGING) - if(!wear_suit || !(wear_suit.flags_inv & HIDETAIL)) - message = "[src] starts wagging [p_their()] tail." - start_tail_wagging() - else - return - else - return - m_type = 1 - - if("swag", "swags") - if((dna.species.bodyflags & TAIL_WAGGING) || istype(body_accessory, /datum/body_accessory/tail)) - message = "[src] stops wagging [p_their()] tail." - stop_tail_wagging() - else - return - m_type = 1 - - if("airguitar") - if(!restrained()) - message = "[src] is strumming the air and headbanging like a safari chimp." - m_type = 1 - - if("dance") - if(!restrained()) - message = "[src] dances around happily." - m_type = 1 - - if("jump") - if(!restrained()) - message = "[src] jumps!" - m_type = 1 - - if("blink", "blinks") - message = "[src] blinks." - m_type = 1 - - if("blink_r", "blinks_r") - message = "[src] blinks rapidly." - m_type = 1 - - if("bow", "bows") - if(!restrained()) - var/M = handle_emote_param(param) - - message = "[src] bows[M ? " to [M]" : ""]." - m_type = 1 - - if("salute", "salutes") - if(!restrained()) - var/M = handle_emote_param(param) - - message = "[src] salutes[M ? " to [M]" : ""]." - m_type = 1 - - if("choke", "chokes") - if(miming) - message = "[src] clutches [p_their()] throat desperately!" - m_type = 1 - else - if(!muzzled) - message = "[src] chokes!" - m_type = 2 - else - message = "[src] makes a strong noise." - m_type = 2 - - if("burp", "burps") - if(miming) - message = "[src] opens [p_their()] mouth rather obnoxiously." - m_type = 1 - else - if(!muzzled) - message = "[src] burps." - m_type = 2 - else - message = "[src] makes a peculiar noise." - m_type = 2 - if("clap", "claps") - if(miming) - message = "[src] claps silently." - m_type = 1 - else - m_type = 2 - var/obj/item/organ/external/L = get_organ("l_hand") - var/obj/item/organ/external/R = get_organ("r_hand") - - var/left_hand_good = FALSE - var/right_hand_good = FALSE - - if(L && (!(L.status & ORGAN_SPLINTED)) && (!(L.status & ORGAN_BROKEN))) - left_hand_good = TRUE - if(R && (!(R.status & ORGAN_SPLINTED)) && (!(R.status & ORGAN_BROKEN))) - right_hand_good = TRUE - - if(left_hand_good && right_hand_good) - message = "[src] claps." - var/clap = pick('sound/misc/clap1.ogg', 'sound/misc/clap2.ogg', 'sound/misc/clap3.ogg', 'sound/misc/clap4.ogg') - playsound(loc, clap, 50, 1, -1) - - else - to_chat(usr, "You need your hands working in order to clap.") - - if("flap", "flaps") - message = "[src] flaps [p_their()] wings." - m_type = 2 - if(miming) - m_type = 1 - - if("flutter", "flutters") - message = "[src] flutters [p_their()] wings." - m_type = 2 - if(miming) - m_type = 1 - - if("flip", "flips") - m_type = 1 - if(!restrained()) - var/M = null - if(param) - for(var/mob/A in view(1, null)) - if(param == A.name) - M = A - break - if(M == src) - M = null - - if(M) - if(lying) - message = "[src] flops and flails around on the floor." - else - message = "[src] flips in [M]'s general direction." - SpinAnimation(5,1) - else - if(lying || IsWeakened()) - message = "[src] flops and flails around on the floor." - else - var/obj/item/grab/G - if(istype(get_active_hand(), /obj/item/grab)) - G = get_active_hand() - if(G && G.affecting) - if(buckled || G.affecting.buckled) - return - var/turf/oldloc = loc - var/turf/newloc = G.affecting.loc - if(isturf(oldloc) && isturf(newloc)) - SpinAnimation(5,1) - glide_for(6) // This and the glide_for below are purely arbitrary. Pick something that looks aesthetically pleasing. - forceMove(newloc) - G.glide_for(6) - G.affecting.forceMove(oldloc) - message = "[src] flips over [G.affecting]!" - else - if(prob(5)) - message = "[src] attempts a flip and crashes to the floor!" - SpinAnimation(5,1) - sleep(3) - Weaken(4 SECONDS) - else - message = "[src] does a flip!" - SpinAnimation(5,1) - - if("spin", "spins") - if(!incapacitated(ignore_lying = TRUE)) - if(prob(5)) - spin(32, 1) - to_chat(src, "You spin too much!") - Dizzy(24 SECONDS) - Confused(24 SECONDS) - else - spin(20, 1) - - if("aflap", "aflaps") - message = "[src] flaps [p_their()] wings ANGRILY!" - m_type = 2 - if(miming) - m_type = 1 - - if("drool", "drools") - message = "[src] drools." - m_type = 1 - - if("eyebrow") - message = "[src] raises an eyebrow." - m_type = 1 - - if("chuckle", "chuckles") - if(miming) - message = "[src] appears to chuckle." - m_type = 1 - else - if(!muzzled) - message = "[src] chuckles." - m_type = 2 - else - message = "[src] makes a noise." - m_type = 2 - - if("twitch", "twitches") - message = "[src] twitches violently." - m_type = 1 - - if("twitch_s", "twitches_s") - message = "[src] twitches." - m_type = 1 - - if("faint", "faints") - message = "[src] faints." - if(IsSleeping()) - return //Can't faint while asleep - AdjustSleeping(4 SECONDS) - m_type = 1 - - if("cough", "coughs") - if(miming) - message = "[src] appears to cough!" - m_type = 1 - else - if(!muzzled) - message = "[src] coughs!" - m_type = 2 - if(gender == FEMALE) - if(dna.species.female_cough_sounds) - playsound(src, pick(dna.species.female_cough_sounds), 120, 1, frequency = get_age_pitch()) - else - if(dna.species.male_cough_sounds) - playsound(src, pick(dna.species.male_cough_sounds), 120, 1, frequency = get_age_pitch()) - else - message = "[src] makes a strong noise." - m_type = 2 - - if("frown", "frowns") - var/M = handle_emote_param(param) - - message = "[src] frowns[M ? " at [M]" : ""]." - m_type = 1 - - if("nod", "nods") - var/M = handle_emote_param(param) - - message = "[src] nods[M ? " at [M]" : ""]." - m_type = 1 - - if("kiss", "kisses") - var/M = handle_emote_param(param) - - message = "[src] blows a kiss[M ? " at [M]" : ""]." - m_type = 1 - - if("blush", "blushes") - message = "[src] blushes." - m_type = 1 - - if("wave", "waves") - var/M = handle_emote_param(param) - - message = "[src] waves[M ? " at [M]" : ""]." - m_type = 1 - - if("quiver", "quivers") - message = "[src] quivers." - m_type = 1 - - if("gasp", "gasps") - if(miming) - message = "[src] appears to be gasping!" - m_type = 1 - else - if(!muzzled) - message = "[src] gasps!" - if(health <= 0) - if(gender == FEMALE) - playsound(loc, pick(dna.species.female_dying_gasp_sounds), 100, 1, frequency = get_age_pitch()) - else - playsound(loc, pick(dna.species.male_dying_gasp_sounds), 100, 1, frequency = get_age_pitch()) - - else - playsound(loc, dna.species.gasp_sound, 15, 1, frequency = get_age_pitch()) - m_type = 2 - else - message = "[src] makes a weak noise." - m_type = 2 - - if("deathgasp", "deathgasps") - message = "[src] [replacetext(dna.species.death_message, "their", p_their())]" - playsound(loc, pick(dna.species.death_sounds), 40, 1, frequency = get_age_pitch()) - m_type = 1 - - if("giggle", "giggles") - if(miming) - message = "[src] giggles silently!" - m_type = 1 - else - if(!muzzled) - message = "[src] giggles." - m_type = 2 - else - message = "[src] makes a noise." - m_type = 2 - - if("glare", "glares") - var/M = handle_emote_param(param) - - message = "[src] glares[M ? " at [M]" : ""]." - m_type = 1 - - if("stare", "stares") - var/M = handle_emote_param(param) - - message = "[src] stares[M ? " at [M]" : ""]." - m_type = 1 - - if("look", "looks") - var/M = handle_emote_param(param) - - message = "[src] looks[M ? " at [M]" : ""]." - m_type = 1 - - if("grin", "grins") - var/M = handle_emote_param(param) - - message = "[src] grins[M ? " at [M]" : ""]." - m_type = 1 - - if("cry", "cries") - if(miming) - message = "[src] cries." - m_type = 1 - else - if(!muzzled) - message = "[src] cries." - m_type = 2 - else - message = "[src] makes a weak noise. [p_they(TRUE)] frown[p_s()]." - m_type = 2 - - if("sigh", "sighs") - var/M = handle_emote_param(param) - if(miming) - message = "[src] sighs[M ? " at [M]" : ""]." - m_type = 1 - else - if(!muzzled) - message = "[src] sighs[M ? " at [M]" : ""]." - m_type = 2 - else - message = "[src] makes a weak noise." - m_type = 2 - - if("hsigh", "hsighs") - if(!muzzled) - message = "[src] sighs contentedly." - m_type = 2 - else - message = "[src] makes a [pick("chill", "relaxed")] noise." - m_type = 2 - - if("laugh", "laughs") - var/M = handle_emote_param(param) - if(miming) - message = "[src] acts out a laugh[M ? " at [M]" : ""]." - m_type = 1 - else - if(!muzzled) - message = "[src] laughs[M ? " at [M]" : ""]." - m_type = 2 - else - message = "[src] makes a noise." - m_type = 2 - - if("mumble", "mumbles") - message = "[src] mumbles!" - m_type = 2 - if(miming) - m_type = 1 - - if("grumble", "grumbles") - var/M = handle_emote_param(param) - if(miming) - message = "[src] grumbles[M ? " at [M]" : ""]!" - m_type = 1 - if(!muzzled) - message = "[src] grumbles[M ? " at [M]" : ""]!" - m_type = 2 - else - message = "[src] makes a noise." - m_type = 2 - - if("groan", "groans") - if(miming) - message = "[src] appears to groan!" - m_type = 1 - else - if(!muzzled) - message = "[src] groans!" - m_type = 2 - else - message = "[src] makes a loud noise." - m_type = 2 - - if("moan", "moans") - if(miming) - message = "[src] appears to moan!" - m_type = 1 - else - message = "[src] moans!" - m_type = 2 - - if("johnny") - var/M - if(param) - M = param - if(!M) - param = null - else - if(miming) - message = "[src] takes a drag from a cigarette and blows \"[M]\" out in smoke." - m_type = 1 - else - message = "[src] says, \"[M], please. They had a family.\" [name] takes a drag from a cigarette and blows [p_their()] name out in smoke." - m_type = 2 - - if("point", "points") - if(!restrained()) - var/atom/M = null - if(param) - for(var/atom/A as mob|obj|turf in view()) - if(param == A.name) - M = A - break - - if(!M) - message = "[src] points." - else - pointed(M) - m_type = 1 - - if("raise", "raises") - if(!restrained()) - message = "[src] raises a hand." - m_type = 1 - - if("shake", "shakes") - var/M = handle_emote_param(param, 1) //Check to see if the param is valid (mob with the param name is in view) but exclude ourselves. - - message = "[src] shakes [p_their()] head[M ? " at [M]" : ""]." - m_type = 1 - - if("shrug", "shrugs") - message = "[src] shrugs." - m_type = 1 - - if("signal", "signals") - if(!restrained()) - var/t1 = round(text2num(param)) - if(isnum(t1)) - if(t1 <= 5 && t1 >= 1 && (!r_hand || !l_hand)) - message = "[src] raises [t1] finger\s." - else if(t1 <= 10 && t1 >= 1 && (!r_hand && !l_hand)) - message = "[src] raises [t1] finger\s." - m_type = 1 - - if("smile", "smiles") - var/M = handle_emote_param(param, 1) - - message = "[src] smiles[M ? " at [M]" : ""]." - m_type = 1 - - if("shiver", "shivers") - message = "[src] shivers." - m_type = 2 - if(miming) - m_type = 1 - - if("pale", "pales") - message = "[src] goes pale for a second." - m_type = 1 - - if("tremble", "trembles") - message = "[src] trembles." - m_type = 1 - - if("shudder", "shudders") - message = "[src] shudders." - m_type = 1 - - if("bshake", "bshakes") - message = "[src] shakes." - m_type = 1 - - if("sneeze", "sneezes") - if(miming) - message = "[src] sneezes." - m_type = 1 - else - if(!muzzled) - message = "[src] sneezes." - if(gender == FEMALE) - playsound(src, dna.species.female_sneeze_sound, 70, 1, frequency = get_age_pitch()) - else - playsound(src, dna.species.male_sneeze_sound, 70, 1, frequency = get_age_pitch()) - m_type = 2 - else - message = "[src] makes a strange noise." - m_type = 2 - - if("sniff", "sniffs") - message = "[src] sniffs." - m_type = 2 - if(miming) - m_type = 1 - - if("snore", "snores") - if(miming) - message = "[src] sleeps soundly." - m_type = 1 - else - if(!muzzled) - message = "[src] snores." - m_type = 2 - else - message = "[src] makes a noise." - m_type = 2 - - if("whimper", "whimpers") - if(miming) - message = "[src] appears hurt." - m_type = 1 - else - if(!muzzled) - message = "[src] whimpers." - m_type = 2 - else - message = "[src] makes a weak noise." - m_type = 2 - - if("wink", "winks") - var/M = handle_emote_param(param, 1) - - message = "[src] winks[M ? " at [M]" : ""]." - m_type = 1 - - if("yawn", "yawns") - if(!muzzled) - message = "[src] yawns." - m_type = 2 - if(miming) - m_type = 1 - - if("collapse", "collapses") - Paralyse(4 SECONDS) - message = "[src] collapses!" - m_type = 2 - if(miming) - m_type = 1 - - if("hug", "hugs") - m_type = 1 - if(!restrained()) - var/M = handle_emote_param(param, 1, 1) //Check to see if the param is valid (mob with the param name is in view) but exclude ourselves and only check mobs in our immediate vicinity (1 tile distance). - - if(M) - message = "[src] hugs [M]." - else - message = "[src] hugs [p_them()]self." - - if("handshake") - m_type = 1 - if(!restrained() && !r_hand) - var/mob/M = handle_emote_param(param, 1, 1, 1) //Check to see if the param is valid (mob with the param name is in view) but exclude ourselves, only check mobs in our immediate vicinity (1 tile distance) and return the whole mob instead of just its name. - - if(M) - if(M.canmove && !M.r_hand && !M.restrained()) - message = "[src] shakes hands with [M]." - else - message = "[src] holds out [p_their()] hand to [M]." - - if("dap", "daps") - m_type = 1 - if(!restrained()) - var/M = handle_emote_param(param, null, 1) - - if(M) - message = "[src] gives daps to [M]." - else - message = "[src] sadly can't find anybody to give daps to, and daps [p_them()]self. Shameful." - - if("slap", "slaps") - var/obj/item/slapper/N = new(src) - if(put_in_hands(N)) - to_chat(src, "You ready your slapping hand.") - else - qdel(N) - to_chat(src, "You're incapable of slapping in your current state.") - - if("scream", "screams") - var/M = handle_emote_param(param) - if(miming) - message = "[src] acts out a scream[M ? " at [M]" : ""]!" - m_type = 1 - else - if(!muzzled) - message = "[src] [dna.species.scream_verb][M ? " at [M]" : ""]!" - m_type = 2 - if(gender == FEMALE) - playsound(loc, dna.species.female_scream_sound, 80, 1, frequency = get_age_pitch()) - else - playsound(loc, dna.species.male_scream_sound, 80, 1, frequency = get_age_pitch()) //default to male screams if no gender is present. - - else - message = "[src] makes a very loud noise[M ? " at [M]" : ""]." - m_type = 2 - - if("snap", "snaps") - if(prob(95)) - m_type = 2 - var/mob/living/carbon/human/H = src - var/obj/item/organ/external/L = H.get_organ("l_hand") - var/obj/item/organ/external/R = H.get_organ("r_hand") - var/left_hand_good = 0 - var/right_hand_good = 0 - if(L && (!(L.status & ORGAN_SPLINTED)) && (!(L.status & ORGAN_BROKEN))) - left_hand_good = 1 - if(R && (!(R.status & ORGAN_SPLINTED)) && (!(R.status & ORGAN_BROKEN))) - right_hand_good = 1 - - if(!left_hand_good && !right_hand_good) - to_chat(usr, "You need at least one hand in good working order to snap your fingers.") - return - - var/M = handle_emote_param(param) - - message = "[src] snaps [p_their()] fingers[M ? " at [M]" : ""]." - playsound(loc, 'sound/effects/fingersnap.ogg', 50, 1, -3) - else - message = "[src] snaps [p_their()] fingers right off!" - playsound(loc, 'sound/effects/snap.ogg', 50, 1) - - if("fart", "farts") - var/farted_on_thing = FALSE - for(var/atom/A in get_turf(src)) - farted_on_thing += A.fart_act(src) - if(!farted_on_thing) - message = "[src] [pick("passes wind", "farts")]." - m_type = 2 - - if("hem") - message = "[src] hems." - - if("highfive") - if(restrained()) - return - if(has_status_effect(STATUS_EFFECT_HIGHFIVE)) - to_chat(src, "You give up on the highfive.") - remove_status_effect(STATUS_EFFECT_HIGHFIVE) - return - visible_message("[name] requests a highfive.", "You request a high five.") - apply_status_effect(STATUS_EFFECT_HIGHFIVE) - for(var/mob/living/L in orange(1)) - if(L.has_status_effect(STATUS_EFFECT_HIGHFIVE)) - if((mind && mind.special_role == SPECIAL_ROLE_WIZARD) && (L.mind && L.mind.special_role == SPECIAL_ROLE_WIZARD)) - visible_message("[name] and [L.name] high-five EPICALLY!") - status_flags |= GODMODE - L.status_flags |= GODMODE - explosion(loc,5,2,1,3) - status_flags &= ~GODMODE - L.status_flags &= ~GODMODE - return - visible_message("[name] and [L.name] high-five!") - playsound('sound/effects/snap.ogg', 50) - remove_status_effect(STATUS_EFFECT_HIGHFIVE) - L.remove_status_effect(STATUS_EFFECT_HIGHFIVE) - return - - if("help") - var/emotelist = "airguitar, blink(s), blink(s)_r, blush(es), bow(s)-none/mob, burp(s), choke(s), chuckle(s), clap(s), collapse(s), cough(s), cry, cries, custom, dance, dap(s)-none/mob," \ - + " deathgasp(s), drool(s), eyebrow, fart(s), faint(s), flip(s), frown(s), gasp(s), giggle(s), glare(s)-none/mob, grin(s), groan(s), grumble(s), grin(s)," \ - + " handshake-mob, hug(s)-none/mob, hem, highfive, johnny, jump, kiss(es), laugh(s), look(s)-none/mob, moan(s), mumble(s), nod(s), pale(s), point(s)-atom, quiver(s), raise(s), salute(s)-none/mob, scream(s), shake(s)," \ - + " shiver(s), shrug(s), sigh(s), signal(s)-#1-10, slap(s), smile(s),snap(s), sneeze(s), sniff(s), snore(s), spin(s) stare(s)-none/mob, tremble(s), twitch(es), twitch(es)_s," \ - + " wave(s), whimper(s), wink(s), yawn(s)" - - switch(dna.species.name) //dear future coders, do not use strings like this - if("Diona") - emotelist += "\nDiona specific emotes :- creak(s)" - if("Drask") - emotelist += "\nDrask specific emotes :- drone(s)-none/mob, hum(s)-none/mob, rumble(s)-none/mob" - if("Kidan") - emotelist += "\nKidan specific emotes :- click(s), clack(s)" - if("Skrell") - emotelist += "\nSkrell specific emotes :- warble(s)" - if("Tajaran") - emotelist += "\nTajaran specifc emotes :- wag(s), swag(s)" - if("Unathi") - emotelist += "\nUnathi specific emotes :- wag(s), swag(s), hiss(es)" - if("Vox") - emotelist += "\nVox specific emotes :- wag(s), swag(s), quill(s)" - if("Vulpkanin") - emotelist += "\nVulpkanin specific emotes :- wag(s), swag(s), growl(s)-none/mob, howl(s)-none/mob" - if("Plasmaman") - emotelist += "\nPlasmaman specific emotes :- rattle(s)-none/mob" - if("Skeleton") - emotelist += "\nSkeleton specific emotes :- rattle(s)-none/mob" - if("Nian") - emotelist += "\nNian specific emotes :- aflap(s), flap(s), flutter(s)" - - if(ismachineperson(src)) - emotelist += "\nMachine specific emotes :- beep(s)-none/mob, buzz(es)-none/mob, no-none/mob, ping(s)-none/mob, yes-none/mob, buzz2-none/mob" - else - var/obj/item/organ/external/head/H = get_organ("head") // If you have a robotic head, you can make beep-boop noises - if(H && H.is_robotic()) - emotelist += "\nRobotic head specific emotes :- beep(s)-none/mob, buzz(es)-none/mob, no-none/mob, ping(s)-none/mob, yes-none/mob, buzz2-none/mob" - - if(isslimeperson(src)) - emotelist += "\nSlime people specific emotes :- squish(es)-none/mob" - else - for(var/obj/item/organ/external/L in bodyparts) // if your limbs are squishy you can squish too! - if(istype(L.dna.species, /datum/species/slime)) - emotelist += "\nSlime people body part specific emotes :- squish(es)-none/mob" - break - - to_chat(src, emotelist) - else - to_chat(src, "Unusable emote '[act]'. Say *help for a list.") - - if(message) //Humans are special fucking snowflakes and have 800 lines of emotes, they get to handle their own emotes, not call the parent. - log_emote(message, src) - - //Hearing gasp and such every five seconds is not good emotes were not global for a reason. - // Maybe some people are okay with that. - - for(var/mob/M in GLOB.dead_mob_list) - if(!M.client) - continue - - if(isnewplayer(M)) - continue - - if(isobserver(M) && M.get_preference(PREFTOGGLE_CHAT_GHOSTSIGHT) && !(M in viewers(src, null)) && client) // The client check makes sure people with ghost sight don't get spammed by simple mobs emoting. - M.show_message(message) - - switch(m_type) - if(1) - visible_message(message) - if(2) - audible_message(message) - -/mob/living/carbon/human/verb/pose() - set name = "Set Pose" - set desc = "Sets a description which will be shown when someone examines you." - set category = "IC" - - pose = sanitize(copytext(input(usr, "This is [src]. [p_they(TRUE)] [p_are()]...", "Pose", null) as text, 1, MAX_MESSAGE_LEN)) - -/mob/living/carbon/human/verb/set_flavor() - set name = "Set Flavour Text" - set desc = "Sets an extended description of your character's features." - set category = "IC" - - update_flavor_text() diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm index 9e35fb42c4e..cc8418b8660 100644 --- a/code/modules/mob/living/carbon/human/human.dm +++ b/code/modules/mob/living/carbon/human/human.dm @@ -2015,3 +2015,21 @@ Eyes need to have significantly high darksight to shine unless the mob has the X to_chat(usr, "[src] does not have any stored infomation!") else to_chat(usr, "OOC Metadata is not supported by this server!") + +/mob/living/carbon/human/verb/pose() + set name = "Set Pose" + set desc = "Sets a description which will be shown when someone examines you." + set category = "IC" + + // no metagaming + if(stat) + return + + pose = sanitize(copytext(input(usr, "This is [src]. [p_they(TRUE)] [p_are()]...", "Pose", null) as text, 1, MAX_MESSAGE_LEN)) + +/mob/living/carbon/human/verb/set_flavor() + set name = "Set Flavour Text" + set desc = "Sets an extended description of your character's features." + set category = "IC" + + update_flavor_text() diff --git a/code/modules/mob/living/carbon/human/human_emote.dm b/code/modules/mob/living/carbon/human/human_emote.dm new file mode 100644 index 00000000000..fe34dabb376 --- /dev/null +++ b/code/modules/mob/living/carbon/human/human_emote.dm @@ -0,0 +1,686 @@ +/datum/emote/living/carbon/human + /// Custom messages that should be applied based on species + /// Should be an associative list of species name: message + var/species_custom_messages = list() + /// Custom messages applied to mimes of a particular species + var/species_custom_mime_messages = list() + mob_type_allowed_typecache = list(/mob/living/carbon/human) + +/datum/emote/living/carbon/human/select_message_type(mob/user, msg, intentional) + . = ..() + + var/mob/living/carbon/human/human_user = user + + if(!species_custom_messages || (human_user.mind?.miming && !species_custom_mime_messages)) + return . + + var/custom_message + if(user.mind?.miming) + custom_message = species_custom_mime_messages[human_user.dna.species?.name] + else + custom_message = species_custom_messages[human_user.dna.species?.name] + + if(custom_message) + return custom_message + +/datum/emote/living/carbon/human/run_emote(mob/user, params, type_override, intentional) + var/mob/living/carbon/human/H = user + if((emote_type & EMOTE_MOUTH) && !H.mind?.miming) + if(H.getOxyLoss() > 10 || H.AmountLoseBreath() >= 8 SECONDS) // no screaming if you don't have enough breath to scream + H.emote("gasp") + return TRUE + return ..() + +/datum/emote/living/carbon/human/airguitar + key = "airguitar" + message = "is strumming the air and headbanging like a safari chimp." + emote_type = EMOTE_VISIBLE + hands_use_check = TRUE + +/datum/emote/living/carbon/human/cry + key = "cry" + key_third_person = "cries" + message = "cries." + muzzled_noises = list("weak", "pathetic", "sad") + emote_type = EMOTE_AUDIBLE + +/datum/emote/living/carbon/human/dap + key = "dap" + key_third_person = "daps" + message = "sadly can't find anybody to give daps to, and daps themself. Shameful." + message_param = "give daps to %t." + emote_target_type = EMOTE_TARGET_MOB + hands_use_check = TRUE + +/datum/emote/living/carbon/human/eyebrow + key = "eyebrow" + message = "raises an eyebrow." + message_param = "raises an eyebrow at %t." + +/datum/emote/living/carbon/human/grumble + key = "grumble" + key_third_person = "grumbles" + message = "grumbles!" + message_mime = "seems to grumble!" + message_postfix = "at %t!" + muzzled_noises = list("bothered") + emote_type = EMOTE_AUDIBLE | EMOTE_MOUTH + +/datum/emote/living/carbon/human/hug + key = "hug" + key_third_person = "hugs" + message = "hugs themselves." + message_param = "hugs %t." + hands_use_check = TRUE + +/datum/emote/living/carbon/human/mumble + key = "mumble" + key_third_person = "mumbles" + message = "mumbles!" + message_mime = "seems to be speaking sweet nothings!" + message_postfix = "at %t!" + emote_type = EMOTE_AUDIBLE | EMOTE_MOUTH + +/datum/emote/living/carbon/human/nod + key = "nod" + key_third_person = "nods" + message = "nods their head." + message_param = "nods their head at %t." + +/datum/emote/living/carbon/human/scream + key = "scream" + key_third_person = "screams" + message = "screams!" + message_mime = "acts out a scream!" + message_postfix = "at %t!" + muzzled_noises = list("very loud") + emote_type = EMOTE_SOUND | EMOTE_MOUTH + only_forced_audio = FALSE + vary = TRUE + age_based = TRUE + cooldown = 5 SECONDS + mob_type_blacklist_typecache = list( + /mob/living/carbon/human/monkey, // screech instead + /mob/living/silicon // Robot sounds + ) + +/datum/emote/living/carbon/human/scream/select_message_type(mob/user, msg, intentional) + . = ..() + var/mob/living/carbon/human/H = user + if(H.dna.species?.scream_verb) + if(H.mind?.miming) + return "[H.dna.species?.scream_verb] silently!" + else + return "[H.dna.species?.scream_verb]!" + +/datum/emote/living/carbon/human/scream/get_sound(mob/living/user) + var/mob/living/carbon/human/human = user + if(human.mind?.miming || !istype(human)) + return + if(human.gender == FEMALE) + return human.dna.species.female_scream_sound + else + return human.dna.species.male_scream_sound + +/datum/emote/living/carbon/human/gasp + key = "gasp" + key_third_person = "gasps" + message = "gasps!" + message_mime = "appears to be gasping!" + emote_type = EMOTE_SOUND // Don't make this one a mouth emote since we don't want it to be caught by nobreath + age_based = TRUE + unintentional_stat_allowed = UNCONSCIOUS + volume = 100 + +/datum/emote/living/carbon/human/gasp/get_sound(mob/user) + var/mob/living/carbon/human/H = user + if(H.is_muzzled()) + // If you're muzzled you're not making noise + return + + if(H.health > 0) + return H.dna.species.gasp_sound + + if(H.gender == FEMALE) + return pick(H.dna.species.female_dying_gasp_sounds) + else + return pick(H.dna.species.male_dying_gasp_sounds) + +/datum/emote/living/carbon/human/shake + key = "shake" + key_third_person = "shakes" + message = "shakes their head." + message_param = "shakes their head at %t." + +/datum/emote/living/carbon/human/pale + key = "pale" + message = "goes pale for a second." + +/datum/emote/living/carbon/human/raise + key = "raise" + key_third_person = "raises" + message = "raises a hand." + hands_use_check = TRUE + +/datum/emote/living/carbon/human/salute + key = "salute" + key_third_person = "salutes" + message = "salutes." + message_param = "salutes to %t." + hands_use_check = TRUE + +/datum/emote/living/carbon/human/shrug + key = "shrug" + key_third_person = "shrugs" + message = "shrugs." + +/datum/emote/living/carbon/human/sniff + key = "sniff" + key_third_person = "sniff" + message = "sniffs." + emote_type = EMOTE_AUDIBLE + +/datum/emote/living/carbon/human/johnny + key = "johnny" + message = "takes a drag from a cigarette and blows their own name out in smoke." + message_param = "dummy" // Gets handled in select_param + emote_type = EMOTE_AUDIBLE + target_behavior = EMOTE_TARGET_BHVR_DEFAULT_TO_BASE + emote_target_type = EMOTE_TARGET_MOB + +/datum/emote/living/carbon/human/johnny/select_param(mob/user, params) + if(!params) + return message + var/mob/target = find_target(user, params, EMOTE_TARGET_MOB) + if(!target) + return message + var/msg = message + if(user.mind?.miming) + msg = "takes a drag from a cigarette and blows \"[target.name]\" out in smoke." + else + msg = "says, \"[target.name], please. They had a family.\" [user] takes a drag from a cigarette and blows [user.p_their()] name out in smoke." + return msg + +/datum/emote/living/carbon/human/johnny/run_emote(mob/user, params, type_override, intentional) + var/mob/living/carbon/human/H = user + if(!istype(H.wear_mask, /obj/item/clothing/mask/cigarette)) + to_chat(user, "You can't be that cool without a cigarette between your lips.") + return TRUE + + var/obj/item/clothing/mask/cigarette/cig = H.wear_mask + + if(!cig.lit) + to_chat(user, "You have to light that [cig] first, cool cat.") + return TRUE + + if(H.getOxyLoss() > 30) + user.visible_message( + "[user] gasps for air and swallows their cigarette!", + "You gasp for air and accidentally swallow your [cig]!" + ) + if(cig.lit) + to_chat(user, "The lit [cig] burns on the way down!") + user.unEquip(cig) + qdel(cig) + H.adjustFireLoss(5) + return TRUE + return ..() + +/datum/emote/living/carbon/human/sneeze + key = "sneeze" + key_third_person = "sneezes" + message = "sneezes." + muzzled_noises = list("strange", "sharp") + emote_type = EMOTE_SOUND | EMOTE_MOUTH + volume = 70 + +/datum/emote/living/carbon/human/sneeze/get_sound(mob/user) + var/mob/living/carbon/human/H = user + if(H.gender == FEMALE) + return H.dna.species.female_sneeze_sound + else + return H.dna.species.male_sneeze_sound + +/datum/emote/living/carbon/human/slap + key = "slap" + key_third_person = "slaps" + hands_use_check = TRUE + cooldown = 3 SECONDS // to prevent endless table slamming + +/datum/emote/living/carbon/human/slap/run_emote(mob/user, params, type_override, intentional) + . = ..() + if(!.) + return FALSE + var/obj/item/slapper/N = new(user) + if(user.put_in_hands(N)) + to_chat(user, "You ready your slapping hand.") + else + qdel(N) + to_chat(user, "You're incapable of slapping in your current state.") + +/datum/emote/living/carbon/human/wink + key = "wink" + key_third_person = "winks" + message = "winks." + +/datum/emote/living/carbon/human/highfive + key = "highfive" + key_third_person = "highfives" + message = "requests a highfive." + hands_use_check = TRUE + cooldown = 3 SECONDS + +/datum/emote/living/carbon/human/highfive/can_run_emote(mob/user, status_check, intentional) + . = ..() + var/mob/living/carbon/user_carbon = user + if(user_carbon.restrained()) + return FALSE + +/datum/emote/living/carbon/human/highfive/proc/wiz_cleanup(mob/user, mob/highfived) + user.status_flags &= ~GODMODE + highfived.status_flags &= ~GODMODE + +/datum/emote/living/carbon/human/highfive/run_emote(mob/user, params, type_override, intentional) + var/mob/living/carbon/user_carbon = user + if(user_carbon.has_status_effect(STATUS_EFFECT_HIGHFIVE)) + user.visible_message("[user.name] shakes [user.p_their()] hand around slightly, impatiently waiting for someone to high-five them.") + return TRUE + user_carbon.apply_status_effect(STATUS_EFFECT_HIGHFIVE) + for(var/mob/living/L in orange(1)) + if(L.has_status_effect(STATUS_EFFECT_HIGHFIVE) && L != user) + if(iswizard(user) && iswizard(L)) + user.visible_message("[user.name] and [L.name] high-five EPICALLY!") + user_carbon.status_flags |= GODMODE + L.status_flags |= GODMODE + explosion(get_turf(user), 5, 2, 1, 3) + // explosions have a spawn so this makes sure that we don't get gibbed + addtimer(CALLBACK(src, .proc/wiz_cleanup, user_carbon, L), 1) + user_carbon.remove_status_effect(STATUS_EFFECT_HIGHFIVE) + L.remove_status_effect(STATUS_EFFECT_HIGHFIVE) + return TRUE + user.visible_message("[user.name] and [L.name] high-five!") + playsound(user, 'sound/effects/snap.ogg', 50) + user_carbon.remove_status_effect(STATUS_EFFECT_HIGHFIVE) + L.remove_status_effect(STATUS_EFFECT_HIGHFIVE) + return TRUE + return ..() + +/datum/emote/living/carbon/human/handshake + key = "handshake" + message = "holds out their hand." + hands_use_check = TRUE + emote_target_type = EMOTE_TARGET_MOB + target_behavior = EMOTE_TARGET_BHVR_DEFAULT_TO_BASE + +/datum/emote/living/carbon/human/handshake/act_on_target(mob/user, target) + . = ..() + if(!target) + user.visible_message( + "[user] seems to shake hands with empty space.", + "You shake the air's hand." + ) + return EMOTE_ACT_STOP_EXECUTION + + if(!user.Adjacent(target) || !ishuman(target)) + message_param = "extends a hand towards %t." + return TRUE + + var/mob/living/carbon/human/human_target = target + + if(human_target.canmove && !human_target.r_hand && !human_target.restrained()) + message_param = "shakes hands with %t." + else + message_param = "holds out [user.p_their()] hand to %t." + +/datum/emote/living/carbon/human/handshake/run_emote(mob/user, params, type_override, intentional) + var/mob/living/target + for(var/mob/living/A in oview(5, user)) + if(params == A.name) + target = A + + if(!target) + user.visible_message( + "[user] seems to shake hands with empty space.", + "You shake the air's hand." + ) + return TRUE + + if(target.canmove && !target.r_hand && !target.restrained()) + message_param = "shakes hands with %t." + else + message_param = "holds out [user.p_their()] hand to %t." + + return ..() + +/datum/emote/living/carbon/human/snap + key = "snap" + key_third_person = "snaps" + message = "snaps their fingers." + message_param = "snaps their fingers at %t." + sound = "sound/effects/fingersnap.ogg" + emote_type = EMOTE_SOUND + +/datum/emote/living/carbon/human/snap/run_emote(mob/user, params, type_override, intentional) + + var/mob/living/carbon/human/H = user + var/obj/item/organ/external/LH = H.get_organ("l_hand") + var/obj/item/organ/external/RH = H.get_organ("r_hand") + var/left_hand_good = FALSE + var/right_hand_good = FALSE + if(LH && !(LH.status & ORGAN_SPLINTED) && !(LH.status & ORGAN_BROKEN)) + left_hand_good = TRUE + if(RH && !(RH.status & ORGAN_SPLINTED) && !(RH.status & ORGAN_BROKEN)) + right_hand_good = TRUE + + if(!left_hand_good && !right_hand_good) + to_chat(user, "You need at least one hand in good working order to snap your fingers.") + return TRUE + + if(prob(5)) + user.visible_message("[user] snaps [p_their()] fingers right off!") + playsound(user.loc, 'sound/effects/snap.ogg', 50, 1) + return TRUE + return ..() + +/datum/emote/living/carbon/human/fart + key = "fart" + key_third_person = "farts" + message = "farts." + message_param = "farts in %t's general direction." + emote_type = EMOTE_AUDIBLE + +/datum/emote/living/carbon/human/fart/run_emote(mob/user, params, type_override, intentional) + var/farted_on_something = FALSE + for(var/atom/A in get_turf(user)) + farted_on_something = A.fart_act(user) || farted_on_something + if(!farted_on_something) + return ..() + +/datum/emote/living/carbon/sign/signal + key = "signal" + key_third_person = "signals" + message_param = "raises %t fingers." + param_desc = "number(0-10)" + mob_type_allowed_typecache = list(/mob/living/carbon/human) + hands_use_check = TRUE + + +///////// +// Species-specific emotes + +/datum/emote/living/carbon/human/wag + key = "wag" + key_third_person = "wags" + message = "starts wagging their tail." + emote_type = EMOTE_VISIBLE | EMOTE_FORCE_NO_RUNECHAT + species_type_whitelist_typecache = list( + /datum/species/unathi, + /datum/species/vulpkanin, + /datum/species/tajaran, + /datum/species/vox + ) + +/datum/emote/living/carbon/human/proc/can_wag(mob/user) + var/mob/living/carbon/human/H = user + if(!(H.dna.species.bodyflags & TAIL_WAGGING)) + return FALSE + var/obscured = H.wear_suit && (H.wear_suit.flags_inv & HIDETAIL) + if(!istype(H)) + return FALSE + if(istype(H.body_accessory, /datum/body_accessory/tail)) + if(!H.body_accessory.try_restrictions(user)) + return FALSE + + if(H.dna.species.bodyflags & TAIL_WAGGING && obscured) + return FALSE + + return TRUE + +/datum/emote/living/carbon/human/wag/run_emote(mob/user, params, type_override, intentional) + . = ..() + if(!.) + return FALSE + var/mob/living/carbon/human/H = user + H.start_tail_wagging() + + +/datum/emote/living/carbon/human/wag/can_run_emote(mob/user, status_check = TRUE, intentional) + if(!..()) + return FALSE + + if(!can_wag(user)) + return FALSE + + return TRUE + +/datum/emote/living/carbon/human/wag/stop + key = "swag" // B) + key_third_person = "swags" + message = "stops wagging their tail." + +/datum/emote/living/carbon/human/wag/stop/run_emote(mob/user, params, type_override, intentional) + . = ..() + if(!.) + return FALSE + var/mob/living/carbon/human/H = user + H.stop_tail_wagging() + +///Snowflake emotes only for le epic chimp +/datum/emote/living/carbon/human/monkey + species_type_whitelist_typecache = list(/datum/species/monkey) + + +// Note: subtype of human scream, not monkey, so we need the overrides. +/datum/emote/living/carbon/human/scream/screech + key = "screech" + key_third_person = "screeches" + message = "screeches!" + message_param = "screeches at %t!" + vary = FALSE + mob_type_blacklist_typecache = list() + mob_type_allowed_typecache = list(/mob/living/carbon/human/monkey) + species_type_whitelist_typecache = list(/datum/species/monkey) + +/datum/emote/living/carbon/human/scream/screech/roar + key = "roar" + key_third_person = "roars" + message = "roars!" + message_param = "roars at %t!" + +/datum/emote/living/carbon/human/monkey/gnarl + key = "gnarl" + key_third_person = "gnarls" + message = "gnarls and shows their teeth!" + message_param = "gnarls and shows their teeth at %t." + emote_type = EMOTE_AUDIBLE | EMOTE_MOUTH + +/datum/emote/living/carbon/human/monkey/roll + key = "roll" + key_third_person = "rolls" + message = "rolls." + hands_use_check = TRUE + +/datum/emote/living/carbon/human/monkey/roll/run_emote(mob/user, params, type_override, intentional) + . = ..() + if(.) + user.SpinAnimation(10, 1) + +/datum/emote/living/carbon/human/monkey/scratch + key = "scratch" + key_third_person = "scratches" + message = "scratches." + hands_use_check = TRUE + +/datum/emote/living/carbon/human/monkey/tail + key = "tail" + message = "waves their tail." + +/////// +// More specific human species emotes + +/datum/emote/living/carbon/human/flap + key = "flap" + key_third_person = "flaps" + message = "flaps their wings." + species_type_whitelist_typecache = list(/datum/species/moth) + +/datum/emote/living/carbon/human/flap/angry + key = "aflap" + key_third_person = "aflaps" + message = "flaps their wings ANGRILY!" + +/datum/emote/living/carbon/human/flutter + key = "flutter" + key_third_person = "flutters" + message = "flutters their wings." + species_type_whitelist_typecache = list(/datum/species/moth) + +/datum/emote/living/carbon/human/quill + key = "quill" + key_third_person = "quills" + message = "rustles their quills." + message_param = "rustles their quills at %t." + emote_type = EMOTE_SOUND + age_based = TRUE + // Credit to sound-ideas (freesfx.co.uk) for the sound. + sound = "sound/effects/voxrustle.ogg" + species_type_whitelist_typecache = list(/datum/species/vox) + +/datum/emote/living/carbon/human/warble + key = "warble" + key_third_person = "warbles" + message = "warbles." + message_param = "warbles at %t." + emote_type = EMOTE_SOUND + age_based = TRUE + // Copyright CC BY 3.0 alienistcog (freesound.org) for the sound. + sound = "sound/effects/warble.ogg" + species_type_whitelist_typecache = list(/datum/species/skrell) + +/datum/emote/living/carbon/human/clack + key = "clack" + key_third_person = "clacks" + message = "clacks their mandibles." + message_param = "clacks their mandibles at %t." + species_type_whitelist_typecache = list(/datum/species/kidan) + emote_type = EMOTE_SOUND + audio_cooldown = 3 SECONDS + age_based = TRUE + // Credit to DrMinky (freesound.org) for the sound. + sound = "sound/effects/Kidanclack.ogg" + +/datum/emote/living/carbon/human/clack/run_emote(mob/user, params, type_override, intentional) + . = ..() + if(!.) + return FALSE + mineral_scan_pulse(get_turf(src), range = world.view) + +/datum/emote/living/carbon/human/clack/click + key = "click" + key_third_person = "clicks" + message = "clicks their mandibles." + message_param = "clicks their mandibles at %t." + // Credit to DrMinky (freesound.org) for the sound. + sound = "sound/effects/Kidanclack2.ogg" + +/datum/emote/living/carbon/human/drask_talk + species_type_whitelist_typecache = list(/datum/species/drask) + emote_type = EMOTE_SOUND + age_based = TRUE + sound = "sound/voice/drasktalk.ogg" + +/datum/emote/living/carbon/human/drask_talk/drone + key = "drone" + key_third_person = "drones" + message = "drones." + message_param = "drones at %t." + +/datum/emote/living/carbon/human/drask_talk/hum + key = "hum" + key_third_person = "hums" + message = "hums." + message_param = "hums at %t." + +/datum/emote/living/carbon/human/drask_talk/rumble + key = "rumble" + key_third_person = "rumbles" + message = "rumbles." + message_param = "rumbles at %t." + +/datum/emote/living/carbon/human/hiss + key = "hiss" + key_third_person = "hisses" + message = "hisses." + message_param = "hisses at %t." + species_type_whitelist_typecache = list(/datum/species/unathi) + emote_type = EMOTE_SOUND | EMOTE_MOUTH + age_based = TRUE + // Credit to Jamius (freesound.org) for the sound. + sound = "sound/effects/unathihiss.ogg" + muzzled_noises = list("weak hissing") + +/datum/emote/living/carbon/human/creak + key = "creak" + key_third_person = "creaks" + message = "creaks." + message_param = "creaks at %t." + emote_type = EMOTE_SOUND + age_based = TRUE + species_type_whitelist_typecache = list(/datum/species/diona) + sound = "sound/voice/dionatalk1.ogg" + +/datum/emote/living/carbon/human/squish + key = "squish" + key_third_person = "squishes" + message = "squishes." + message_param = "squishes at %t." + emote_type = EMOTE_SOUND + age_based = TRUE + // Credit to DrMinky (freesound.org) for the sound. + sound = "sound/effects/slime_squish.ogg" + +/datum/emote/living/carbon/human/squish/can_run_emote(mob/user, status_check, intentional) + . = ..() + if(!.) + return FALSE + if(isslimeperson(src)) //Only Slime People can squish + return TRUE + else + var/mob/living/carbon/human/H = user + for(var/obj/item/organ/external/L in H.bodyparts) // if your limbs are squishy you can squish too! + if(istype(L.dna.species, /datum/species/slime)) + return TRUE + return FALSE + +/datum/emote/living/carbon/human/howl + key = "howl" + key_third_person = "howls" + message = "howls." + message_mime = "acts out a howl." + message_param = "howls at %t." + species_type_whitelist_typecache = list(/datum/species/vulpkanin) + emote_type = EMOTE_SOUND | EMOTE_MOUTH + age_based = TRUE + sound = "sound/goonstation/voice/howl.ogg" + muzzled_noises = list("very loud") + volume = 100 + cooldown = 10 SECONDS + +/datum/emote/living/carbon/human/growl + key = "growl" + key_third_person = "growls" + message = "growls." + message_mime = "growls silently." + message_param = "growls at %t." + species_type_whitelist_typecache = list(/datum/species/vulpkanin) + sound = "growls" // what the fuck why is this just top level + volume = 80 + muzzled_noises = list("annoyed") + emote_type = EMOTE_SOUND | EMOTE_MOUTH + +/datum/emote/living/carbon/human/rattle + key = "rattle" + key_third_person = "rattles" + message = "rattles their bones." + message_param = "rattles their bones at %t." + species_type_whitelist_typecache = list(/datum/species/skeleton, /datum/species/plasmaman) diff --git a/code/modules/mob/living/carbon/human/human_organs.dm b/code/modules/mob/living/carbon/human/human_organs.dm index 5d937cbb863..3cd8d49799e 100644 --- a/code/modules/mob/living/carbon/human/human_organs.dm +++ b/code/modules/mob/living/carbon/human/human_organs.dm @@ -67,7 +67,7 @@ if(!(lying || resting)) if(!HAS_TRAIT(src, TRAIT_NOPAIN)) emote("scream") - custom_emote(1, "collapses!") + emote("collapses") Weaken(10 SECONDS) //can't emote while weakened, apparently. @@ -93,7 +93,7 @@ continue var/emote_scream = pick("screams in pain and ", "lets out a sharp cry and ", "cries out and ") - custom_emote(1, "[HAS_TRAIT(src, TRAIT_NOPAIN) ? "" : emote_scream ]drops what [p_they()] [p_were()] holding in [p_their()] [E.name]!") + custom_emote(EMOTE_VISIBLE, "[HAS_TRAIT(src, TRAIT_NOPAIN) ? "" : emote_scream ]drops what [p_they()] [p_were()] holding in [p_their()] [E.name]!") else if(E.is_malfunctioning()) @@ -108,7 +108,7 @@ if(!unEquip(r_hand)) continue - custom_emote(1, "drops what [p_they()] [p_were()] holding, [p_their()] [E.name] malfunctioning!") + custom_emote(EMOTE_VISIBLE, "drops what [p_they()] [p_were()] holding, [p_their()] [E.name] malfunctioning!") do_sparks(5, 0, src) diff --git a/code/modules/mob/living/death.dm b/code/modules/mob/living/death.dm index 35e28b527e1..621f61ce336 100644 --- a/code/modules/mob/living/death.dm +++ b/code/modules/mob/living/death.dm @@ -62,7 +62,7 @@ SetLoseBreath(0) if(!gibbed && deathgasp_on_death) - emote("deathgasp", force = TRUE) + emote("deathgasp") if(mind && suiciding) mind.suicided = TRUE diff --git a/code/modules/mob/living/living_defense.dm b/code/modules/mob/living/living_defense.dm index 78034459479..8e9b195e0c3 100644 --- a/code/modules/mob/living/living_defense.dm +++ b/code/modules/mob/living/living_defense.dm @@ -317,7 +317,7 @@ /mob/living/attack_animal(mob/living/simple_animal/M) M.face_atom(src) if((M.a_intent == INTENT_HELP && M.ckey) || M.melee_damage_upper == 0) - M.custom_emote(1, "[M.friendly] [src].") + M.custom_emote(EMOTE_VISIBLE, "[M.friendly] [src].") return FALSE if(HAS_TRAIT(M, TRAIT_PACIFISM)) to_chat(M, "You don't want to hurt anyone!") diff --git a/code/modules/mob/living/living_emote.dm b/code/modules/mob/living/living_emote.dm new file mode 100644 index 00000000000..50f94777ecf --- /dev/null +++ b/code/modules/mob/living/living_emote.dm @@ -0,0 +1,434 @@ +/datum/emote/living + mob_type_allowed_typecache = /mob/living + mob_type_blacklist_typecache = list( + /mob/living/carbon/brain, // nice try + /mob/living/captive_brain, + /mob/living/silicon, + /mob/living/simple_animal/bot + ) + message_postfix = "at %t." + +/datum/emote/living/should_play_sound(mob/user, intentional) + . = ..() + if(user.mind?.miming) + return FALSE // shh + return . + +/datum/emote/living/blush + key = "blush" + key_third_person = "blushes" + message = "blushes." + +/datum/emote/living/bow + key = "bow" + key_third_person = "bows" + message = "bows." + message_param = "bows to %t." + message_postfix = "to %t." + +/datum/emote/living/burp + key = "burp" + key_third_person = "burps" + message = "burps." + message_mime = "opens their mouth rather obnoxiously." + emote_type = EMOTE_AUDIBLE + muzzled_noises = list("peculiar") + +/datum/emote/living/choke + key = "choke" + key_third_person = "chokes" + message = "chokes!" + message_mime = "clutches their throat desperately!" + emote_type = EMOTE_AUDIBLE + muzzled_noises = list("gagging", "strong") + +/datum/emote/living/collapse + key = "collapse" + key_third_person = "collapses" + message = "collapses!" + emote_type = EMOTE_VISIBLE + +/datum/emote/living/collapse/run_emote(mob/user, params, type_override, intentional) + . = ..() + if(. && isliving(user)) + var/mob/living/L = user + L.Paralyse(4 SECONDS) + +/datum/emote/living/dance + key = "dance" + key_third_person = "dances" + message = "dances around happily." + +/datum/emote/living/jump + key = "jump" + key_third_person = "jumps" + message = "jumps!" + +/datum/emote/living/deathgasp + key = "deathgasp" + key_third_person = "deathgasps" + emote_type = EMOTE_AUDIBLE | EMOTE_VISIBLE // make sure deathgasp gets runechatted regardless + age_based = TRUE + cooldown = 10 SECONDS + volume = 40 + unintentional_stat_allowed = DEAD + message = "seizes up and falls limp, their eyes dead and lifeless..." + message_alien = "seizes up and falls limp, their eyes dead and lifeless..." + message_robot = "shudders violently for a moment before falling still, its eyes slowly darkening." + message_AI = "screeches, its screen flickering as its systems slowly halt." + message_alien = "lets out a waning guttural screech, green blood bubbling from its maw..." + message_larva = "lets out a sickly hiss of air and falls limply to the floor..." + message_monkey = "lets out a faint chimper as it collapses and stops moving..." + message_simple = "stops moving..." + + mob_type_blacklist_typecache = list( + /mob/living/carbon/brain, + /mob/living/captive_brain + ) + +/datum/emote/living/deathgasp/get_sound(mob/living/user) + . = ..() + + if(isanimal(user)) + var/mob/living/simple_animal/S = user + if(S.deathmessage) + message_simple = S.deathmessage + return S.death_sound + + if(ishuman(user)) + var/mob/living/carbon/human/H = user + if(H.dna.species) + message = H.dna.species.death_message + return pick(H.dna.species.death_sounds) + + if(isalien(user)) + var/mob/living/carbon/alien/A = user + message_alien = A.death_message + return A.death_sound + + if(issilicon(user)) + var/mob/living/silicon/SI = user + return SI.death_sound + +/datum/emote/living/drool + key = "drool" + key_third_person = "drools" + message = "drools." + unintentional_stat_allowed = UNCONSCIOUS + +/datum/emote/living/quiver + key = "quiver" + key_third_person = "quivers" + message = "quivers." + unintentional_stat_allowed = UNCONSCIOUS + +/datum/emote/living/frown + key = "frown" + key_third_person = "frowns" + message = "frowns." + message_param = "frowns at %t." + +/datum/emote/living/gag + key = "gag" + key_third_person = "gags" + message = "gags." + message_mime = "appears to gag." + message_param = "gags at %t." + emote_type = EMOTE_AUDIBLE + +/datum/emote/living/glare + key = "glare" + key_third_person = "glares" + message = "glares." + message_param = "glares at %t." + +/datum/emote/living/grin + key = "grin" + key_third_person = "grins" + message = "grins." + +/datum/emote/living/grimace + key = "grimace" + key_third_person = "grimaces" + message = "grimaces." + +/datum/emote/living/look + key = "look" + key_third_person = "looks" + message = "looks." + message_param = "looks at %t." + +/datum/emote/living/bshake + key = "bshake" + key_third_person = "bshakes" + message = "shakes." + unintentional_stat_allowed = UNCONSCIOUS + +/datum/emote/living/shudder + key = "shudder" + key_third_person = "shudders" + message = "shudders." + unintentional_stat_allowed = UNCONSCIOUS + +/datum/emote/living/point + key = "point" + key_third_person = "points" + message = "points." + message_param = "points at %t." + hands_use_check = TRUE + target_behavior = EMOTE_TARGET_BHVR_USE_PARAMS_ANYWAY + emote_target_type = EMOTE_TARGET_ANY + +/datum/emote/living/point/act_on_target(mob/user, target) + if(!target) + return + + user.pointed(target) + +/datum/emote/living/point/run_emote(mob/user, params, type_override, intentional) + // again, /tg/ has some flavor when pointing (like if you only have one leg) that applies debuffs + // but it's so common that seems unnecessary for here + message_param = initial(message_param) // reset + if(ishuman(user)) + var/mob/living/carbon/human/H = user + if(!H.has_left_hand() && !H.has_right_hand()) + if(H.get_num_legs() != 0) + message_param = "tries to point at %t with a leg." + else + // nugget + message_param = "Invalid emote.") + return TRUE + return FALSE + +/datum/emote/living/custom/run_emote(mob/user, params, type_override = null, intentional = FALSE) + var/custom_emote + var/custom_emote_type + + if(QDELETED(user)) + return FALSE + else if(check_mute(user?.client?.ckey, MUTE_IC)) + to_chat(user, "You cannot send IC messages (muted).") + return FALSE + else if(!params) + custom_emote = copytext(sanitize(input("Choose an emote to display.") as text|null), 1, MAX_MESSAGE_LEN) + if(custom_emote && !check_invalid(user, custom_emote)) + var/type = input("Is this a visible or hearable emote?") as null|anything in list("Visible", "Hearable") + switch(type) + if("Visible") + custom_emote_type = EMOTE_VISIBLE + if("Hearable") + custom_emote_type = EMOTE_AUDIBLE + else + to_chat(user,"Unable to use this emote, must be either hearable or visible.") + return + else + custom_emote = params + if(type_override) + custom_emote_type = type_override + + message = custom_emote + emote_type = custom_emote_type + . = ..() + message = initial(message) + emote_type = initial(emote_type) + +/datum/emote/living/custom/replace_pronoun(mob/user, message) + // Trust the user said what they mean + return message + diff --git a/code/modules/mob/living/say.dm b/code/modules/mob/living/say.dm index ff676645ea8..e5e2b4395fd 100644 --- a/code/modules/mob/living/say.dm +++ b/code/modules/mob/living/say.dm @@ -125,7 +125,7 @@ GLOBAL_LIST_EMPTY(channel_to_radio_key) var/message_mode = parse_message_mode(message, "headset") if(copytext(message, 1, 2) == "*") - return emote(copytext(message, 2)) + return emote(copytext(message, 2), intentional = TRUE) //parse the radio code and consume it if(message_mode) @@ -295,44 +295,6 @@ GLOBAL_LIST_EMPTY(channel_to_radio_key) /mob/living/proc/GetVoice() return name -/mob/living/emote(act, type, message, force) //emote code is terrible, this is so that anything that isn't already snowflaked to shit can call the parent and handle emoting sanely - if(client) - if(check_mute(client.ckey, MUTE_IC)) - to_chat(src, "You cannot speak in IC (Muted).") - return - - if(stat) - return 0 - - if(..()) - return 1 - - if(act && type && message) //parent call - log_emote(message, src) - - for(var/mob/M in GLOB.dead_mob_list) - if(!M.client) - continue //skip monkeys and leavers - - if(isnewplayer(M)) - continue - - if(isobserver(M) && M.get_preference(PREFTOGGLE_CHAT_GHOSTSIGHT) && !(M in viewers(src, null)) && client) // The client check makes sure people with ghost sight don't get spammed by simple mobs emoting. - M.show_message(message) - - switch(type) - if(1) //Visible - visible_message(message) - return 1 - if(2) //Audible - audible_message(message) - return 1 - - else //everything else failed, emote is probably invalid - if(act == "help") - return //except help, because help is handled individually - to_chat(src, "Unusable emote '[act]'. Say *help for a list.") - /mob/living/whisper(message as text) message = trim_strip_html_properly(message) diff --git a/code/modules/mob/living/silicon/death.dm b/code/modules/mob/living/silicon/death.dm index 63b206debba..94ab4d20292 100644 --- a/code/modules/mob/living/silicon/death.dm +++ b/code/modules/mob/living/silicon/death.dm @@ -45,6 +45,6 @@ /mob/living/silicon/death(gibbed) . = ..() - if(!gibbed) + if(. && !gibbed) if(death_sound) playsound(get_turf(src), death_sound, 200, 1) diff --git a/code/modules/mob/living/silicon/emote.dm b/code/modules/mob/living/silicon/emote.dm deleted file mode 100644 index 1b1ee4f7f48..00000000000 --- a/code/modules/mob/living/silicon/emote.dm +++ /dev/null @@ -1,81 +0,0 @@ -/mob/living/silicon/emote(act, m_type=1, message = null, force) - var/param = null - if(findtext(act, "-", 1, null)) - var/t1 = findtext(act, "-", 1, null) - param = copytext(act, t1 + 1, length(act) + 1) - act = copytext(act, 1, t1) - - //Emote Cooldown System (it's so simple!) - //handle_emote_CD() located in [code\modules\mob\emote.dm] - var/on_CD = 0 - act = lowertext(act) - switch(act) - //Cooldown-inducing emotes - if("scream", "screams") - on_CD = handle_emote_CD(50) //longer cooldown - if("ping","pings","buzz","buzzs","buzzes","beep","beeps","yes","no", "buzz2") - //halt is exempt because it's used to stop criminal scum //WHOEVER THOUGHT THAT WAS A GOOD IDEA IS GOING TO GET SHOT. - on_CD = handle_emote_CD() //proc located in code\modules\mob\emote.dm - //Everything else, including typos of the above emotes - else - on_CD = 0 //If it doesn't induce the cooldown, we won't check for the cooldown - - if(!force && on_CD == 1) // Check if we need to suppress the emote attempt. - return // Suppress emote, you're still cooling off. - //--FalseIncarnate - - switch(act) - if("ping","pings") - var/M = handle_emote_param(param) - - message = "[src] pings[M ? " at [M]" : ""]." - playsound(src.loc, 'sound/machines/ping.ogg', 50, 0) - m_type = 2 - - if("buzz","buzzs","buzzes") - var/M = handle_emote_param(param) - - message = "[src] buzzes[M ? " at [M]" : ""]." - playsound(src.loc, 'sound/machines/buzz-sigh.ogg', 50, 0) - m_type = 2 - - if("beep","beeps") - var/M = handle_emote_param(param) - - message = "[src] beeps[M ? " at [M]" : ""]." - playsound(src.loc, 'sound/machines/twobeep.ogg', 50, 0) - m_type = 2 - - if("yes") - var/M = handle_emote_param(param) - - message = "[src] emits an affirmative blip[M ? " at [M]" : ""]." - playsound(src.loc, 'sound/machines/synth_yes.ogg', 50, 0) - m_type = 2 - - if("no") - var/M = handle_emote_param(param) - - message = "[src] emits a negative blip[M ? " at [M]" : ""]." - playsound(src.loc, 'sound/machines/synth_no.ogg', 50, 0) - m_type = 2 - - if("scream", "screams") - var/M = handle_emote_param(param) - - message = "[src] screams[M ? " at [M]" : ""]!" - playsound(src.loc, 'sound/goonstation/voice/robot_scream.ogg', 80, 0) - m_type = 2 - - if("buzz2") - var/M = handle_emote_param(param) - - message = "[src] emits an irritated buzzing sound[M ? " at [M]" : ""]." - playsound(src.loc, 'sound/machines/buzz-two.ogg', 50, 0) - m_type = 2 - - - if("help") - to_chat(src, "yes, no, beep, ping, buzz, scream, buzz2") - - ..() diff --git a/code/modules/mob/living/silicon/robot/death.dm b/code/modules/mob/living/silicon/robot/death.dm index 11b0cc600d8..af42deef6ab 100644 --- a/code/modules/mob/living/silicon/robot/death.dm +++ b/code/modules/mob/living/silicon/robot/death.dm @@ -49,9 +49,6 @@ /mob/living/silicon/robot/death(gibbed) if(can_die()) - if(!gibbed && deathgasp_on_death) - emote("deathgasp", force = TRUE) - if(module) module.handle_death(src, gibbed) diff --git a/code/modules/mob/living/silicon/robot/drone/drone_say.dm b/code/modules/mob/living/silicon/robot/drone/drone_say.dm index bf89872a2b3..b03afdaf108 100644 --- a/code/modules/mob/living/silicon/robot/drone/drone_say.dm +++ b/code/modules/mob/living/silicon/robot/drone/drone_say.dm @@ -1,9 +1,3 @@ -/mob/living/silicon/robot/drone/say(message) - if(copytext(message, 1, 2) == "*") - return emote(copytext(message, 2)) - - return ..() - /mob/living/silicon/robot/drone/whisper_say(list/message_pieces) say(multilingual_to_message(message_pieces)) //drones do not get to whisper, only speak normally return 1 diff --git a/code/modules/mob/living/silicon/robot/emote.dm b/code/modules/mob/living/silicon/robot/emote.dm deleted file mode 100644 index 36dea666cc5..00000000000 --- a/code/modules/mob/living/silicon/robot/emote.dm +++ /dev/null @@ -1,183 +0,0 @@ -/mob/living/silicon/robot/emote(act, m_type=1, message = null, force) - var/param = null - if(findtext(act, "-", 1, null)) - var/t1 = findtext(act, "-", 1, null) - param = copytext(act, t1 + 1, length(act) + 1) - act = copytext(act, 1, t1) - - //Emote Cooldown System (it's so simple!) - //handle_emote_CD() located in [code\modules\mob\emote.dm] - var/on_CD = 0 - act = lowertext(act) - switch(act) - //Cooldown-inducing emotes - if("law","flip","flips","halt") //halt is exempt because it's used to stop criminal scum //WHOEVER THOUGHT THAT WAS A GOOD IDEA IS GOING TO GET SHOT. - on_CD = handle_emote_CD() //proc located in code\modules\mob\emote.dm - if("spin","spins") - on_CD = handle_emote_CD(5 SECONDS) - //Everything else, including typos of the above emotes - else - on_CD = 0 //If it doesn't induce the cooldown, we won't check for the cooldown - - if(!force && on_CD == 1) // Check if we need to suppress the emote attempt. - return // Suppress emote, you're still cooling off. - //--FalseIncarnate - - switch(act) - - if("salute","salutes") - if(!src.buckled) - var/M = null - if(param) - for(var/mob/A in view(null, null)) - if(param == A.name) - M = A - break - if(!M) - param = null - - if(param) - message = "[src] salutes to [param]." - else - message = "[src] salutes." - m_type = 1 - if("bow","bows") - if(!src.buckled) - var/M = null - if(param) - for(var/mob/A in view(null, null)) - if(param == A.name) - M = A - break - if(!M) - param = null - - if(param) - message = "[src] bows to [param]." - else - message = "[src] bows." - m_type = 1 - - if("clap","claps") - if(!src.restrained()) - message = "[src] claps." - m_type = 2 - if("flap","flaps") - if(!src.restrained()) - message = "[src] flaps its wings." - m_type = 2 - - if("aflap","aflaps") - if(!src.restrained()) - message = "[src] flaps its wings ANGRILY!" - m_type = 2 - - if("twitch") - message = "[src] twitches violently." - m_type = 1 - - if("twitch_s","twitches") - message = "[src] twitches." - m_type = 1 - - if("nod","nods") - message = "[src] nods." - m_type = 1 - - if("deathgasp") - message = "[src] shudders violently for a moment, then becomes motionless, its eyes slowly darkening." - m_type = 1 - - if("glare","glares") - var/M = null - if(param) - for(var/mob/A in view(null, null)) - if(param == A.name) - M = A - break - if(!M) - param = null - - if(param) - message = "[src] glares at [param]." - else - message = "[src] glares." - - if("stare","stares") - var/M = null - if(param) - for(var/mob/A in view(null, null)) - if(param == A.name) - M = A - break - if(!M) - param = null - - if(param) - message = "[src] stares at [param]." - else - message = "[src] stares." - - if("look","looks") - var/M = null - if(param) - for(var/mob/A in view(null, null)) - if(param == A.name) - M = A - break - - if(!M) - param = null - - if(param) - message = "[src] looks at [param]." - else - message = "[src] looks." - m_type = 1 - - - if("law") - if(istype(module,/obj/item/robot_module/security)) - message = "[src] shows its legal authorization barcode." - - playsound(src.loc, 'sound/voice/biamthelaw.ogg', 50, 0) - m_type = 2 - else - to_chat(src, "You are not THE LAW, pal.") - - if("halt") - if(istype(module,/obj/item/robot_module/security)) - message = "[src]'s speakers skreech, \"Halt! Security!\"." - - playsound(src.loc, 'sound/voice/halt.ogg', 50, 0) - m_type = 2 - else - to_chat(src, "You are not security.") - - if("flip","flips") - m_type = 1 - message = "[src] does a flip!" - src.SpinAnimation(5,1) - - if("spin","spins") - spin(20, 1) - message = "[src] spins!" - - if("help") - to_chat(src, "salute, bow-(none)/mob, clap, flap, aflap, twitch, twitches, nod, deathgasp, glare-(none)/mob, stare-(none)/mob, look,\n law, halt, flip, spin") - - ..() - -/mob/living/silicon/robot/verb/powerwarn() - set category = "Robot Commands" - set name = "Power Warning" - - if(handle_emote_CD(50)) - return - - if(!is_component_functioning("power cell") || !cell || !cell.charge) - visible_message("The power warning light on [src] flashes urgently.",\ - "You announce you are operating in low power mode.") - playsound(loc, 'sound/machines/buzz-two.ogg', 50, 0) - else - to_chat(src, "You can only use this emote when you're out of charge.") diff --git a/code/modules/mob/living/silicon/robot/robot.dm b/code/modules/mob/living/silicon/robot/robot.dm index 33f70a2ed8e..99e847c6983 100644 --- a/code/modules/mob/living/silicon/robot/robot.dm +++ b/code/modules/mob/living/silicon/robot/robot.dm @@ -1528,3 +1528,19 @@ GLOBAL_LIST_INIT(robot_verbs_default, list( /mob/living/silicon/robot/can_see_reagents() return see_reagents + +/mob/living/silicon/robot/verb/powerwarn() + set category = "Robot Commands" + set name = "Power Warning" + + + + if(!is_component_functioning("power cell") || !cell || !cell.charge) + if(!start_audio_emote_cooldown(10 SECONDS)) + to_chat(src, "The low-power capacitor for your speaker system is still recharging, please try again later.") + return + visible_message("The power warning light on [src] flashes urgently.",\ + "You announce you are operating in low power mode.") + playsound(loc, 'sound/machines/buzz-two.ogg', 50, 0) + else + to_chat(src, "You can only use this emote when you're out of charge.") diff --git a/code/modules/mob/living/silicon/say.dm b/code/modules/mob/living/silicon/say.dm index 047ecbdaf32..78615f43d77 100644 --- a/code/modules/mob/living/silicon/say.dm +++ b/code/modules/mob/living/silicon/say.dm @@ -99,7 +99,7 @@ to_chat(src, "Holopad action relayed, [real_name] [message]") for(var/mob/M in viewers(T.loc)) - M.show_message(rendered, 2) + M.show_message(rendered, EMOTE_VISIBLE) log_emote("(HPAD) [message]", src) else //This shouldn't occur, but better safe then sorry. @@ -107,10 +107,10 @@ return return 1 -/mob/living/silicon/ai/emote(act, type, message, force) +/mob/living/silicon/ai/emote(act, type, message, intentional = TRUE, force_silence = FALSE) var/obj/machinery/hologram/holopad/T = current if(istype(T) && T.masters[src])//Is the AI using a holopad? - src.holopad_emote(message) + holopad_emote(message) else //Emote normally, then. ..() diff --git a/code/modules/mob/living/silicon/silicon_emote.dm b/code/modules/mob/living/silicon/silicon_emote.dm new file mode 100644 index 00000000000..18bc7a2dc03 --- /dev/null +++ b/code/modules/mob/living/silicon/silicon_emote.dm @@ -0,0 +1,97 @@ +/datum/emote/living/silicon + // Humans are allowed for the sake of IPCs + mob_type_allowed_typecache = list(/mob/living/silicon, /mob/living/simple_animal/bot, /mob/living/carbon/human) + mob_type_blacklist_typecache = list() + +/datum/emote/living/silicon/can_run_emote(mob/user, status_check, intentional) + . = ..() + // Let IPCs (and people with robo-heads) make beep-boop noises + if(!.) + return FALSE + + if(ishuman(user)) + var/mob/living/carbon/human/H = user + var/obj/item/organ/external/head/head = H.get_organ(BODY_ZONE_HEAD) + if(head && !head.is_robotic()) + return FALSE + +/datum/emote/living/silicon/scream + key = "scream" + key_third_person = "screams" + message = "screams!" + message_param = "screams at %t!" + emote_type = EMOTE_SOUND | EMOTE_AUDIBLE + vary = TRUE + sound = "sound/goonstation/voice/robot_scream.ogg" + volume = 80 + +/datum/emote/living/silicon/ping + key = "ping" + key_third_person = "pings" + message = "pings." + message_param = "pings at %t." + emote_type = EMOTE_SOUND | EMOTE_AUDIBLE + sound = "sound/machines/ping.ogg" + +/datum/emote/living/silicon/buzz + key = "buzz" + key_third_person = "buzzes" + message = "buzzes." + message_param = "buzzes at %t." + emote_type = EMOTE_SOUND | EMOTE_AUDIBLE + sound = "sound/machines/buzz-sigh.ogg" + +/datum/emote/living/silicon/buzz2 + key = "buzz2" + message = "emits an irritated buzzing sound." + message_param = "emits an irritated buzzing sound at %t." + emote_type = EMOTE_SOUND | EMOTE_AUDIBLE + sound = "sound/machines/buzz-two.ogg" + +/datum/emote/living/silicon/beep + key = "beep" + key_third_person = "beeps" + message = "beeps." + message_param = "beeps at %t." + emote_type = EMOTE_SOUND | EMOTE_AUDIBLE + sound = "sound/machines/twobeep.ogg" + +/datum/emote/living/silicon/yes + key = "yes" + message = "emits an affirmative blip." + message_param = "emits an affirmative blip at %t." + emote_type = EMOTE_SOUND | EMOTE_AUDIBLE + sound = "sound/machines/synth_yes.ogg" + +/datum/emote/living/silicon/no + key = "no" + message = "emits a negative blip." + message_param = "emits a negative blip at %t." + emote_type = EMOTE_SOUND | EMOTE_AUDIBLE + sound = "sound/machines/synth_no.ogg" + +/datum/emote/living/silicon/law + key = "law" + message = "shows its legal authorization barcode." + message_param = "shows %t its legal authorization barcode." + emote_type = EMOTE_SOUND | EMOTE_AUDIBLE + sound = "sound/voice/biamthelaw.ogg" + +/datum/emote/living/silicon/law/can_run_emote(mob/user, status_check, intentional) + . = ..() + var/mob/living/silicon/robot/bot = user + if(!istype(bot) || !istype(bot.module, /obj/item/robot_module/security)) + return FALSE + +/datum/emote/living/silicon/halt + key = "halt" + message = "screeches \"HALT! SECURITY!\" from its speakers." + message_param = "instructs %t to HALT." + emote_type = EMOTE_SOUND + sound = "sound/voice/halt.ogg" + +/datum/emote/living/silicon/halt/can_run_emote(mob/user, status_check, intentional) + . = ..() + var/mob/living/silicon/robot/bot = user + if(!istype(bot) || !istype(bot.module, /obj/item/robot_module/security)) + return FALSE diff --git a/code/modules/mob/living/simple_animal/bot/bot.dm b/code/modules/mob/living/simple_animal/bot/bot.dm index 5d1d1b7f71b..8ec431279b6 100644 --- a/code/modules/mob/living/simple_animal/bot/bot.dm +++ b/code/modules/mob/living/simple_animal/bot/bot.dm @@ -1,4 +1,5 @@ //Defines for bots are now found in code\__DEFINES\bots.dm +// Emotes for bots are shared with silicon, and are found at code/modules/mob/living/silicon/emotes.dm // AI (i.e. game AI, not the AI player) controlled bots /mob/living/simple_animal/bot diff --git a/code/modules/mob/living/simple_animal/bot/emote.dm b/code/modules/mob/living/simple_animal/bot/emote.dm deleted file mode 100644 index c59e044cc67..00000000000 --- a/code/modules/mob/living/simple_animal/bot/emote.dm +++ /dev/null @@ -1,74 +0,0 @@ -/mob/living/simple_animal/bot/emote(act, m_type = 1, message = null, force) - var/param = null - if(findtext(act, "-", 1, null)) - var/t1 = findtext(act, "-", 1, null) - param = copytext(act, t1 + 1, length(act) + 1) - act = copytext(act, 1, t1) - - if(findtext(act,"s",-1) && !findtext(act,"_",-2))//Removes ending s's unless they are prefixed with a '_' - act = copytext(act,1,length(act)) - - //Emote Cooldown System (it's so simple!) - //handle_emote_CD() located in [code\modules\mob\emote.dm] - var/on_CD = 0 - act = lowertext(act) - switch(act) - //Cooldown-inducing emotes - if("scream", "screams") - on_CD = handle_emote_CD(50) //longer cooldown - if("ping","buzz","beep","yes","no") //halt is exempt because it's used to stop criminal scum //WHOEVER THOUGHT THAT WAS A GOOD IDEA IS GOING TO GET SHOT. - on_CD = handle_emote_CD() //proc located in code\modules\mob\emote.dm - //Everything else, including typos of the above emotes - else - on_CD = 0 //If it doesn't induce the cooldown, we won't check for the cooldown - - if(!force && on_CD == 1) // Check if we need to suppress the emote attempt. - return // Suppress emote, you're still cooling off. - //--FalseIncarnate - - switch(act) - if("ping") - var/M = handle_emote_param(param) - - message = "[src] pings[M ? " at [M]" : ""]." - playsound(src.loc, 'sound/machines/ping.ogg', 50, 0) - m_type = 2 - - if("buzz") - var/M = handle_emote_param(param) - - message = "[src] buzzes[M ? " at [M]" : ""]." - playsound(src.loc, 'sound/machines/buzz-sigh.ogg', 50, 0) - m_type = 2 - - if("beep") - var/M = handle_emote_param(param) - - message = "[src] beeps[M ? " at [M]" : ""]." - playsound(src.loc, 'sound/machines/twobeep.ogg', 50, 0) - m_type = 2 - - if("yes") - var/M = handle_emote_param(param) - - message = "[src] emits an affirmative blip[M ? " at [M]" : ""]." - playsound(src.loc, 'sound/machines/synth_yes.ogg', 50, 0) - m_type = 2 - - if("no") - var/M = handle_emote_param(param) - - message = "[src] emits an negative blip[M ? " at [M]" : ""]." - playsound(src.loc, 'sound/machines/synth_no.ogg', 50, 0) - m_type = 2 - - if("scream", "screams") - var/M = handle_emote_param(param) - - message = "[src] screams[M ? " at [M]" : ""]!" - playsound(src.loc, 'sound/goonstation/voice/robot_scream.ogg', 80, 0) - m_type = 2 - - if("help") - to_chat(src, "scream(s), yes, no, beep, buzz, ping") - ..() diff --git a/code/modules/mob/living/simple_animal/friendly/cat.dm b/code/modules/mob/living/simple_animal/friendly/cat.dm index d3fa2a2d753..cca5b3326d2 100644 --- a/code/modules/mob/living/simple_animal/friendly/cat.dm +++ b/code/modules/mob/living/simple_animal/friendly/cat.dm @@ -11,7 +11,6 @@ speak_emote = list("purrs", "meows") emote_hear = list("meows", "mews") emote_see = list("shakes its head", "shivers") - var/meow_sound = 'sound/creatures/cat_meow.ogg' //Used in emote. speak_chance = 1 turns_per_move = 5 see_in_dark = 6 @@ -104,26 +103,26 @@ /mob/living/simple_animal/pet/cat/handle_automated_action() if(!stat && !buckled) if(prob(1)) - custom_emote(1, pick("stretches out for a belly rub.", "wags its tail.", "lies down.")) + custom_emote(EMOTE_VISIBLE, pick("stretches out for a belly rub.", "wags its tail.", "lies down.")) StartResting() else if(prob(1)) - custom_emote(1, pick("sits down.", "crouches on its hind legs.", "looks alert.")) + custom_emote(EMOTE_VISIBLE, pick("sits down.", "crouches on its hind legs.", "looks alert.")) icon_state = "[icon_living]_sit" collar_type = "[initial(collar_type)]_sit" resting = TRUE update_canmove() else if(prob(1)) if(resting) - custom_emote(1, pick("gets up and meows.", "walks around.", "stops resting.")) + custom_emote(EMOTE_VISIBLE, pick("gets up and meows.", "walks around.", "stops resting.")) StopResting() else - custom_emote(1, pick("grooms its fur.", "twitches its whiskers.", "shakes out its coat.")) + custom_emote(EMOTE_VISIBLE, pick("grooms its fur.", "twitches its whiskers.", "shakes out its coat.")) //MICE! if(eats_mice && isturf(loc) && !incapacitated()) for(var/mob/living/simple_animal/mouse/M in view(1, src)) if(!M.stat && Adjacent(M)) - custom_emote(1, "splats \the [M]!") + custom_emote(EMOTE_VISIBLE, "splats \the [M]!") M.death() M.splat() movement_target = null @@ -131,7 +130,7 @@ break for(var/obj/item/toy/cattoy/T in view(1, src)) if(T.cooldown < (world.time - 400)) - custom_emote(1, "bats \the [T] around with its paw!") + custom_emote(EMOTE_VISIBLE, "bats \the [T] around with its paw!") T.cooldown = world.time /mob/living/simple_animal/pet/cat/handle_automated_movement() @@ -155,41 +154,6 @@ stop_automated_movement = 1 walk_to(src,movement_target,0,3) -/mob/living/simple_animal/pet/cat/emote(act, m_type = 1, message = null, force) - if(stat != CONSCIOUS) - return - - var/on_CD = 0 - act = lowertext(act) - switch(act) - if("meow") - on_CD = handle_emote_CD() - if("hiss") - on_CD = handle_emote_CD() - if("purr") - on_CD = handle_emote_CD() - else - on_CD = 0 - - if(!force && on_CD == 1) - return - - switch(act) - if("meow") - message = "[src] [pick(emote_hear)]!" - m_type = 2 //audible - playsound(src, meow_sound, 50, 0.75) - if("hiss") - message = "[src] hisses!" - m_type = 2 - if("purr") - message = "[src] purrs." - m_type = 2 - if("help") - to_chat(src, "scream, meow, hiss, purr") - - ..() - /mob/living/simple_animal/pet/cat/Proc name = "Proc" gender = MALE @@ -215,7 +179,6 @@ icon_living = "Syndicat" icon_dead = "Syndicat_dead" icon_resting = "Syndicat_rest" - meow_sound = null //Need robo-meow. gender = FEMALE faction = list("syndicate") gold_core_spawnable = NO_SPAWN diff --git a/code/modules/mob/living/simple_animal/friendly/diona.dm b/code/modules/mob/living/simple_animal/friendly/diona.dm index 79964fd582d..da8f54c2c8b 100644 --- a/code/modules/mob/living/simple_animal/friendly/diona.dm +++ b/code/modules/mob/living/simple_animal/friendly/diona.dm @@ -33,7 +33,6 @@ melee_damage_upper = 8 attacktext = "bites" attack_sound = 'sound/weapons/bite.ogg' - var/chirp_sound = 'sound/creatures/nymphchirp.ogg' //used in emote speed = 0 stop_automated_movement = 0 @@ -277,31 +276,6 @@ to_chat(src, "You don't have any hands!") return -/mob/living/simple_animal/diona/emote(act, m_type = 1, message = null, force) - if(stat != CONSCIOUS) - return - - var/on_CD = 0 - act = lowertext(act) - switch(act) - if("chirp") - on_CD = handle_emote_CD() - else - on_CD = 0 - - if(!force && on_CD == 1) - return - - switch(act) //IMPORTANT: Emotes MUST NOT CONFLICT anywhere along the chain. - if("chirp") - message = "\The [src] chirps!" - m_type = 2 //audible - playsound(src, chirp_sound, 40, 1, 1) - if("help") - to_chat(src, "scream, chirp") - - ..() - /mob/living/simple_animal/diona/npc_safe(mob/user) if(!jobban_isbanned(user, ROLE_NYMPH)) return TRUE diff --git a/code/modules/mob/living/simple_animal/friendly/dog.dm b/code/modules/mob/living/simple_animal/friendly/dog.dm index 57d0f4d1d3e..afc84e98891 100644 --- a/code/modules/mob/living/simple_animal/friendly/dog.dm +++ b/code/modules/mob/living/simple_animal/friendly/dog.dm @@ -43,40 +43,6 @@ return playsound(src, yelp_sound, 75, TRUE) -/mob/living/simple_animal/pet/dog/emote(act, m_type = 1, message = null, force) - if(incapacitated()) - return - - var/on_CD = 0 - act = lowertext(act) - switch(act) - if("bark") - on_CD = handle_emote_CD() - if("yelp") - on_CD = handle_emote_CD() - else - on_CD = 0 - - if(!force && on_CD == 1) - return - - switch(act) - if("bark") - message = "[src] [pick(src.speak_emote)]!" - m_type = 2 //audible - playsound(src, pick(src.bark_sound), 50, TRUE) - if("yelp") - message = "[src] yelps!" - m_type = 2 //audible - playsound(src, yelp_sound, 75, TRUE) - if("growl") - message = "[src] growls!" - m_type = 2 //audible - if("help") - to_chat(src, "scream, bark, growl") - - ..() - /mob/living/simple_animal/pet/dog/attack_hand(mob/living/carbon/human/M) . = ..() switch(M.a_intent) @@ -90,10 +56,10 @@ if(change > 0) if(M && stat != DEAD) // Added check to see if this mob (the corgi) is dead to fix issue 2454 new /obj/effect/temp_visual/heart(loc) - custom_emote(1, "yaps happily!") + custom_emote(EMOTE_VISIBLE, "yaps happily!") else if(M && stat != DEAD) // Same check here, even though emote checks it as well (poor form to check it only in the help case) - custom_emote(1, "growls!") + emote("growl") //Corgis and pugs are now under one dog subtype @@ -564,7 +530,7 @@ if(isturf(movement_target.loc)) movement_target.attack_animal(src) // Eat the thing else if(prob(30) && ishuman(movement_target.loc)) // mean hooman has stolen it - custom_emote(EMOTE_VISUAL, "stares at [movement_target.loc]'s [movement_target] with a sad puppy-face.") + custom_emote(EMOTE_VISIBLE, "stares at [movement_target.loc]'s [movement_target] with a sad puppy-face.") /obj/item/reagent_containers/food/snacks/meat/corgi name = "Corgi meat" @@ -669,7 +635,7 @@ . = ..() if(!resting && !buckled) if(prob(1)) - custom_emote(1, pick("dances around.","chases her tail.")) + custom_emote(EMOTE_VISIBLE, pick("dances around.","chases her tail.")) spin(20, 1) /mob/living/simple_animal/pet/dog/corgi/exoticcorgi @@ -765,7 +731,7 @@ . = ..() if(!resting && !buckled) if(prob(1)) - custom_emote(1, pick("chases its tail.")) + custom_emote(EMOTE_VISIBLE, pick("chases its tail.")) spawn(0) for(var/i in list(1, 2, 4, 8, 4, 2, 1, 2, 4, 8, 4, 2, 1, 2, 4, 8, 4, 2)) dir = i diff --git a/code/modules/mob/living/simple_animal/friendly/friendly_emote.dm b/code/modules/mob/living/simple_animal/friendly/friendly_emote.dm new file mode 100644 index 00000000000..63010c23bf7 --- /dev/null +++ b/code/modules/mob/living/simple_animal/friendly/friendly_emote.dm @@ -0,0 +1,93 @@ +/datum/emote/living/simple_animal + mob_type_allowed_typecache = list(/mob/living/simple_animal) + +/datum/emote/living/simple_animal/diona_chirp + key = "chirp" + key_third_person = "chirps" + message = "chirps!" + sound = "sound/creatures/nymphchirp.ogg" + emote_type = EMOTE_SOUND + mob_type_allowed_typecache = list(/mob/living/simple_animal/diona) + +// Dog emotes + +/datum/emote/living/simple_animal/pet/dog + mob_type_allowed_typecache = list(/mob/living/simple_animal/pet/dog) + +/datum/emote/living/simple_animal/pet/dog/bark + key = "bark" + key_third_person = "barks" + message = "barks." + message_param = "barks at %t." + emote_type = EMOTE_SOUND + +/datum/emote/living/simple_animal/pet/dog/bark/get_sound(mob/living/user) + . = ..() + + var/mob/living/simple_animal/pet/dog/D = user + message = pick(D.speak_emote) + return pick(D.bark_sound) + +/datum/emote/living/simple_animal/pet/dog/yelp + key = "yelp" + key_third_person = "yelps" + message = "yelps!" + message_param = "yelps at %t!" + emote_type = EMOTE_SOUND + +/datum/emote/living/simple_animal/pet/dog/yelp/get_sound(mob/living/user) + var/mob/living/simple_animal/pet/dog/D = user + return D.yelp_sound + +/datum/emote/living/simple_animal/pet/dog/growl + key = "growl" + key_third_person = "growls" + message = "growls!" + message_param = "growls at %t!" + emote_type = EMOTE_SOUND + sound = "growl" + +// Mouse + +/datum/emote/living/simple_animal/mouse + mob_type_allowed_typecache = list(/mob/living/simple_animal/mouse) + +/datum/emote/living/simple_animal/mouse/squeak + key = "squeak" + key_third_person = "squeaks" + message = "squeaks!" + message_param = "squeaks at %t!" + emote_type = EMOTE_SOUND + +/datum/emote/living/simple_animal/mouse/squeak/get_sound(mob/living/user) + var/mob/living/simple_animal/mouse/M = user + return M.squeak_sound + +// cat + +/datum/emote/living/simple_animal/pet/cat + mob_type_allowed_typecache = list(/mob/living/simple_animal/pet/cat) + +/datum/emote/living/simple_animal/pet/cat/meow + key = "meow" + key_third_person = "meows" + message = "meows." + message_param = "meows at %t." + sound = "sound/creatures/cat_meow.ogg" + emote_type = EMOTE_SOUND + +/datum/emote/living/simple_animal/pet/cat/meow/run_emote(mob/user, params, type_override, intentional) + var/mob/living/simple_animal/pet/cat/C = user + message = pick(C.emote_hear) + . = ..() + +/datum/emote/living/simple_animal/pet/cat/hiss + key = "hiss" + key_third_person = "hisses" + message = "hisses!" + message_param = "hisses at %t!" + +/datum/emote/living/simple_animal/pet/cat/purr + key = "purr" + key_third_person = "purrs" + message = "purrs." diff --git a/code/modules/mob/living/simple_animal/friendly/mouse.dm b/code/modules/mob/living/simple_animal/friendly/mouse.dm index e17ce00c171..4e2a1b82751 100644 --- a/code/modules/mob/living/simple_animal/friendly/mouse.dm +++ b/code/modules/mob/living/simple_animal/friendly/mouse.dm @@ -67,7 +67,7 @@ if(prob(1)) StopResting() else if(prob(5)) - custom_emote(2, "snuffles") + custom_emote(EMOTE_AUDIBLE, "snuffles") else if(prob(0.5)) StartResting() @@ -119,31 +119,6 @@ if(client) client.time_died_as_mouse = world.time -/mob/living/simple_animal/mouse/emote(act, m_type = 1, message = null, force) - if(stat != CONSCIOUS) - return - - var/on_CD = 0 - act = lowertext(act) - switch(act) - if("squeak") //Mouse time - on_CD = handle_emote_CD() - else - on_CD = 0 - - if(!force && on_CD == 1) - return - - switch(act) - if("squeak") - message = "\The [src] [pick(emote_hear)]!" - m_type = 2 //audible - playsound(src, squeak_sound, 40, 1) - if("help") - to_chat(src, "scream, squeak") - - ..() - /* * Mouse types */ diff --git a/code/modules/mob/living/simple_animal/hostile/hellhound.dm b/code/modules/mob/living/simple_animal/hostile/hellhound.dm index 7ccd87261ad..128baf57e60 100644 --- a/code/modules/mob/living/simple_animal/hostile/hellhound.dm +++ b/code/modules/mob/living/simple_animal/hostile/hellhound.dm @@ -47,11 +47,11 @@ return if(resting) if(!wants_to_rest()) - custom_emote(1, "growls, and gets up.") + custom_emote(EMOTE_VISIBLE, "growls, and gets up.") playsound(get_turf(src), 'sound/hallucinations/growl2.ogg', 50, 1) StopResting() else if(wants_to_rest()) - custom_emote(1, "lays down, and starts to lick their wounds.") + custom_emote(EMOTE_VISIBLE, "lays down, and starts to lick their wounds.") StartResting() /mob/living/simple_animal/hostile/hellhound/examine(mob/user) diff --git a/code/modules/mob/living/simple_animal/hostile/hostile.dm b/code/modules/mob/living/simple_animal/hostile/hostile.dm index 40f0e1d1dcd..98ad810c859 100644 --- a/code/modules/mob/living/simple_animal/hostile/hostile.dm +++ b/code/modules/mob/living/simple_animal/hostile/hostile.dm @@ -330,7 +330,7 @@ /mob/living/simple_animal/hostile/proc/Aggro() vision_range = aggro_vision_range if(target && emote_taunt.len && prob(taunt_chance)) - emote("me", 1, "[pick(emote_taunt)] at [target].") + custom_emote(EMOTE_VISIBLE, "[pick(emote_taunt)] at [target].") taunt_chance = max(taunt_chance-7,2) /mob/living/simple_animal/hostile/proc/LoseAggro() diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/drake.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/drake.dm index 391129bd666..7af7f747a52 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/drake.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/drake.dm @@ -394,7 +394,7 @@ Difficulty: Medium return FALSE return ..() -/mob/living/simple_animal/hostile/megafauna/dragon/visible_message() +/mob/living/simple_animal/hostile/megafauna/dragon/visible_message(message, self_message, blind_message) if(swooping & SWOOP_INVULNERABLE) //to suppress attack messages without overriding every single proc that could send a message saying we got hit return return ..() diff --git a/code/modules/mob/living/simple_animal/parrot.dm b/code/modules/mob/living/simple_animal/parrot.dm index 167f704bd6e..ff120daee9e 100644 --- a/code/modules/mob/living/simple_animal/parrot.dm +++ b/code/modules/mob/living/simple_animal/parrot.dm @@ -121,7 +121,7 @@ /mob/living/simple_animal/parrot/death(gibbed) if(can_die()) if(held_item) - custom_emote(EMOTE_VISUAL, "lets go of [held_item]!") + custom_emote(EMOTE_VISIBLE, "lets go of [held_item]!") drop_held_item() walk(src, 0) return ..() @@ -247,7 +247,7 @@ parrot_state |= PARROT_ATTACK else if(held_item) - custom_emote(EMOTE_VISUAL, "lets go of [held_item]!") + custom_emote(EMOTE_VISIBLE, "lets go of [held_item]!") parrot_state |= PARROT_FLEE //Otherwise, fly like a bat out of hell! drop_held_item(FALSE) @@ -347,7 +347,7 @@ //Search for item to steal parrot_interest = search_for_perch_and_item() if(parrot_interest) - custom_emote(EMOTE_VISUAL, "looks in [parrot_interest]'s direction and takes flight.") + custom_emote(EMOTE_VISIBLE, "looks in [parrot_interest]'s direction and takes flight.") parrot_state = PARROT_SWOOP|PARROT_STEAL icon_state = "parrot_fly" return @@ -371,7 +371,7 @@ parrot_interest = AM parrot_state = PARROT_SWOOP|PARROT_STEAL face_atom(AM) - custom_emote(EMOTE_VISUAL, "turns and flies towards [parrot_interest].") + custom_emote(EMOTE_VISIBLE, "turns and flies towards [parrot_interest].") return else //Else it's a perch parrot_perch = AM @@ -493,10 +493,10 @@ var/obj/item/organ/external/affecting = H.get_organ(ran_zone(pick(parrot_dam_zone))) H.apply_damage(damage, BRUTE, affecting, H.run_armor_check(affecting, MELEE), sharp = TRUE) - custom_emote(EMOTE_VISUAL, pick("pecks [H]'s [affecting].", "cuts [H]'s [affecting] with its talons.")) + custom_emote(EMOTE_VISIBLE, pick("pecks [H]'s [affecting].", "cuts [H]'s [affecting] with its talons.")) else L.adjustBruteLoss(damage) - custom_emote(EMOTE_VISUAL, pick("pecks at [L].", "claws [L].")) + custom_emote(EMOTE_VISIBLE, pick("pecks at [L].", "claws [L].")) return //Otherwise, fly towards the mob! else diff --git a/code/modules/mob/living/simple_animal/simple_animal.dm b/code/modules/mob/living/simple_animal/simple_animal.dm index bbe8643507e..6ceb9c813c0 100644 --- a/code/modules/mob/living/simple_animal/simple_animal.dm +++ b/code/modules/mob/living/simple_animal/simple_animal.dm @@ -229,23 +229,23 @@ else randomValue -= speak.len if(emote_see && randomValue <= emote_see.len) - custom_emote(1, pick(emote_see)) + custom_emote(EMOTE_VISIBLE, pick(emote_see)) else - custom_emote(2, pick(emote_hear)) + custom_emote(EMOTE_AUDIBLE, pick(emote_hear)) else say(pick(speak)) else if(!(emote_hear && emote_hear.len) && (emote_see && emote_see.len)) - custom_emote(1, pick(emote_see)) + custom_emote(EMOTE_VISIBLE, pick(emote_see)) if((emote_hear && emote_hear.len) && !(emote_see && emote_see.len)) - custom_emote(2, pick(emote_hear)) + custom_emote(EMOTE_AUDIBLE, pick(emote_hear)) if((emote_hear && emote_hear.len) && (emote_see && emote_see.len)) var/length = emote_hear.len + emote_see.len var/pick = rand(1,length) if(pick <= emote_see.len) - custom_emote(1, pick(emote_see)) + custom_emote(EMOTE_VISIBLE, pick(emote_see)) else - custom_emote(2,pick(emote_hear)) + custom_emote(EMOTE_AUDIBLE, pick(emote_hear)) /mob/living/simple_animal/handle_environment(datum/gas_mixture/environment) @@ -316,20 +316,6 @@ pcollar.forceMove(drop_location()) pcollar = null ..() - -/mob/living/simple_animal/emote(act, m_type = 1, message = null, force) - if(stat) - return - act = lowertext(act) - switch(act) //IMPORTANT: Emotes MUST NOT CONFLICT anywhere along the chain. - if("scream") - message = "\The [src] whimpers." - m_type = 2 - if("help") - to_chat(src, "scream") - - ..() - /mob/living/simple_animal/say_quote(message) var/verb = "says" diff --git a/code/modules/mob/living/simple_animal/slime/emote.dm b/code/modules/mob/living/simple_animal/slime/emote.dm deleted file mode 100644 index bcda2a44481..00000000000 --- a/code/modules/mob/living/simple_animal/slime/emote.dm +++ /dev/null @@ -1,105 +0,0 @@ -/mob/living/simple_animal/slime/emote(act, m_type = 1, message = null, force) - if(stat) - return - - if(findtext(act, "-", 1, null)) - var/t1 = findtext(act, "-", 1, null) - //param = copytext(act, t1 + 1, length(act) + 1) - act = copytext(act, 1, t1) - - if(findtext(act,"s",-1) && !findtext(act,"_",-2))//Removes ending s's unless they are prefixed with a '_' - act = copytext(act,1,length(act)) - - act = lowertext(act) - - var/regenerate_icons - - switch(act) //Alphabetical please - if("me") - if(HAS_TRAIT(src, TRAIT_MUTE)) - return - if(src.client) - if(check_mute(client.ckey, MUTE_IC)) - to_chat(src, "You cannot send IC messages (muted).") - return - if(src.client.handle_spam_prevention(message, MUTE_IC)) - return - if(stat) - return - if(!(message)) - return - return custom_emote(m_type, message) - if("bounce") - message = "[src] bounces in place." - m_type = 1 - - if("custom") - return custom_emote(m_type, message) - - if("jiggle") - message = "[src] jiggles!" - m_type = 1 - - if("light") - message = "[src] lights up for a bit, then stops." - m_type = 1 - - if("moan") - message = "[src] moans." - m_type = 2 - - if("shiver") - message = "[src] shivers." - m_type = 2 - - if("sway") - message = "[src] sways around dizzily." - m_type = 1 - - if("twitch") - message = "[src] twitches." - m_type = 1 - - if("vibrate") - message = "[src] vibrates!" - m_type = 1 - - if("noface") //mfw I have no face - mood = null - regenerate_icons = 1 - - if("smile") - mood = "mischevous" - regenerate_icons = 1 - - if(":3") - mood = ":33" - regenerate_icons = 1 - - if("pout") - mood = "pout" - regenerate_icons = 1 - - if("frown") - mood = "sad" - regenerate_icons = 1 - - if("scowl") - mood = "angry" - regenerate_icons = 1 - - if("help") //This is an exception - to_chat(src, "Help for slime emotes. You can use these emotes with say \"*emote\":\n\nbounce, custom, jiggle, light, moan, shiver, sway, twitch, vibrate. \n\nYou may also change your face with: \n\nsmile, :3, pout, frown, scowl, noface") - - else - to_chat(src, "Unusable emote '[act]'. Say *help for a list.") - if((message && stat == CONSCIOUS)) - if(client) - log_emote("[name]/[key] : [message]", src) - if(m_type & 1) - visible_message(message) - else - loc.audible_message(message) - - if(regenerate_icons) - regenerate_icons() diff --git a/code/modules/mob/living/simple_animal/slime/slime_emote.dm b/code/modules/mob/living/simple_animal/slime/slime_emote.dm new file mode 100644 index 00000000000..c76d2137cd7 --- /dev/null +++ b/code/modules/mob/living/simple_animal/slime/slime_emote.dm @@ -0,0 +1,66 @@ +/// Sentinel value; passing this as mood sets mood to null. +#define MOOD_RESET "reset" + +/datum/emote/living/simple_animal/slime + mob_type_allowed_typecache = list(/mob/living/simple_animal/slime) + /// Apply mood of the emote. Set this to MOOD_RESET to cause the emote to reset the mood back to default. + var/mood + +/datum/emote/living/simple_animal/slime/run_emote(mob/user, params, type_override, intentional) + . = ..() + if(!.) + return FALSE + + if(mood) + var/mob/living/simple_animal/slime/S = user + if(mood == MOOD_RESET) + S.mood = null + else + S.mood = mood + +/datum/emote/living/simple_animal/slime/bounce + key = "bounce" + key_third_person = "bounces" + message = "bounces in place." + +/datum/emote/living/simple_animal/slime/jiggle + key = "jiggle" + key_third_person = "jiggles" + message = "jiggles!" + +/datum/emote/living/simple_animal/slime/light + key = "light" + key_third_person = "lights" + message = "lights up for a bit, then stops." + +/datum/emote/living/simple_animal/slime/vibrate + key = "vibrate" + key_third_person = "vibrates" + message = "vibrates!" + +/datum/emote/living/simple_animal/slime/noface + // mfw no face + key = "noface" + mood = MOOD_RESET + +/datum/emote/living/simple_animal/slime/smile + key = "smile" + mood = "mischevous" + +/datum/emote/living/simple_animal/slime/colon_three + key = ":3" + mood = ":33" + +/datum/emote/living/simple_animal/slime/pout + key = "pout" + mood = "pout" + +/datum/emote/living/simple_animal/slime/sad + key = "frown" + mood = "sad" + +/datum/emote/living/simple_animal/slime/scowl + key = "scowl" + mood = "angry" + +#undef MOOD_RESET diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm index c1fe84f7c12..794e9ecf604 100644 --- a/code/modules/mob/mob.dm +++ b/code/modules/mob/mob.dm @@ -70,26 +70,26 @@ t+= "N2O: [environment.sleeping_agent] \n" t+= "Agent B: [environment.agent_b] \n" - usr.show_message(t, 1) + usr.show_message(t, EMOTE_VISIBLE) /mob/proc/show_message(msg, type, alt, alt_type)//Message, type of message (1 or 2), alternative message, alt message type (1 or 2) if(!client) return if(type) - if(type & 1 && !has_vision(information_only=TRUE))//Vision related + if(type & EMOTE_VISIBLE && !has_vision(information_only=TRUE))//Vision related + if(!(alt)) + return + else + msg = alt + type = alt_type + if(type & EMOTE_AUDIBLE && !can_hear())//Hearing related if(!( alt )) return else msg = alt type = alt_type - if(type & 2 && !can_hear())//Hearing related - if(!( alt )) - return - else - msg = alt - type = alt_type - if(type & 1 && !has_vision(information_only=TRUE)) + if(type & EMOTE_VISIBLE && !has_vision(information_only=TRUE)) return // Added voice muffling for Issue 41. if(stat == UNCONSCIOUS) @@ -111,7 +111,7 @@ var/msg = message if(self_message && M == src) msg = self_message - M.show_message(msg, 1, blind_message, 2) + M.show_message(msg, EMOTE_VISIBLE, blind_message, EMOTE_AUDIBLE) // Show a message to all mobs in sight of this atom // Use for objects performing visible actions @@ -121,7 +121,7 @@ for(var/mob/M in get_mobs_in_view(7, src)) if(!M.client) continue - M.show_message(message, 1, blind_message, 2) + M.show_message(message, EMOTE_VISIBLE, blind_message, EMOTE_AUDIBLE) // Show a message to all mobs in earshot of this one // This would be for audible actions by the src mob @@ -135,7 +135,7 @@ range = hearing_distance var/msg = message for(var/mob/M in get_mobs_in_view(range, src)) - M.show_message(msg, 2, deaf_message, 1) + M.show_message(msg, EMOTE_AUDIBLE, deaf_message, EMOTE_VISIBLE) // based on say code var/omsg = replacetext(message, "[src] ", "") @@ -161,7 +161,7 @@ if(hearing_distance) range = hearing_distance for(var/mob/M in get_mobs_in_view(range, src)) - M.show_message(message, 2, deaf_message, 1) + M.show_message(message, EMOTE_AUDIBLE, deaf_message, EMOTE_VISIBLE) /mob/proc/findname(msg) for(var/mob/M in GLOB.mob_list) diff --git a/code/modules/mob/mob_defines.dm b/code/modules/mob/mob_defines.dm index 731f504c085..bc68f02f242 100644 --- a/code/modules/mob/mob_defines.dm +++ b/code/modules/mob/mob_defines.dm @@ -57,7 +57,7 @@ var/list/languages = list() // For speaking/listening. var/list/abilities = list() // For species-derived or admin-given powers. var/list/speak_emote = list("says") // Verbs used when speaking. Defaults to 'say' if speak_emote is null. - var/emote_type = 1 // Define emote default type, 1 for seen emotes, 2 for heard emotes + var/emote_type = EMOTE_VISIBLE // Define emote default type, 1 for seen emotes, 2 for heard emotes var/name_archive //For admin things like possession var/timeofdeath = 0 //Living @@ -98,7 +98,17 @@ var/in_throw_mode = 0 - var/emote_cd = 0 // Used to supress emote spamming. 1 if on CD, 2 if disabled by admin (manually set), else 0 + // See /datum/emote + + /// Cooldown on audio effects from emotes. + var/audio_emote_cd_status = EMOTE_READY + + /// Override for cooldowns on non-audio emotes. Should be a number in deciseconds. + var/emote_cooldown_override = null + + /// Tracks last uses of emotes for cooldown purposes + var/list/emotes_used + var/list/emotes_on_cooldown var/job = null //Living diff --git a/code/modules/mob/mob_emote.dm b/code/modules/mob/mob_emote.dm new file mode 100644 index 00000000000..3c7df1ffdae --- /dev/null +++ b/code/modules/mob/mob_emote.dm @@ -0,0 +1,192 @@ + +// The datum in use is defined in code/datums/emotes.dm + +/** + * Send an emote. + * + * * emote_key: Key of the emote being triggered + * * m_type: Type of the emote, like EMOTE_AUDIBLE. If this is not null, the default type of the emote will be overridden. + * * message: Custom parameter for the emote. This should be used if you want to pass something like a target programmatically. + * * intentional: Whether or not the emote was deliberately triggered by the mob. If true, it's forced, which skips some checks when calling the emote. + * * force_silence: If true, unusable/nonexistent emotes will not notify the user. + */ +/mob/proc/emote(emote_key, type_override = null, message = null, intentional = FALSE, force_silence = FALSE) + emote_key = lowertext(emote_key) + var/param = message + var/custom_param_offset = findtext(emote_key, EMOTE_PARAM_SEPARATOR, 1, null) + if(custom_param_offset) + param = copytext(emote_key, custom_param_offset + length(emote_key[custom_param_offset])) + emote_key = copytext(emote_key, 1, custom_param_offset) + + var/list/key_emotes = GLOB.emote_list[emote_key] + + if(!length(key_emotes)) + if(intentional && !force_silence) + to_chat(src, "'[emote_key]' emote does not exist. Say *help for a list.") + else if(!intentional) + CRASH("Emote with key [emote_key] was attempted to be called, though doesn't exist!") + return FALSE + var/silenced = FALSE + for(var/datum/emote/P in key_emotes) + if(!P.check_cooldown(src, intentional)) + // if an emote's on cooldown, don't spam them with messages of not being able to use it + silenced = TRUE + continue + if(P.try_run_emote(src, param, type_override, intentional)) + return TRUE + if(intentional && !silenced && !force_silence) + to_chat(src, "Unusable emote '[emote_key]'. Say *help for a list.") + return FALSE + +/** + * Perform a custom emote. + * + * * m_type: Type of message to send. + * * message: Content of the message. If none is provided, the user will be prompted to choose the input. + * * intentional: Whether or not the user intendeded to perform the emote. + */ +/mob/proc/custom_emote(m_type = EMOTE_VISIBLE, message = null, intentional = FALSE) + var/input = "" + if(!message && !client) + CRASH("An empty custom emote was called from a client-less mob.") + else if (!message) + input = sanitize(copytext(input(src,"Choose an emote to display.") as text|null, 1, MAX_MESSAGE_LEN)) + else + input = message + + emote("me", m_type, input, intentional) + +/** + * Get a list of all emote keys usable by the current mob. + * + * * intentional_use: Whether or not to check based on if the action was intentional. + */ +/mob/proc/usable_emote_keys(intentional_use = TRUE) + var/list/all_keys = list() + for(var/key in GLOB.emote_list) + for(var/datum/emote/P in GLOB.emote_list[key]) + if(P.key in all_keys) + continue + if(P.can_run_emote(src, status_check = FALSE, intentional = null)) + all_keys += P.key + if(P.key_third_person) + all_keys += P.key_third_person + + return all_keys + +/datum/emote/help + key = "help" + mob_type_ignore_stat_typecache = list(/mob/dead/observer, /mob/living/silicon/ai) + +/datum/emote/help/run_emote(mob/user, params, type_override, intentional) + . = ..() + var/list/base_keys = list() + var/list/all_keys = list() + var/list/species_emotes = list() + var/list/message = list("Available emotes, you can use them with say \"*emote\": ") + + var/mob/living/carbon/human/H = user + for(var/key in GLOB.emote_list) + for(var/datum/emote/P in GLOB.emote_list[key]) + var/full_key = P.key + if(P.key in all_keys) + continue + if(P.can_run_emote(user, status_check = FALSE, intentional = TRUE)) + if(P.message_param && P.param_desc) + // Add our parameter description, like flap-user + full_key = P.key + "\[[EMOTE_PARAM_SEPARATOR][P.param_desc]\]" + if(istype(H) && P.species_type_whitelist_typecache && H.dna && is_type_in_typecache(H.dna.species, P.species_type_whitelist_typecache)) + species_emotes += full_key + else + base_keys += full_key + all_keys += P.key + + base_keys = sortList(base_keys) + message += base_keys.Join(", ") + message += "." + message = message.Join("") + if(length(species_emotes) > 0) + species_emotes = sortList(species_emotes) + message += "\n[user?.dna?.species.name] specific emotes :- " + message += species_emotes.Join(", ") + message += "." + to_chat(user, message) + +/datum/emote/flip + key = "flip" + key_third_person = "flips" + message = "does a flip!" + hands_use_check = TRUE + emote_type = EMOTE_VISIBLE | EMOTE_FORCE_NO_RUNECHAT // don't need an emote to see that + mob_type_allowed_typecache = list(/mob/living, /mob/dead/observer) // okay but what if we allowed ghosts to flip as well + mob_type_blacklist_typecache = list(/mob/living/carbon/brain, /mob/camera, /mob/living/silicon/ai) + mob_type_ignore_stat_typecache = list(/mob/dead/observer) + +/datum/emote/flip/run_emote(mob/user, params, type_override, intentional) + + if(isobserver(user)) + user.SpinAnimation(5, 1) + return TRUE + + var/mob/living/L = user + + if(user.lying || (istype(L) && L.IsWeakened())) + message = "flops and flails around on the floor." + return ..() + else if(params) + message_param = "flips in %t's general direction." + else if(ishuman(user)) + var/mob/living/carbon/human/H = user + if(istype(H.get_active_hand(), /obj/item/grab)) + var/obj/item/grab/G = H.get_active_hand() + if(G && G.affecting) + if(H.buckled || G.affecting.buckled) + to_chat(user, "[G.affecting] is buckled, you can't flip around [G.affecting.p_them()]!") + return TRUE + var/turf/oldloc = user.loc + var/turf/newloc = G.affecting.loc + if(isturf(oldloc) && isturf(newloc)) + user.SpinAnimation(5, 1) + user.glide_for(0.6 SECONDS) // This and the glide_for below are purely arbitrary. Pick something that looks aesthetically pleasing. + user.forceMove(newloc) + G.glide_for(0.6 SECONDS) + G.affecting.forceMove(oldloc) + message = "flips over [G.affecting]!" + return ..() + + user.SpinAnimation(5, 1) + + if(prob(5) && ishuman(user)) + message = "attempts a flip and crashes to the floor!" + sleep(0.3 SECONDS) + if(istype(L)) + L.Weaken(4 SECONDS) + return ..() + + . = ..() + +/datum/emote/spin + key = "spin" + key_third_person = "spins" + hands_use_check = TRUE + emote_type = EMOTE_VISIBLE | EMOTE_FORCE_NO_RUNECHAT + mob_type_allowed_typecache = list(/mob/living, /mob/dead/observer) + mob_type_blacklist_typecache = list(/mob/living/carbon/brain, /mob/camera, /mob/living/silicon/ai) + mob_type_ignore_stat_typecache = list(/mob/dead/observer) + cooldown = 2 SECONDS // how long the spin takes, any faster and mobs can spin + +/datum/emote/spin/run_emote(mob/user, params, type_override, intentional) + . = ..() + if(!.) + return FALSE + + if(prob(95) || isobserver(user) || !ishuman(user)) + user.spin(20, 1) + return TRUE + + user.spin(32, 1) + to_chat(user, "You spin too much!") + var/mob/living/L = user + if(istype(L)) + L.Dizzy(24 SECONDS) + L.Confused(24 SECONDS) diff --git a/code/modules/mob/mob_helpers.dm b/code/modules/mob/mob_helpers.dm index e284642f50b..51586346048 100644 --- a/code/modules/mob/mob_helpers.dm +++ b/code/modules/mob/mob_helpers.dm @@ -744,3 +744,48 @@ GLOBAL_LIST_INIT(intents, list(INTENT_HELP,INTENT_DISARM,INTENT_GRAB,INTENT_HARM message_admins("[src.ckey] just got booted back to lobby with no jobs enabled, but antag rolling enabled. Likely antag rolling abuse.") return FALSE //This is the only case someone should actually be completely blocked from antag rolling as well return TRUE + +/** + * Helper proc to determine if a mob can use emotes that make sound or not. + */ +/mob/proc/can_use_audio_emote() + switch(audio_emote_cd_status) + if(EMOTE_INFINITE) // Spam those emotes + return TRUE + if(EMOTE_ADMIN_BLOCKED) // Cooldown emotes were disabled by an admin, prevent use + return FALSE + if(EMOTE_ON_COOLDOWN) // Already on CD, prevent use + return FALSE + if(EMOTE_READY) + return TRUE + + CRASH("Invalid emote type") + +/** + * # Start the cooldown for an emote that plays audio. + * + * * cooldown: The amount of time that should be waited before any other audio emote can fire. + */ +/mob/proc/start_audio_emote_cooldown(cooldown = AUDIO_EMOTE_COOLDOWN) + if(!can_use_audio_emote()) + return FALSE + + if(audio_emote_cd_status == EMOTE_READY) + audio_emote_cd_status = EMOTE_ON_COOLDOWN // Starting cooldown + addtimer(CALLBACK(src, .proc/on_audio_emote_cooldown_end), cooldown) + return TRUE // proceed with emote + + +/mob/proc/on_audio_emote_cooldown_end() + if(audio_emote_cd_status == EMOTE_ON_COOLDOWN) + // only reset emotes that probably weren't set by an admin + audio_emote_cd_status = EMOTE_READY + +/proc/stat_to_text(stat) + switch(stat) + if(CONSCIOUS) + return "conscious" + if(UNCONSCIOUS) + return "unconscious" + if(DEAD) + return "dead" diff --git a/code/modules/mob/say.dm b/code/modules/mob/say.dm index 86e3a44aa8a..94c8e862e62 100644 --- a/code/modules/mob/say.dm +++ b/code/modules/mob/say.dm @@ -2,6 +2,8 @@ #define ILLEGAL_CHARACTERS_LIST list("<" = "", ">" = "", \ "\[" = "", "]" = "", "{" = "", "}" = "") +#define USABLE_DEAD_EMOTES list("*flip", "*spin") + /mob/proc/say() return @@ -39,9 +41,9 @@ set_typing_indicator(FALSE, TRUE) if(use_me) - custom_emote(usr.emote_type, message) + custom_emote(usr.emote_type, message, intentional = TRUE) else - usr.emote(message) + usr.emote(message, intentional = TRUE) /mob/proc/say_dead(message) @@ -62,6 +64,13 @@ if(client.handle_spam_prevention(message, MUTE_DEADCHAT)) return + + if(message in USABLE_DEAD_EMOTES) + emote(copytext(message, 2), intentional = TRUE) + log_emote(message, src) + create_log(DEADCHAT_LOG, message) + return + say_dead_direct("[pick("complains", "moans", "whines", "laments", "blubbers", "salts")], \"[message]\"", src) create_log(DEADCHAT_LOG, message) log_ghostsay(message, src) @@ -110,12 +119,6 @@ verb = "asks" return verb - -/mob/proc/emote(act, type, message, force) - if(act == "me") - return custom_emote(type, message) - - /mob/proc/get_ear() // returns an atom representing a location on the map from which this // mob can hear things @@ -220,4 +223,5 @@ . += S.message + " " . = trim_right(.) +#undef USABLE_DEAD_EMOTES #undef ILLEGAL_CHARACTERS_LIST diff --git a/code/modules/surgery/organs/blood.dm b/code/modules/surgery/organs/blood.dm index 22c641ffa0f..2597aef0bf4 100644 --- a/code/modules/surgery/organs/blood.dm +++ b/code/modules/surgery/organs/blood.dm @@ -106,7 +106,7 @@ if(blood_volume) blood_volume = max(blood_volume - amt, 0) if(prob(10 * amt)) // +5% chance per internal bleeding site that we'll cough up blood on a given tick. - custom_emote(1, "coughs up blood!") + custom_emote(EMOTE_VISIBLE, "coughs up blood!") add_splatter_floor(loc, 1) return 1 else if(amt >= 1 && prob(5 * amt)) // +2.5% chance per internal bleeding site that we'll cough up blood on a given tick. Must be bleeding internally in more than one place to have a chance at this. diff --git a/code/modules/surgery/organs/lungs.dm b/code/modules/surgery/organs/lungs.dm index 7c402761d73..7098bc4660c 100644 --- a/code/modules/surgery/organs/lungs.dm +++ b/code/modules/surgery/organs/lungs.dm @@ -77,10 +77,10 @@ if(is_bruised()) if(prob(2) && !(NO_BLOOD in owner.dna.species.species_traits)) - owner.custom_emote(1, "coughs up blood!") + owner.custom_emote(EMOTE_VISIBLE, "coughs up blood!") owner.bleed(1) if(prob(4)) - owner.custom_emote(1, "gasps for air!") + owner.custom_emote(EMOTE_VISIBLE, "gasps for air!") owner.AdjustLoseBreath(10 SECONDS) /obj/item/organ/internal/lungs/proc/check_breath(datum/gas_mixture/breath, mob/living/carbon/human/H) diff --git a/code/modules/surgery/organs/organ_external.dm b/code/modules/surgery/organs/organ_external.dm index adf5e6bf8d6..01b4e4bd5ce 100644 --- a/code/modules/surgery/organs/organ_external.dm +++ b/code/modules/surgery/organs/organ_external.dm @@ -181,7 +181,7 @@ I.receive_damage(brute * 0.5) brute -= brute * 0.5 - if(status & ORGAN_BROKEN && prob(40) && brute) + if(status & ORGAN_BROKEN && prob(40) && brute && !owner.stat) owner.emote("scream") //getting hit on broken hand hurts if(status & ORGAN_SPLINTED && prob((brute + burn)*4)) //taking damage to splinted limbs removes the splints status &= ~ORGAN_SPLINTED @@ -627,7 +627,7 @@ Note that amputating the affected organ does in fact remove the infection from t ) to_chat(owner, "Something feels like it shattered in your [name]!") playsound(owner, "bonebreak", 150, 1) - if(!HAS_TRAIT(owner, TRAIT_NOPAIN)) + if(!HAS_TRAIT(owner, TRAIT_NOPAIN) && !owner.stat) owner.emote("scream") status |= ORGAN_BROKEN diff --git a/code/modules/surgery/organs/subtypes/standard.dm b/code/modules/surgery/organs/subtypes/standard.dm index 61a15127ab7..a1f5ecead33 100644 --- a/code/modules/surgery/organs/subtypes/standard.dm +++ b/code/modules/surgery/organs/subtypes/standard.dm @@ -62,7 +62,7 @@ if(hand && owner.canUnEquip(hand)) owner.unEquip(hand) to_chat(owner, "Your [name] malfunctions, dropping what it was holding!") - owner.custom_emote(1, "drops what [owner.p_they()] [owner.p_were()] holding, [owner.p_their()] [name] malfunctioning!") + owner.custom_emote(EMOTE_VISIBLE, "drops what [owner.p_they()] [owner.p_were()] holding, [owner.p_their()] [name] malfunctioning!") /obj/item/organ/external/arm/right limb_name = "r_arm" @@ -92,10 +92,10 @@ return if(owner.AmountWeakened()) to_chat(owner, "Your [name] malfunctions, preventing you from getting back up!") - owner.custom_emote(1, "is unable to get back up, [owner.p_their()] [name] malfunctioning!") + owner.custom_emote(EMOTE_VISIBLE, "is unable to get back up, [owner.p_their()] [name] malfunctioning!") else to_chat(owner, "Your [name] malfunctions, dropping you to the ground!") - owner.custom_emote(1, "drops to the ground, [owner.p_their()] [name] malfunctioning!") + owner.custom_emote(EMOTE_VISIBLE, "drops to the ground, [owner.p_their()] [name] malfunctioning!") switch(severity) if(1) owner.AdjustWeakened(8 SECONDS) @@ -130,10 +130,10 @@ return if(owner.AmountWeakened()) to_chat(owner, "Your [name] malfunctions, preventing you from getting back up!") - owner.custom_emote(1, "is unable to get back up, [owner.p_their()] [name] malfunctioning!") + owner.custom_emote(EMOTE_VISIBLE, "is unable to get back up, [owner.p_their()] [name] malfunctioning!") else to_chat(owner, "Your [name] malfunctions, dropping you to the ground!") - owner.custom_emote(1, "drops to the ground, [owner.p_their()] [name] malfunctioning!") + owner.custom_emote(EMOTE_VISIBLE, "drops to the ground, [owner.p_their()] [name] malfunctioning!") switch(severity) if(1) owner.AdjustWeakened(8 SECONDS) @@ -173,7 +173,7 @@ if(hand && owner.canUnEquip(hand)) owner.unEquip(hand) to_chat(owner, "Your [name] malfunctions, dropping what it was holding!") - owner.custom_emote(1, "drops what [owner.p_they()] [owner.p_were()] holding, [owner.p_their()] [name] malfunctioning!") + owner.custom_emote(EMOTE_VISIBLE, "drops what [owner.p_they()] [owner.p_were()] holding, [owner.p_their()] [name] malfunctioning!") /obj/item/organ/external/hand/remove() if(owner) diff --git a/code/modules/unit_tests/_unit_tests.dm b/code/modules/unit_tests/_unit_tests.dm index 1bcb832f6fd..d52c3c33bef 100644 --- a/code/modules/unit_tests/_unit_tests.dm +++ b/code/modules/unit_tests/_unit_tests.dm @@ -5,6 +5,7 @@ #include "component_tests.dm" #include "config_sanity.dm" #include "crafting_lists.dm" +#include "emotes.dm" #include "log_format.dm" #include "map_templates.dm" #include "purchase_reference_test.dm" diff --git a/code/modules/unit_tests/emotes.dm b/code/modules/unit_tests/emotes.dm new file mode 100644 index 00000000000..18f7c4b11e3 --- /dev/null +++ b/code/modules/unit_tests/emotes.dm @@ -0,0 +1,29 @@ + +/datum/unit_test/emote/Run() + // be aware that some of these values (like message, message_param) are subject to being set at runtime. + for(var/emote_type in subtypesof(/datum/emote)) + var/datum/emote/cur_emote = new emote_type() + if(cur_emote.message_param && !cur_emote.param_desc) + Fail("emote [cur_emote] was given a message parameter without a description.") + + // Sanity checks, these emotes probably won't appear to a user but we should make sure they're cleaned up. + if(!cur_emote.key) + if(cur_emote.message || cur_emote.message_param) + Fail("emote [cur_emote] is missing a key but has a message defined.") + if(cur_emote.key_third_person) + Fail("emote [cur_emote] has a third-person key defined, but no first-person key. Either first person, both, or neither should be defined.") + + // These are ones that might appear to a user, and so could use some special handling. + else + if(isnull(cur_emote.emote_type)) + Fail("emote [cur_emote] has a null target type.") + + if(isnum(cur_emote.max_stat_allowed) && cur_emote.max_stat_allowed < cur_emote.stat_allowed) + Fail("emote [cur_emote]'s max_stat_allowed is greater than its stat_allowed, and would be unusable.") + + if(isnum(cur_emote.max_unintentional_stat_allowed) && cur_emote.max_unintentional_stat_allowed < cur_emote.unintentional_stat_allowed) + Fail("emote [cur_emote]'s max_unintentional_stat_allowed is greater than its unintentional_stat_allowed, and would be unusable.") + + +/datum/unit_test/emote/proc/has_punctuation(datum/emote/E, msg) + return E.remove_ending_punctuation(msg) == msg diff --git a/paradise.dme b/paradise.dme index 22f57b4c13b..2f3e02dd2cc 100644 --- a/paradise.dme +++ b/paradise.dme @@ -40,6 +40,7 @@ #include "code\__DEFINES\criminal_status.dm" #include "code\__DEFINES\cult.dm" #include "code\__DEFINES\dna.dm" +#include "code\__DEFINES\emotes.dm" #include "code\__DEFINES\error_handler.dm" #include "code\__DEFINES\flags.dm" #include "code\__DEFINES\footstep.dm" @@ -300,6 +301,7 @@ #include "code\datums\datumvars.dm" #include "code\datums\discord.dm" #include "code\datums\dog_fashion.dm" +#include "code\datums\emote.dm" #include "code\datums\holocall.dm" #include "code\datums\http.dm" #include "code\datums\hud.dm" @@ -1829,7 +1831,6 @@ #include "code\modules\mining\lavaland\loot\legion_loot.dm" #include "code\modules\mining\lavaland\loot\tendril_loot.dm" #include "code\modules\mob\death.dm" -#include "code\modules\mob\emote.dm" #include "code\modules\mob\hear_say.dm" #include "code\modules\mob\holder.dm" #include "code\modules\mob\inventory.dm" @@ -1838,6 +1839,7 @@ #include "code\modules\mob\logout.dm" #include "code\modules\mob\mob.dm" #include "code\modules\mob\mob_defines.dm" +#include "code\modules\mob\mob_emote.dm" #include "code\modules\mob\mob_grab.dm" #include "code\modules\mob\mob_helpers.dm" #include "code\modules\mob\mob_movement.dm" @@ -1865,6 +1867,7 @@ #include "code\modules\mob\living\living.dm" #include "code\modules\mob\living\living_defense.dm" #include "code\modules\mob\living\living_defines.dm" +#include "code\modules\mob\living\living_emote.dm" #include "code\modules\mob\living\login.dm" #include "code\modules\mob\living\logout.dm" #include "code\modules\mob\living\say.dm" @@ -1876,6 +1879,7 @@ #include "code\modules\mob\living\carbon\carbon.dm" #include "code\modules\mob\living\carbon\carbon_defense.dm" #include "code\modules\mob\living\carbon\carbon_defines.dm" +#include "code\modules\mob\living\carbon\carbon_emote.dm" #include "code\modules\mob\living\carbon\death.dm" #include "code\modules\mob\living\carbon\give.dm" #include "code\modules\mob\living\carbon\life.dm" @@ -1890,10 +1894,10 @@ #include "code\modules\mob\living\carbon\alien\login.dm" #include "code\modules\mob\living\carbon\alien\logout.dm" #include "code\modules\mob\living\carbon\alien\humanoid\alien_powers.dm" -#include "code\modules\mob\living\carbon\alien\humanoid\emote.dm" #include "code\modules\mob\living\carbon\alien\humanoid\empress.dm" #include "code\modules\mob\living\carbon\alien\humanoid\humanoid.dm" #include "code\modules\mob\living\carbon\alien\humanoid\humanoid_defense.dm" +#include "code\modules\mob\living\carbon\alien\humanoid\humanoid_emote.dm" #include "code\modules\mob\living\carbon\alien\humanoid\inventory.dm" #include "code\modules\mob\living\carbon\alien\humanoid\life.dm" #include "code\modules\mob\living\carbon\alien\humanoid\queen.dm" @@ -1901,7 +1905,6 @@ #include "code\modules\mob\living\carbon\alien\humanoid\caste\drone.dm" #include "code\modules\mob\living\carbon\alien\humanoid\caste\hunter.dm" #include "code\modules\mob\living\carbon\alien\humanoid\caste\sentinel.dm" -#include "code\modules\mob\living\carbon\alien\larva\emote.dm" #include "code\modules\mob\living\carbon\alien\larva\inventory.dm" #include "code\modules\mob\living\carbon\alien\larva\larva.dm" #include "code\modules\mob\living\carbon\alien\larva\larva_defense.dm" @@ -1911,9 +1914,9 @@ #include "code\modules\mob\living\carbon\alien\special\alien_embryo.dm" #include "code\modules\mob\living\carbon\alien\special\facehugger.dm" #include "code\modules\mob\living\carbon\brain\brain.dm" +#include "code\modules\mob\living\carbon\brain\brain_emote.dm" #include "code\modules\mob\living\carbon\brain\brain_item.dm" #include "code\modules\mob\living\carbon\brain\death.dm" -#include "code\modules\mob\living\carbon\brain\emote.dm" #include "code\modules\mob\living\carbon\brain\life.dm" #include "code\modules\mob\living\carbon\brain\login.dm" #include "code\modules\mob\living\carbon\brain\MMI.dm" @@ -1924,12 +1927,12 @@ #include "code\modules\mob\living\carbon\human\appearance.dm" #include "code\modules\mob\living\carbon\human\body_accessories.dm" #include "code\modules\mob\living\carbon\human\death.dm" -#include "code\modules\mob\living\carbon\human\emote.dm" #include "code\modules\mob\living\carbon\human\examine.dm" #include "code\modules\mob\living\carbon\human\human.dm" #include "code\modules\mob\living\carbon\human\human_damage.dm" #include "code\modules\mob\living\carbon\human\human_defense.dm" #include "code\modules\mob\living\carbon\human\human_defines.dm" +#include "code\modules\mob\living\carbon\human\human_emote.dm" #include "code\modules\mob\living\carbon\human\human_movement.dm" #include "code\modules\mob\living\carbon\human\human_organs.dm" #include "code\modules\mob\living\carbon\human\inventory.dm" @@ -1964,12 +1967,12 @@ #include "code\modules\mob\living\carbon\human\species\vulpkanin.dm" #include "code\modules\mob\living\carbon\human\species\wryn.dm" #include "code\modules\mob\living\silicon\death.dm" -#include "code\modules\mob\living\silicon\emote.dm" #include "code\modules\mob\living\silicon\laws.dm" #include "code\modules\mob\living\silicon\login.dm" #include "code\modules\mob\living\silicon\say.dm" #include "code\modules\mob\living\silicon\silicon.dm" #include "code\modules\mob\living\silicon\silicon_defense.dm" +#include "code\modules\mob\living\silicon\silicon_emote.dm" #include "code\modules\mob\living\silicon\subsystems.dm" #include "code\modules\mob\living\silicon\ai\ai.dm" #include "code\modules\mob\living\silicon\ai\ai_defense.dm" @@ -2002,7 +2005,6 @@ #include "code\modules\mob\living\silicon\pai\software\pai_toggles.dm" #include "code\modules\mob\living\silicon\robot\component.dm" #include "code\modules\mob\living\silicon\robot\death.dm" -#include "code\modules\mob\living\silicon\robot\emote.dm" #include "code\modules\mob\living\silicon\robot\examine.dm" #include "code\modules\mob\living\silicon\robot\inventory.dm" #include "code\modules\mob\living\silicon\robot\laws.dm" @@ -2039,7 +2041,6 @@ #include "code\modules\mob\living\simple_animal\bot\cleanbot.dm" #include "code\modules\mob\living\simple_animal\bot\construction.dm" #include "code\modules\mob\living\simple_animal\bot\ed209bot.dm" -#include "code\modules\mob\living\simple_animal\bot\emote.dm" #include "code\modules\mob\living\simple_animal\bot\floorbot.dm" #include "code\modules\mob\living\simple_animal\bot\griefsky.dm" #include "code\modules\mob\living\simple_animal\bot\honkbot.dm" @@ -2057,6 +2058,7 @@ #include "code\modules\mob\living\simple_animal\friendly\dog.dm" #include "code\modules\mob\living\simple_animal\friendly\farm_animals.dm" #include "code\modules\mob\living\simple_animal\friendly\fox.dm" +#include "code\modules\mob\living\simple_animal\friendly\friendly_emote.dm" #include "code\modules\mob\living\simple_animal\friendly\lizard.dm" #include "code\modules\mob\living\simple_animal\friendly\mouse.dm" #include "code\modules\mob\living\simple_animal\friendly\penguin.dm" @@ -2136,11 +2138,11 @@ #include "code\modules\mob\living\simple_animal\hostile\terror_spiders\terror_spiders.dm" #include "code\modules\mob\living\simple_animal\hostile\terror_spiders\white.dm" #include "code\modules\mob\living\simple_animal\slime\death.dm" -#include "code\modules\mob\living\simple_animal\slime\emote.dm" #include "code\modules\mob\living\simple_animal\slime\life.dm" #include "code\modules\mob\living\simple_animal\slime\powers.dm" #include "code\modules\mob\living\simple_animal\slime\say.dm" #include "code\modules\mob\living\simple_animal\slime\slime.dm" +#include "code\modules\mob\living\simple_animal\slime\slime_emote.dm" #include "code\modules\mob\living\simple_animal\slime\subtypes.dm" #include "code\modules\mob\new_player\login.dm" #include "code\modules\mob\new_player\logout.dm"