mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2025-12-15 20:22:07 +00:00
Vote clean up and admin additions (#82981)
## About The Pull Request - Fixes `vote_delay` not being a thing. I broke this two years ago but there's no bug report associated. - Admins can now reset the vote delay (to let people vote again instantly) - Admins can now end the current vote immediately (rather than cancelling) - Custom multi and custom single combined into one vote ## Why It's Good For The Game Makes voting a bit easier to use, both for admins and for coders adding new votes.  ## Changelog 🆑 Melbert admin: Custom Single and Custom Multi votes are now combined into one vote admin: Admins can now end votes instantly, rather than cancelling them admin: Admins can now reset the vote cooldown fix: Vote cooldown actually applies now /🆑
This commit is contained in:
@@ -346,3 +346,6 @@
|
||||
#define VOTE_WINNER_METHOD_WEIGHTED_RANDOM "Weighted Random"
|
||||
/// There is no winner for this vote.
|
||||
#define VOTE_WINNER_METHOD_NONE "None"
|
||||
|
||||
/// Returned by [/datum/vote/proc/can_be_initiated] to denote the vote is valid and can be initiated.
|
||||
#define VOTE_AVAILABLE "Vote Available"
|
||||
|
||||
@@ -184,13 +184,13 @@
|
||||
|
||||
/// minimum time between voting sessions (deciseconds, 10 minute default)
|
||||
/datum/config_entry/number/vote_delay
|
||||
default = 6000
|
||||
default = 10 MINUTES
|
||||
integer = FALSE
|
||||
min_val = 0
|
||||
|
||||
/// length of voting period (deciseconds, default 1 minute)
|
||||
/datum/config_entry/number/vote_period
|
||||
default = 600
|
||||
default = 1 MINUTES
|
||||
integer = FALSE
|
||||
min_val = 0
|
||||
|
||||
|
||||
@@ -18,6 +18,8 @@ SUBSYSTEM_DEF(vote)
|
||||
var/list/voted = list()
|
||||
/// A list of all ckeys currently voting for the current vote.
|
||||
var/list/voting = list()
|
||||
/// World.time we started our last vote
|
||||
var/last_vote_time = -INFINITY
|
||||
|
||||
/datum/controller/subsystem/vote/Initialize()
|
||||
for(var/vote_type in subtypesof(/datum/vote))
|
||||
@@ -30,13 +32,17 @@ SUBSYSTEM_DEF(vote)
|
||||
|
||||
return SS_INIT_SUCCESS
|
||||
|
||||
|
||||
// Called by master_controller
|
||||
/datum/controller/subsystem/vote/fire()
|
||||
if(!current_vote)
|
||||
return
|
||||
current_vote.time_remaining = round((current_vote.started_time + CONFIG_GET(number/vote_period) - world.time) / 10)
|
||||
if(current_vote.time_remaining < 0)
|
||||
end_vote()
|
||||
|
||||
/// Ends the current vote.
|
||||
/datum/controller/subsystem/vote/proc/end_vote()
|
||||
ASSERT(current_vote)
|
||||
process_vote_result()
|
||||
SStgui.close_uis(src)
|
||||
reset()
|
||||
@@ -168,24 +174,10 @@ SUBSYSTEM_DEF(vote)
|
||||
* * vote_type - The type of vote to initiate. Can be a [/datum/vote] typepath, a [/datum/vote] instance, or the name of a vote datum.
|
||||
* * vote_initiator_name - The ckey (if player initiated) or name that initiated a vote. Ex: "UristMcAdmin", "the server"
|
||||
* * vote_initiator - If a person / mob initiated the vote, this is the mob that did it
|
||||
* * forced - Whether we're forcing the vote to go through regardless of existing votes or other circumstances. Note: If the vote is admin created, forced becomes true regardless.
|
||||
* * forced - Whether we're forcing the vote to go through regardless of existing votes or other circumstances.
|
||||
*/
|
||||
/datum/controller/subsystem/vote/proc/initiate_vote(vote_type, vote_initiator_name, mob/vote_initiator, forced = FALSE)
|
||||
|
||||
// Even if it's forced we can't vote before we're set up
|
||||
if(!MC_RUNNING(init_stage))
|
||||
if(vote_initiator)
|
||||
to_chat(vote_initiator, span_warning("You cannot start vote now, the server is not done initializing."))
|
||||
return FALSE
|
||||
|
||||
// Check if we have unlimited voting power.
|
||||
// Admin started (or forced) voted will go through even if there's an ongoing vote,
|
||||
// if voting is on cooldown, or regardless if a vote is config disabled (in some cases)
|
||||
var/unlimited_vote_power = forced || !!GLOB.admin_datums[vote_initiator?.ckey]
|
||||
|
||||
if(current_vote && !unlimited_vote_power)
|
||||
if(vote_initiator)
|
||||
to_chat(vote_initiator, span_warning("There is already a vote in progress! Please wait for it to finish."))
|
||||
if(!can_vote_start(vote_initiator, forced))
|
||||
return FALSE
|
||||
|
||||
// Get our actual datum
|
||||
@@ -212,7 +204,7 @@ SUBSYSTEM_DEF(vote)
|
||||
return FALSE
|
||||
|
||||
// Vote can't be initiated in our circumstances? No vote
|
||||
if(!to_vote.can_be_initiated(vote_initiator, unlimited_vote_power))
|
||||
if(to_vote.can_be_initiated(forced) != VOTE_AVAILABLE)
|
||||
return FALSE
|
||||
|
||||
// Okay, we're ready to actually create a vote -
|
||||
@@ -223,8 +215,12 @@ SUBSYSTEM_DEF(vote)
|
||||
if(!to_vote.create_vote(vote_initiator))
|
||||
return FALSE
|
||||
|
||||
if(!vote_initiator_name && vote_initiator)
|
||||
vote_initiator_name = vote_initiator.key
|
||||
|
||||
// Okay, the vote's happening now, for real. Set it up.
|
||||
current_vote = to_vote
|
||||
last_vote_time = world.time
|
||||
|
||||
var/duration = CONFIG_GET(number/vote_period)
|
||||
var/to_display = current_vote.initiate_vote(vote_initiator_name, duration)
|
||||
@@ -248,6 +244,36 @@ SUBSYSTEM_DEF(vote)
|
||||
|
||||
return TRUE
|
||||
|
||||
/**
|
||||
* Checks if we can start a vote.
|
||||
*
|
||||
* * vote_initiator - The mob that initiated the vote.
|
||||
* * forced - Whether we're forcing the vote to go through regardless of existing votes or other circumstances.
|
||||
*
|
||||
* Returns TRUE if we can start a vote, FALSE if we can't.
|
||||
*/
|
||||
/datum/controller/subsystem/vote/proc/can_vote_start(mob/vote_initiator, forced)
|
||||
// Even if it's forced we can't vote before we're set up
|
||||
if(!MC_RUNNING(init_stage))
|
||||
if(vote_initiator)
|
||||
to_chat(vote_initiator, span_warning("You cannot start a vote now, the server is not done initializing."))
|
||||
return FALSE
|
||||
|
||||
if(forced)
|
||||
return TRUE
|
||||
|
||||
var/next_allowed_time = last_vote_time + CONFIG_GET(number/vote_delay)
|
||||
if(next_allowed_time > world.time)
|
||||
if(vote_initiator)
|
||||
to_chat(vote_initiator, span_warning("A vote was initiated recently. You must wait [DisplayTimeText(next_allowed_time - world.time)] before a new vote can be started!"))
|
||||
return FALSE
|
||||
|
||||
if(current_vote)
|
||||
if(vote_initiator)
|
||||
to_chat(vote_initiator, span_warning("There is already a vote in progress! Please wait for it to finish."))
|
||||
return FALSE
|
||||
|
||||
return TRUE
|
||||
/datum/controller/subsystem/vote/ui_state()
|
||||
return GLOB.always_state
|
||||
|
||||
@@ -282,11 +308,12 @@ SUBSYSTEM_DEF(vote)
|
||||
if(!istype(vote))
|
||||
continue
|
||||
|
||||
var/can_vote = vote.can_be_initiated(is_lower_admin)
|
||||
var/list/vote_data = list(
|
||||
"name" = vote_name,
|
||||
"canBeInitiated" = vote.can_be_initiated(forced = is_lower_admin),
|
||||
"canBeInitiated" = can_vote == VOTE_AVAILABLE,
|
||||
"config" = vote.is_config_enabled(),
|
||||
"message" = vote.message,
|
||||
"message" = can_vote == VOTE_AVAILABLE ? vote.default_message : can_vote,
|
||||
)
|
||||
|
||||
if(vote == current_vote)
|
||||
@@ -310,9 +337,15 @@ SUBSYSTEM_DEF(vote)
|
||||
all_vote_data += list(vote_data)
|
||||
|
||||
data["possibleVotes"] = all_vote_data
|
||||
data["LastVoteTime"] = last_vote_time - world.time
|
||||
|
||||
return data
|
||||
|
||||
/datum/controller/subsystem/vote/ui_static_data(mob/user)
|
||||
var/list/data = list()
|
||||
data["VoteCD"] = CONFIG_GET(number/vote_delay)
|
||||
return data
|
||||
|
||||
/datum/controller/subsystem/vote/ui_act(action, params)
|
||||
. = ..()
|
||||
if(.)
|
||||
@@ -323,19 +356,37 @@ SUBSYSTEM_DEF(vote)
|
||||
switch(action)
|
||||
if("cancel")
|
||||
if(!voter.client?.holder)
|
||||
message_admins("[key_name(voter)] tried to cancel the current vote while having no admin holder, \
|
||||
this is potentially a malicious exploit and worth noting.")
|
||||
return
|
||||
|
||||
voter.log_message("cancelled a vote.", LOG_ADMIN)
|
||||
message_admins("[key_name_admin(voter)] has cancelled the current vote.")
|
||||
SStgui.close_uis(src)
|
||||
reset()
|
||||
return TRUE
|
||||
|
||||
if("endNow")
|
||||
if(!voter.client?.holder)
|
||||
message_admins("[key_name(voter)] tried to end the current vote while having no admin holder, \
|
||||
this is potentially a malicious exploit and worth noting.")
|
||||
return
|
||||
|
||||
voter.log_message("ended the current vote early", LOG_ADMIN)
|
||||
message_admins("[key_name_admin(voter)] has ended the current vote.")
|
||||
end_vote()
|
||||
return TRUE
|
||||
|
||||
if("toggleVote")
|
||||
var/datum/vote/selected = possible_votes[params["voteName"]]
|
||||
if(!istype(selected))
|
||||
return
|
||||
if(!check_rights_for(voter.client, R_ADMIN))
|
||||
message_admins("[key_name(voter)] tried to toggle vote availability while having improper rights, \
|
||||
this is potentially a malicious exploit and worth noting.")
|
||||
return
|
||||
|
||||
return selected.toggle_votable(voter)
|
||||
return selected.toggle_votable()
|
||||
|
||||
if("callVote")
|
||||
var/datum/vote/selected = possible_votes[params["voteName"]]
|
||||
@@ -344,7 +395,12 @@ SUBSYSTEM_DEF(vote)
|
||||
|
||||
// Whether the user actually can initiate this vote is checked in initiate_vote,
|
||||
// meaning you can't spoof initiate a vote you're not supposed to be able to
|
||||
return initiate_vote(selected, voter.key, voter)
|
||||
return initiate_vote(
|
||||
vote_type = selected,
|
||||
vote_initiator_name = voter.key,
|
||||
vote_initiator = voter,
|
||||
forced = !!GLOB.admin_datums[voter.ckey],
|
||||
)
|
||||
|
||||
if("voteSingle")
|
||||
return submit_single_vote(voter, params["voteOption"])
|
||||
@@ -352,6 +408,15 @@ SUBSYSTEM_DEF(vote)
|
||||
if("voteMulti")
|
||||
return submit_multi_vote(voter, params["voteOption"])
|
||||
|
||||
if("resetCooldown")
|
||||
if(!voter.client.holder)
|
||||
message_admins("[key_name(voter)] tried to reset the vote cooldown while having no admin holder, \
|
||||
this is potentially a malicious exploit and worth noting.")
|
||||
return
|
||||
|
||||
last_vote_time = -INFINITY
|
||||
return TRUE
|
||||
|
||||
/datum/controller/subsystem/vote/ui_close(mob/user)
|
||||
voting -= user.client?.ckey
|
||||
|
||||
@@ -360,6 +425,10 @@ SUBSYSTEM_DEF(vote)
|
||||
set category = "OOC"
|
||||
set name = "Vote"
|
||||
|
||||
if(!SSvote.initialized)
|
||||
to_chat(usr, span_notice("<i>Voting is not set up yet!</i>"))
|
||||
return
|
||||
|
||||
SSvote.ui_interact(usr)
|
||||
|
||||
/// Datum action given to mobs that allows players to vote on the current vote.
|
||||
|
||||
@@ -15,19 +15,8 @@
|
||||
var/list/default_choices
|
||||
/// Does the name of this vote contain the word "vote"?
|
||||
var/contains_vote_in_name = FALSE
|
||||
/// What message do we want to pass to the player-side vote panel as a tooltip?
|
||||
var/message = "Click to initiate a vote."
|
||||
|
||||
// Internal values used when tracking ongoing votes.
|
||||
// Don't mess with these, change the above values / override procs for subtypes.
|
||||
/// An assoc list of [all choices] to [number of votes in the current running vote].
|
||||
var/list/choices = list()
|
||||
/// A assoc list of [ckey] to [what they voted for in the current running vote].
|
||||
var/list/choices_by_ckey = list()
|
||||
/// The world time this vote was started.
|
||||
var/started_time
|
||||
/// The time remaining in this vote's run.
|
||||
var/time_remaining
|
||||
/// What message do we show as the tooltip of this vote if the vote can be initiated?
|
||||
var/default_message = "Click to initiate a vote."
|
||||
/// The counting method we use for votes.
|
||||
var/count_method = VOTE_COUNT_METHOD_SINGLE
|
||||
/// The method for selecting a winner.
|
||||
@@ -35,6 +24,17 @@
|
||||
/// Should we show details about the number of votes submitted for each option?
|
||||
var/display_statistics = TRUE
|
||||
|
||||
// Internal values used when tracking ongoing votes.
|
||||
// Don't mess with these, change the above values / override procs for subtypes.
|
||||
/// An assoc list of [all choices] to [number of votes in the current running vote].
|
||||
VAR_FINAL/list/choices = list()
|
||||
/// A assoc list of [ckey] to [what they voted for in the current running vote].
|
||||
VAR_FINAL/list/choices_by_ckey = list()
|
||||
/// The world time this vote was started.
|
||||
VAR_FINAL/started_time = -1
|
||||
/// The time remaining in this vote's run.
|
||||
VAR_FINAL/time_remaining = -1
|
||||
|
||||
/**
|
||||
* Used to determine if this vote is a possible
|
||||
* vote type for the vote subsystem.
|
||||
@@ -55,14 +55,13 @@
|
||||
choices.Cut()
|
||||
choices_by_ckey.Cut()
|
||||
started_time = null
|
||||
time_remaining = null
|
||||
time_remaining = -1
|
||||
|
||||
/**
|
||||
* If this vote has a config associated, toggles it between enabled and disabled.
|
||||
* Returns TRUE on a successful toggle, FALSE otherwise
|
||||
*/
|
||||
/datum/vote/proc/toggle_votable(mob/toggler)
|
||||
return FALSE
|
||||
/datum/vote/proc/toggle_votable()
|
||||
return
|
||||
|
||||
/**
|
||||
* If this vote has a config associated, returns its value (True or False, usually).
|
||||
@@ -74,20 +73,18 @@
|
||||
/**
|
||||
* Checks if the passed mob can initiate this vote.
|
||||
*
|
||||
* Return TRUE if the mob can begin the vote, allowing anyone to actually vote on it.
|
||||
* Return FALSE if the mob cannot initiate the vote.
|
||||
* * forced - if being invoked by someone who is an admin
|
||||
*
|
||||
* Return VOTE_AVAILABLE if the mob can initiate the vote.
|
||||
* Return a string with the reason why the mob can't initiate the vote.
|
||||
*/
|
||||
/datum/vote/proc/can_be_initiated(mob/by_who, forced = FALSE)
|
||||
/datum/vote/proc/can_be_initiated(forced = FALSE)
|
||||
SHOULD_CALL_PARENT(TRUE)
|
||||
|
||||
if(started_time)
|
||||
var/next_allowed_time = (started_time + CONFIG_GET(number/vote_delay))
|
||||
if(next_allowed_time > world.time && !forced)
|
||||
message = "A vote was initiated recently. You must wait [DisplayTimeText(next_allowed_time - world.time)] before a new vote can be started!"
|
||||
return FALSE
|
||||
if(!forced && !is_config_enabled())
|
||||
return "This vote is currently disabled by the server configuration."
|
||||
|
||||
message = initial(message)
|
||||
return TRUE
|
||||
return VOTE_AVAILABLE
|
||||
|
||||
/**
|
||||
* Called prior to the vote being initiated.
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
/// The max amount of options someone can have in a custom vote.
|
||||
#define MAX_CUSTOM_VOTE_OPTIONS 10
|
||||
|
||||
/datum/vote/custom_vote/single
|
||||
name = "Custom Standard"
|
||||
message = "Click here to start a custom vote (one selection per voter)"
|
||||
|
||||
/datum/vote/custom_vote/multi
|
||||
name = "Custom Multi"
|
||||
message = "Click here to start a custom multi vote (multiple selections per voter)"
|
||||
count_method = VOTE_COUNT_METHOD_MULTI
|
||||
/datum/vote/custom_vote
|
||||
name = "Custom"
|
||||
default_message = "Click here to start a custom vote."
|
||||
|
||||
// Custom votes ares always accessible.
|
||||
/datum/vote/custom_vote/is_accessible_vote()
|
||||
@@ -17,23 +12,45 @@
|
||||
/datum/vote/custom_vote/reset()
|
||||
default_choices = null
|
||||
override_question = null
|
||||
count_method = VOTE_COUNT_METHOD_SINGLE
|
||||
return ..()
|
||||
|
||||
/datum/vote/custom_vote/can_be_initiated(mob/by_who, forced = FALSE)
|
||||
/datum/vote/custom_vote/can_be_initiated(forced)
|
||||
. = ..()
|
||||
if(!.)
|
||||
return FALSE
|
||||
if(. != VOTE_AVAILABLE)
|
||||
return .
|
||||
if(forced)
|
||||
return .
|
||||
|
||||
// Custom votes can only be created if they're forced to be made.
|
||||
// (Either an admin makes it, or otherwise.)
|
||||
return forced
|
||||
return "Only admins can create custom votes."
|
||||
|
||||
/datum/vote/custom_vote/create_vote(mob/vote_creator)
|
||||
var/custom_count_method = tgui_input_list(
|
||||
user = vote_creator,
|
||||
message = "Single or multiple choice?",
|
||||
title = "Choice Method",
|
||||
items = list("Single", "Multiple"),
|
||||
default = "Single",
|
||||
)
|
||||
switch(custom_count_method)
|
||||
if("Single")
|
||||
count_method = VOTE_COUNT_METHOD_SINGLE
|
||||
if("Multiple")
|
||||
count_method = VOTE_COUNT_METHOD_MULTI
|
||||
if(null)
|
||||
return FALSE
|
||||
else
|
||||
stack_trace("Got '[custom_count_method]' in create_vote() for custom voting.")
|
||||
to_chat(vote_creator, span_boldwarning("Unknown choice method. Contact a coder."))
|
||||
return FALSE
|
||||
|
||||
var/custom_win_method = tgui_input_list(
|
||||
vote_creator,
|
||||
"How should the vote winner be determined?",
|
||||
"Winner Method",
|
||||
list("Simple", "Weighted Random", "No Winner"),
|
||||
user = vote_creator,
|
||||
message = "How should the vote winner be determined?",
|
||||
title = "Winner Method",
|
||||
items = list("Simple", "Weighted Random", "No Winner"),
|
||||
default = "Simple",
|
||||
)
|
||||
switch(custom_win_method)
|
||||
@@ -43,7 +60,10 @@
|
||||
winner_method = VOTE_WINNER_METHOD_WEIGHTED_RANDOM
|
||||
if("No Winner")
|
||||
winner_method = VOTE_WINNER_METHOD_NONE
|
||||
if(null)
|
||||
return FALSE
|
||||
else
|
||||
stack_trace("Got '[custom_win_method]' in create_vote() for custom voting.")
|
||||
to_chat(vote_creator, span_boldwarning("Unknown winner method. Contact a coder."))
|
||||
return FALSE
|
||||
|
||||
@@ -54,7 +74,7 @@
|
||||
list("Yes", "No"),
|
||||
)
|
||||
|
||||
if(display_stats == null)
|
||||
if(isnull(display_stats))
|
||||
return FALSE
|
||||
display_statistics = display_stats == "Yes"
|
||||
|
||||
@@ -74,6 +94,9 @@
|
||||
|
||||
if(!length(default_choices))
|
||||
return FALSE
|
||||
// Sanity for all the tgui input stalling we are doing
|
||||
if(isnull(vote_creator.client?.holder))
|
||||
return FALSE
|
||||
|
||||
return ..()
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/datum/vote/map_vote
|
||||
name = "Map"
|
||||
message = "Vote for next round's map!"
|
||||
default_message = "Vote for next round's map!"
|
||||
count_method = VOTE_COUNT_METHOD_SINGLE
|
||||
winner_method = VOTE_WINNER_METHOD_WEIGHTED_RANDOM
|
||||
display_statistics = FALSE
|
||||
@@ -30,44 +30,26 @@
|
||||
SSmapping.map_voted = TRUE // voted by not voting, very sad.
|
||||
return FALSE
|
||||
|
||||
/datum/vote/map_vote/toggle_votable(mob/toggler)
|
||||
if(!toggler)
|
||||
CRASH("[type] wasn't passed a \"toggler\" mob to toggle_votable.")
|
||||
if(!check_rights_for(toggler.client, R_ADMIN))
|
||||
return FALSE
|
||||
|
||||
/datum/vote/map_vote/toggle_votable()
|
||||
CONFIG_SET(flag/allow_vote_map, !CONFIG_GET(flag/allow_vote_map))
|
||||
return TRUE
|
||||
|
||||
/datum/vote/map_vote/is_config_enabled()
|
||||
return CONFIG_GET(flag/allow_vote_map)
|
||||
|
||||
/datum/vote/map_vote/can_be_initiated(mob/by_who, forced = FALSE)
|
||||
/datum/vote/map_vote/can_be_initiated(forced)
|
||||
. = ..()
|
||||
if(!.)
|
||||
return FALSE
|
||||
|
||||
if(. != VOTE_AVAILABLE)
|
||||
return .
|
||||
if(forced)
|
||||
return TRUE
|
||||
|
||||
return VOTE_AVAILABLE
|
||||
var/number_of_choices = length(check_population())
|
||||
if(number_of_choices < 2)
|
||||
message = "There [number_of_choices == 1 ? "is only one map" : "are no maps"] to choose from."
|
||||
return FALSE
|
||||
|
||||
return "There [number_of_choices == 1 ? "is only one map" : "are no maps"] to choose from."
|
||||
if(SSmapping.map_vote_rocked)
|
||||
return TRUE
|
||||
|
||||
if(!CONFIG_GET(flag/allow_vote_map))
|
||||
message = "Map voting is disabled by server configuration settings."
|
||||
return FALSE
|
||||
|
||||
return VOTE_AVAILABLE
|
||||
if(SSmapping.map_voted)
|
||||
message = "The next map has already been selected."
|
||||
return FALSE
|
||||
|
||||
message = initial(message)
|
||||
return TRUE
|
||||
return "The next map has already been selected."
|
||||
return VOTE_AVAILABLE
|
||||
|
||||
/// Before we create a vote, remove all maps from our choices that are outside of our population range.
|
||||
/// Note that this can result in zero remaining choices for our vote, which is not ideal (but ultimately okay).
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
CHOICE_RESTART,
|
||||
CHOICE_CONTINUE,
|
||||
)
|
||||
message = "Vote to restart the ongoing round."
|
||||
default_message = "Vote to restart the ongoing round. \
|
||||
Only works if there are no non-AFK admins online."
|
||||
|
||||
/// This proc checks to see if any admins are online for the purposes of this vote to see if it can pass. Returns TRUE if there are valid admins online (Has +SERVER and is not AFK), FALSE otherwise.
|
||||
/datum/vote/restart_vote/proc/admins_present()
|
||||
@@ -19,36 +20,24 @@
|
||||
|
||||
return FALSE
|
||||
|
||||
/datum/vote/restart_vote/toggle_votable(mob/toggler)
|
||||
if(!toggler)
|
||||
CRASH("[type] wasn't passed a \"toggler\" mob to toggle_votable.")
|
||||
|
||||
if(!check_rights_for(toggler.client, R_ADMIN))
|
||||
return FALSE
|
||||
|
||||
/datum/vote/restart_vote/toggle_votable()
|
||||
CONFIG_SET(flag/allow_vote_restart, !CONFIG_GET(flag/allow_vote_restart))
|
||||
return TRUE
|
||||
|
||||
/datum/vote/restart_vote/is_config_enabled()
|
||||
return CONFIG_GET(flag/allow_vote_restart)
|
||||
|
||||
/datum/vote/restart_vote/can_be_initiated(mob/by_who, forced)
|
||||
/datum/vote/restart_vote/create_vote(mob/vote_creator)
|
||||
. = ..()
|
||||
if(!.)
|
||||
return FALSE
|
||||
return
|
||||
if(!admins_present())
|
||||
return
|
||||
async_alert_about_admins(vote_creator)
|
||||
|
||||
if(!forced && !CONFIG_GET(flag/allow_vote_restart))
|
||||
message = "Restart voting is disabled by server configuration settings."
|
||||
return FALSE
|
||||
|
||||
// We still want players to be able to vote to restart even if valid admins are online. Let's update the message just so that the player is aware of this fact.
|
||||
// We don't want to lock-out the vote though, so we'll return TRUE.
|
||||
if(admins_present())
|
||||
message = "Regardless of the results of this vote, the round will not automatically restart because an admin is online."
|
||||
return TRUE
|
||||
|
||||
message = initial(message)
|
||||
return TRUE
|
||||
/datum/vote/restart_vote/proc/async_alert_about_admins(mob/vote_creator)
|
||||
set waitfor = FALSE
|
||||
tgui_alert(vote_creator, "Note: Regardless of the results of this vote, \
|
||||
the round will not automatically restart because an active admin is online.")
|
||||
|
||||
/datum/vote/restart_vote/get_vote_result(list/non_voters)
|
||||
if(!CONFIG_GET(flag/default_no_vote))
|
||||
|
||||
@@ -10,58 +10,40 @@
|
||||
CHOICE_TO_ROCK,
|
||||
CHOICE_NOT_TO_ROCK,
|
||||
)
|
||||
message = "Override the current map vote."
|
||||
default_message = "Override the current map vote."
|
||||
/// The number of times we have rocked the vote thus far.
|
||||
var/rocking_votes = 0
|
||||
|
||||
/datum/vote/rock_the_vote/toggle_votable(mob/toggler)
|
||||
if(!toggler)
|
||||
CRASH("[type] wasn't passed a \"toggler\" mob to toggle_votable.")
|
||||
if(!check_rights_for(toggler.client, R_ADMIN))
|
||||
return FALSE
|
||||
|
||||
/datum/vote/rock_the_vote/toggle_votable()
|
||||
CONFIG_SET(flag/allow_rock_the_vote, !CONFIG_GET(flag/allow_rock_the_vote))
|
||||
return TRUE
|
||||
|
||||
/datum/vote/rock_the_vote/is_config_enabled()
|
||||
return CONFIG_GET(flag/allow_rock_the_vote)
|
||||
|
||||
/datum/vote/rock_the_vote/can_be_initiated(mob/by_who, forced)
|
||||
/datum/vote/rock_the_vote/can_be_initiated(forced)
|
||||
. = ..()
|
||||
|
||||
if(!.)
|
||||
return FALSE
|
||||
|
||||
if(!forced && !CONFIG_GET(flag/allow_rock_the_vote))
|
||||
message = "Rocking the vote is disabled by this server's configuration settings."
|
||||
return FALSE
|
||||
if(. != VOTE_AVAILABLE)
|
||||
return .
|
||||
|
||||
if(SSticker.current_state == GAME_STATE_FINISHED)
|
||||
message = "The game is finished, no map votes can be initiated."
|
||||
return FALSE
|
||||
return "The game is finished, no map votes can be initiated."
|
||||
|
||||
if(rocking_votes >= CONFIG_GET(number/max_rocking_votes))
|
||||
message = "The maximum number of times to rock the vote has been reached."
|
||||
return FALSE
|
||||
return "The maximum number of times to rock the vote has been reached."
|
||||
|
||||
if(SSmapping.map_vote_rocked)
|
||||
message = "The vote has already been rocked! Initiate a map vote!"
|
||||
return FALSE
|
||||
return "The vote has already been rocked! Initiate a map vote!"
|
||||
|
||||
if(!SSmapping.map_voted)
|
||||
message = "Rocking the vote is disabled because no map has been voted on yet!"
|
||||
return FALSE
|
||||
return "Rocking the vote is disabled because no map has been voted on yet!"
|
||||
|
||||
if(SSmapping.map_force_chosen)
|
||||
message = "Rocking the vote is disabled because an admin has forcibly set the map!"
|
||||
return FALSE
|
||||
return "Rocking the vote is disabled because an admin has forcibly set the map!"
|
||||
|
||||
if(EMERGENCY_ESCAPED_OR_ENDGAMED && SSmapping.map_voted)
|
||||
message = "The emergency shuttle has already left the station and the next map has already been chosen!"
|
||||
return FALSE
|
||||
return "The emergency shuttle has already left the station and the next map has already been chosen!"
|
||||
|
||||
message = initial(message)
|
||||
return TRUE
|
||||
return VOTE_AVAILABLE
|
||||
|
||||
/datum/vote/rock_the_vote/finalize_vote(winning_option)
|
||||
rocking_votes++
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
Box,
|
||||
Button,
|
||||
Collapsible,
|
||||
Dimmer,
|
||||
Icon,
|
||||
LabeledList,
|
||||
NoticeBox,
|
||||
@@ -59,11 +60,13 @@ type Data = {
|
||||
possibleVotes: Vote[];
|
||||
user: UserData;
|
||||
voting: string[];
|
||||
LastVoteTime: number;
|
||||
VoteCD: number;
|
||||
};
|
||||
|
||||
export const VotePanel = (props) => {
|
||||
const { data } = useBackend<Data>();
|
||||
const { currentVote, user } = data;
|
||||
const { act, data } = useBackend<Data>();
|
||||
const { currentVote, user, LastVoteTime, VoteCD } = data;
|
||||
|
||||
/**
|
||||
* Adds the voting type to title if there is an ongoing vote.
|
||||
@@ -81,7 +84,19 @@ export const VotePanel = (props) => {
|
||||
<Window title={windowTitle} width={400} height={500}>
|
||||
<Window.Content>
|
||||
<Stack fill vertical>
|
||||
<Section title="Create Vote">
|
||||
<Section
|
||||
title="Create Vote"
|
||||
buttons={
|
||||
!!user.isLowerAdmin && (
|
||||
<Button
|
||||
icon="refresh"
|
||||
content="Reset Cooldown"
|
||||
disabled={LastVoteTime + VoteCD <= 0}
|
||||
onClick={() => act('resetCooldown')}
|
||||
/>
|
||||
)
|
||||
}
|
||||
>
|
||||
<VoteOptions />
|
||||
{!!user.isLowerAdmin && currentVote && <VotersList />}
|
||||
</Section>
|
||||
@@ -93,26 +108,54 @@ export const VotePanel = (props) => {
|
||||
);
|
||||
};
|
||||
|
||||
const VoteOptionDimmer = (props) => {
|
||||
const { data } = useBackend<Data>();
|
||||
const { LastVoteTime, VoteCD } = data;
|
||||
|
||||
return (
|
||||
<Dimmer>
|
||||
<Box textAlign="center">
|
||||
<Box fontSize={2} bold>
|
||||
Vote Cooldown
|
||||
</Box>
|
||||
<Box fontSize={1.5}>{Math.floor((VoteCD + LastVoteTime) / 10)}s</Box>
|
||||
</Box>
|
||||
</Dimmer>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* The create vote options menu. Only upper admins can disable voting.
|
||||
* @returns A section visible to everyone with vote options.
|
||||
*/
|
||||
const VoteOptions = (props) => {
|
||||
const { act, data } = useBackend<Data>();
|
||||
const { possibleVotes, user } = data;
|
||||
const { possibleVotes, user, LastVoteTime, VoteCD } = data;
|
||||
|
||||
return (
|
||||
<Stack.Item>
|
||||
<Collapsible title="Start a Vote">
|
||||
<Section>
|
||||
{LastVoteTime + VoteCD > 0 && <VoteOptionDimmer />}
|
||||
<Stack vertical justify="space-between">
|
||||
{possibleVotes.map((option) => (
|
||||
<Stack.Item key={option.name}>
|
||||
{!!user.isLowerAdmin && option.config !== VoteConfig.None && (
|
||||
<Stack>
|
||||
{!!user.isLowerAdmin && (
|
||||
<Stack.Item>
|
||||
<Button.Checkbox
|
||||
mr={option.config === VoteConfig.Disabled ? 1 : 1.6}
|
||||
width={7}
|
||||
color="red"
|
||||
checked={option.config === VoteConfig.Enabled}
|
||||
disabled={!user.isUpperAdmin}
|
||||
disabled={
|
||||
!user.isUpperAdmin ||
|
||||
option.config === VoteConfig.None
|
||||
}
|
||||
tooltip={
|
||||
option.config === VoteConfig.None
|
||||
? 'This vote cannot be disabled.'
|
||||
: null
|
||||
}
|
||||
content={
|
||||
option.config === VoteConfig.Enabled
|
||||
? 'Enabled'
|
||||
@@ -124,8 +167,12 @@ const VoteOptions = (props) => {
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Stack.Item>
|
||||
)}
|
||||
<Stack.Item>
|
||||
<Button
|
||||
width={12}
|
||||
textAlign={'center'}
|
||||
disabled={!option.canBeInitiated}
|
||||
tooltip={option.message}
|
||||
content={option.name}
|
||||
@@ -136,8 +183,11 @@ const VoteOptions = (props) => {
|
||||
}
|
||||
/>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Stack.Item>
|
||||
))}
|
||||
</Stack>
|
||||
</Section>
|
||||
</Collapsible>
|
||||
</Stack.Item>
|
||||
);
|
||||
@@ -153,11 +203,11 @@ const VotersList = (props) => {
|
||||
return (
|
||||
<Stack.Item>
|
||||
<Collapsible
|
||||
title={`View Voters${
|
||||
data.voting.length ? `: ${data.voting.length}` : ''
|
||||
title={`View Active Voters${
|
||||
data.voting.length ? ` (${data.voting.length})` : ''
|
||||
}`}
|
||||
>
|
||||
<Section height={8} fill scrollable>
|
||||
<Section height={4} fill scrollable>
|
||||
{data.voting.map((voter) => {
|
||||
return <Box key={voter}>{voter}</Box>;
|
||||
})}
|
||||
@@ -275,6 +325,17 @@ const TimePanel = (props) => {
|
||||
{currentVote?.timeRemaining || 0}s
|
||||
</Box>
|
||||
{!!user.isLowerAdmin && (
|
||||
<Stack>
|
||||
<Stack.Item>
|
||||
<Button
|
||||
color="green"
|
||||
disabled={!user.isLowerAdmin || !currentVote}
|
||||
onClick={() => act('endNow')}
|
||||
>
|
||||
End Now
|
||||
</Button>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Button
|
||||
color="red"
|
||||
disabled={!user.isLowerAdmin || !currentVote}
|
||||
@@ -282,6 +343,8 @@ const TimePanel = (props) => {
|
||||
>
|
||||
Cancel Vote
|
||||
</Button>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
)}
|
||||
</Stack>
|
||||
</Section>
|
||||
|
||||
Reference in New Issue
Block a user