mirror of
https://github.com/ParadiseSS13/Paradise.git
synced 2026-07-19 11:05:03 +01:00
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>
This commit is contained in:
@@ -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, "<span class='warning'>'[params]' isn't a valid parameter for [key].</span>")
|
||||
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 = "<b>[user]</b> [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("<span class='emote'>[user] ([ghost_follow_link(user, ghost)]) [msg]</span>")
|
||||
|
||||
if(isobserver(user))
|
||||
for(var/mob/dead/observer/ghost in viewers(user))
|
||||
ghost.show_message("<span class=deadsay>[displayed_msg]</span>", EMOTE_VISIBLE)
|
||||
|
||||
else if(emote_type & EMOTE_VISIBLE || user.mind?.miming)
|
||||
user.audible_message(displayed_msg, deaf_message = "<span class='emote'>You see how <b>[user]</b> [msg]</span>")
|
||||
else
|
||||
user.visible_message(displayed_msg, blind_message = "<span class='emote'>You hear how <b>[user]</b> [msg]</span>")
|
||||
|
||||
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, "<span class='warning'>You cannot send emotes (muted).</span>")
|
||||
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, "<span class='warning'>You cannot [key] while [stat]!</span>")
|
||||
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, "<span class='warning'>You cannot use your hands to [key] right now!</span>")
|
||||
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, "<span class='warning'>You cannot send deadchat emotes (muted).</span>")
|
||||
return FALSE
|
||||
if(!(user.client?.prefs.toggles & PREFTOGGLE_CHAT_DEAD))
|
||||
to_chat(src, "<span class='warning'>You have deadchat muted.</span>")
|
||||
return FALSE
|
||||
if(!check_rights(R_ADMIN, FALSE, user))
|
||||
if(!GLOB.dsay_enabled)
|
||||
to_chat(src, "<span class='warning'>Deadchat is globally muted</span>")
|
||||
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 = "<b>[src]</b> [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
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user