Files
Bubberstation/code/datums/components/deadchat_control.dm
SkyratBot 6267efa456 [MIRROR] Makes deadchat control's examine only shows up for ghosts (#4380)
* Makes deadchat control's examine only shows up for ghosts (#57926)

This PR early returns the on_examine proc for deadchat control if the examiner is not an observer.
So, only ghosts (deadchat) are allowed to see if something is under deadchat control. Humans don't need to know (or shouldn't know) if deadchat is expanding its reach into the physical realm.

* Makes deadchat control's examine only shows up for ghosts

Co-authored-by: MrMelbert <51863163+MrMelbert@users.noreply.github.com>
2021-03-24 23:36:00 +00:00

212 lines
8.2 KiB
Plaintext

#define DEMOCRACY_MODE "democracy"
#define ANARCHY_MODE "anarchy"
/**
* Deadchat Plays Things - The Componenting
*
* Allows deadchat to control stuff and things by typing commands into chat.
* These commands will then trigger callbacks to execute procs!
*/
/datum/component/deadchat_control
dupe_mode = COMPONENT_DUPE_UNIQUE
/// The id for the DEMOCRACY_MODE looping vote timer.
var/timerid
/// Assoc list of key-chat command string, value-callback pairs. list("right" = CALLBACK(GLOBAL_PROC, .proc/_step, src, EAST))
var/list/datum/callback/inputs = list()
/// Assoc list of ckey:value pairings. In DEMOCRACY_MODE, value is the player's vote. In ANARCHY_MODE, value is world.time when their cooldown expires.
var/list/ckey_to_cooldown = list()
/// List of everything orbitting this component's parent.
var/orbiters = list()
/// Either DEMOCRACY_MODE which will execute a single command after the cooldown based on player votes, or ANARCHY_MODE which allows each player to do a single command every cooldown.
var/deadchat_mode
/// In DEMOCRACY_MODE, this is how long players have to vote on an input. In ANARCHY_MODE, this is how long between inputs for each unique player.
var/input_cooldown
/// Callback invoked when this component is Destroy()ed to allow the parent to return to a non-deadchat controlled state.
var/datum/callback/on_removal
/datum/component/deadchat_control/Initialize(_deadchat_mode, _inputs, _input_cooldown = 12 SECONDS, _on_removal)
if(!isatom(parent))
return COMPONENT_INCOMPATIBLE
RegisterSignal(parent, COMSIG_ATOM_ORBIT_BEGIN, .proc/orbit_begin)
RegisterSignal(parent, COMSIG_ATOM_ORBIT_STOP, .proc/orbit_stop)
RegisterSignal(parent, COMSIG_VV_TOPIC, .proc/handle_vv_topic)
RegisterSignal(parent, COMSIG_PARENT_EXAMINE, .proc/on_examine)
deadchat_mode = _deadchat_mode
inputs = _inputs
input_cooldown = _input_cooldown
on_removal = _on_removal
if(deadchat_mode == DEMOCRACY_MODE)
timerid = addtimer(CALLBACK(src, .proc/democracy_loop), input_cooldown, TIMER_STOPPABLE | TIMER_LOOP)
notify_ghosts("[parent] is now deadchat controllable!", source = parent, action = NOTIFY_ORBIT, header="Something Interesting!")
/datum/component/deadchat_control/Destroy(force, silent)
on_removal?.Invoke()
inputs = null
orbiters = null
ckey_to_cooldown = null
return ..()
/datum/component/deadchat_control/proc/deadchat_react(mob/source, message)
SIGNAL_HANDLER
message = lowertext(message)
if(!inputs[message])
return
if(deadchat_mode == ANARCHY_MODE)
var/cooldown = ckey_to_cooldown[source.ckey] - world.time
if(cooldown > 0)
to_chat(source, "<span class='warning'>Your deadchat control inputs are still on cooldown for another [cooldown * 0.1] seconds.</span>")
return MOB_DEADSAY_SIGNAL_INTERCEPT
inputs[message].Invoke()
ckey_to_cooldown[source.ckey] = world.time + input_cooldown
to_chat(source, "<span class='notice'>\"[message]\" input accepted. You are now on cooldown for [input_cooldown * 0.1] seconds.</span>")
return MOB_DEADSAY_SIGNAL_INTERCEPT
if(deadchat_mode == DEMOCRACY_MODE)
ckey_to_cooldown[source.ckey] = message
to_chat(source, "<span class='notice'>You have voted for \"[message]\".</span>")
return MOB_DEADSAY_SIGNAL_INTERCEPT
/datum/component/deadchat_control/proc/democracy_loop()
if(QDELETED(parent) || deadchat_mode != DEMOCRACY_MODE)
deltimer(timerid)
return
var/result = count_democracy_votes()
if(!isnull(result))
inputs[result].Invoke()
var/message = "<span class='deadsay italics bold'>[parent] has done action [result]!<br>New vote started. It will end in [input_cooldown * 0.1] seconds.</span>"
for(var/M in orbiters)
to_chat(M, message)
else
var/message = "<span class='deadsay italics bold'>No votes were cast this cycle.</span>"
for(var/M in orbiters)
to_chat(M, message)
/datum/component/deadchat_control/proc/count_democracy_votes()
if(!length(ckey_to_cooldown))
return
var/list/votes = list()
for(var/command in inputs)
votes["[command]"] = 0
for(var/vote in ckey_to_cooldown)
votes[ckey_to_cooldown[vote]]++
ckey_to_cooldown.Remove(vote)
// Solve which had most votes.
var/prev_value = 0
var/result
for(var/vote in votes)
if(votes[vote] > prev_value)
prev_value = votes[vote]
result = vote
if(result in inputs)
return result
/datum/component/deadchat_control/vv_edit_var(var_name, var_value)
. = ..()
if(!.)
return
if(var_name != NAMEOF(src, deadchat_mode))
return
ckey_to_cooldown = list()
if(var_value == DEMOCRACY_MODE)
timerid = addtimer(CALLBACK(src, .proc/democracy_loop), input_cooldown, TIMER_STOPPABLE | TIMER_LOOP)
else
deltimer(timerid)
/datum/component/deadchat_control/proc/orbit_begin(atom/source, atom/orbiter)
SIGNAL_HANDLER
RegisterSignal(orbiter, COMSIG_MOB_DEADSAY, .proc/deadchat_react)
orbiters |= orbiter
/datum/component/deadchat_control/proc/orbit_stop(atom/source, atom/orbiter)
SIGNAL_HANDLER
if(orbiter in orbiters)
UnregisterSignal(orbiter, COMSIG_MOB_DEADSAY)
orbiters -= orbiter
/// Allows for this component to be removed via a dedicated VV dropdown entry.
/datum/component/deadchat_control/proc/handle_vv_topic(datum/source, mob/user, list/href_list)
SIGNAL_HANDLER
if(!href_list[VV_HK_DEADCHAT_PLAYS] || !check_rights(R_FUN))
return
. = COMPONENT_VV_HANDLED
INVOKE_ASYNC(src, .proc/async_handle_vv_topic, user, href_list)
/// Async proc handling the alert input and associated logic for an admin removing this component via the VV dropdown.
/datum/component/deadchat_control/proc/async_handle_vv_topic(mob/user, list/href_list)
if(alert(user, "Remove deadchat control from [parent]?", "Deadchat Plays [parent]", "Remove", "Cancel") == "Remove")
// Quick sanity check as this is an async call.
if(QDELETED(src))
return
to_chat(user, "<span class='notice'>Deadchat can no longer control [parent].</span>")
log_admin("[key_name(user)] has removed deadchat control from [parent]")
message_admins("<span class='notice'>[key_name(user)] has removed deadchat control from [parent]</span>")
qdel(src)
/// Informs any examiners to the inputs available as part of deadchat control, as well as the current operating mode and cooldowns.
/datum/component/deadchat_control/proc/on_examine(atom/A, mob/user, list/examine_list)
SIGNAL_HANDLER
if(!isobserver(user))
return
examine_list += "<span class='notice'>[A.p_theyre(TRUE)] currently under deadchat control using the [deadchat_mode] ruleset!</span>"
if(deadchat_mode == DEMOCRACY_MODE)
examine_list += "<span class='notice'>Type a command into chat to vote on an action. This happens once every [input_cooldown * 0.1] seconds.</span>"
else if(deadchat_mode == ANARCHY_MODE)
examine_list += "<span class='notice'>Type a command into chat to perform. You may do this once every [input_cooldown * 0.1] seconds.</span>"
var/extended_examine = "<span class='notice'>Command list:"
for(var/possible_input in inputs)
extended_examine += " [possible_input]"
extended_examine += ".</span>"
examine_list += extended_examine
/**
* Deadchat Moves Things
*
* A special variant of the deadchat_control component that comes pre-baked with all the hottest inputs for a spicy
* singularity or vomit goose.
*/
/datum/component/deadchat_control/cardinal_movement/Initialize(_deadchat_mode, _inputs, _input_cooldown, _on_removal)
if(!ismovable(parent))
return COMPONENT_INCOMPATIBLE
. = ..()
inputs["up"] = CALLBACK(GLOBAL_PROC, .proc/_step, parent, NORTH)
inputs["down"] = CALLBACK(GLOBAL_PROC, .proc/_step, parent, SOUTH)
inputs["left"] = CALLBACK(GLOBAL_PROC, .proc/_step, parent, WEST)
inputs["right"] = CALLBACK(GLOBAL_PROC, .proc/_step, parent, EAST)
/**
* Deadchat Moves Things
*
* A special variant of the deadchat_control component that comes pre-baked with all the hottest inputs for spicy
* immovable rod.
*/
/datum/component/deadchat_control/immovable_rod/Initialize(_deadchat_mode, _inputs, _input_cooldown, _on_removal)
if(!istype(parent, /obj/effect/immovablerod))
return COMPONENT_INCOMPATIBLE
. = ..()
inputs["up"] = CALLBACK(parent, /obj/effect/immovablerod.proc/walk_in_direction, NORTH)
inputs["down"] = CALLBACK(parent, /obj/effect/immovablerod.proc/walk_in_direction, SOUTH)
inputs["left"] = CALLBACK(parent, /obj/effect/immovablerod.proc/walk_in_direction, WEST)
inputs["right"] = CALLBACK(parent, /obj/effect/immovablerod.proc/walk_in_direction, EAST)