mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2025-12-12 02:32:10 +00:00
* 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>
212 lines
8.2 KiB
Plaintext
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)
|