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"
|
#define VOTE_WINNER_METHOD_WEIGHTED_RANDOM "Weighted Random"
|
||||||
/// There is no winner for this vote.
|
/// There is no winner for this vote.
|
||||||
#define VOTE_WINNER_METHOD_NONE "None"
|
#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)
|
/// minimum time between voting sessions (deciseconds, 10 minute default)
|
||||||
/datum/config_entry/number/vote_delay
|
/datum/config_entry/number/vote_delay
|
||||||
default = 6000
|
default = 10 MINUTES
|
||||||
integer = FALSE
|
integer = FALSE
|
||||||
min_val = 0
|
min_val = 0
|
||||||
|
|
||||||
/// length of voting period (deciseconds, default 1 minute)
|
/// length of voting period (deciseconds, default 1 minute)
|
||||||
/datum/config_entry/number/vote_period
|
/datum/config_entry/number/vote_period
|
||||||
default = 600
|
default = 1 MINUTES
|
||||||
integer = FALSE
|
integer = FALSE
|
||||||
min_val = 0
|
min_val = 0
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ SUBSYSTEM_DEF(vote)
|
|||||||
var/list/voted = list()
|
var/list/voted = list()
|
||||||
/// A list of all ckeys currently voting for the current vote.
|
/// A list of all ckeys currently voting for the current vote.
|
||||||
var/list/voting = list()
|
var/list/voting = list()
|
||||||
|
/// World.time we started our last vote
|
||||||
|
var/last_vote_time = -INFINITY
|
||||||
|
|
||||||
/datum/controller/subsystem/vote/Initialize()
|
/datum/controller/subsystem/vote/Initialize()
|
||||||
for(var/vote_type in subtypesof(/datum/vote))
|
for(var/vote_type in subtypesof(/datum/vote))
|
||||||
@@ -30,16 +32,20 @@ SUBSYSTEM_DEF(vote)
|
|||||||
|
|
||||||
return SS_INIT_SUCCESS
|
return SS_INIT_SUCCESS
|
||||||
|
|
||||||
|
|
||||||
// Called by master_controller
|
// Called by master_controller
|
||||||
/datum/controller/subsystem/vote/fire()
|
/datum/controller/subsystem/vote/fire()
|
||||||
if(!current_vote)
|
if(!current_vote)
|
||||||
return
|
return
|
||||||
current_vote.time_remaining = round((current_vote.started_time + CONFIG_GET(number/vote_period) - world.time) / 10)
|
current_vote.time_remaining = round((current_vote.started_time + CONFIG_GET(number/vote_period) - world.time) / 10)
|
||||||
if(current_vote.time_remaining < 0)
|
if(current_vote.time_remaining < 0)
|
||||||
process_vote_result()
|
end_vote()
|
||||||
SStgui.close_uis(src)
|
|
||||||
reset()
|
/// Ends the current vote.
|
||||||
|
/datum/controller/subsystem/vote/proc/end_vote()
|
||||||
|
ASSERT(current_vote)
|
||||||
|
process_vote_result()
|
||||||
|
SStgui.close_uis(src)
|
||||||
|
reset()
|
||||||
|
|
||||||
/// Resets all of our vars after votes conclude / are cancelled.
|
/// Resets all of our vars after votes conclude / are cancelled.
|
||||||
/datum/controller/subsystem/vote/proc/reset()
|
/datum/controller/subsystem/vote/proc/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_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_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
|
* * 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)
|
/datum/controller/subsystem/vote/proc/initiate_vote(vote_type, vote_initiator_name, mob/vote_initiator, forced = FALSE)
|
||||||
|
if(!can_vote_start(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 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."))
|
|
||||||
return FALSE
|
return FALSE
|
||||||
|
|
||||||
// Get our actual datum
|
// Get our actual datum
|
||||||
@@ -212,7 +204,7 @@ SUBSYSTEM_DEF(vote)
|
|||||||
return FALSE
|
return FALSE
|
||||||
|
|
||||||
// Vote can't be initiated in our circumstances? No vote
|
// 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
|
return FALSE
|
||||||
|
|
||||||
// Okay, we're ready to actually create a vote -
|
// Okay, we're ready to actually create a vote -
|
||||||
@@ -223,8 +215,12 @@ SUBSYSTEM_DEF(vote)
|
|||||||
if(!to_vote.create_vote(vote_initiator))
|
if(!to_vote.create_vote(vote_initiator))
|
||||||
return FALSE
|
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.
|
// Okay, the vote's happening now, for real. Set it up.
|
||||||
current_vote = to_vote
|
current_vote = to_vote
|
||||||
|
last_vote_time = world.time
|
||||||
|
|
||||||
var/duration = CONFIG_GET(number/vote_period)
|
var/duration = CONFIG_GET(number/vote_period)
|
||||||
var/to_display = current_vote.initiate_vote(vote_initiator_name, duration)
|
var/to_display = current_vote.initiate_vote(vote_initiator_name, duration)
|
||||||
@@ -248,6 +244,36 @@ SUBSYSTEM_DEF(vote)
|
|||||||
|
|
||||||
return TRUE
|
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()
|
/datum/controller/subsystem/vote/ui_state()
|
||||||
return GLOB.always_state
|
return GLOB.always_state
|
||||||
|
|
||||||
@@ -282,11 +308,12 @@ SUBSYSTEM_DEF(vote)
|
|||||||
if(!istype(vote))
|
if(!istype(vote))
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
var/can_vote = vote.can_be_initiated(is_lower_admin)
|
||||||
var/list/vote_data = list(
|
var/list/vote_data = list(
|
||||||
"name" = vote_name,
|
"name" = vote_name,
|
||||||
"canBeInitiated" = vote.can_be_initiated(forced = is_lower_admin),
|
"canBeInitiated" = can_vote == VOTE_AVAILABLE,
|
||||||
"config" = vote.is_config_enabled(),
|
"config" = vote.is_config_enabled(),
|
||||||
"message" = vote.message,
|
"message" = can_vote == VOTE_AVAILABLE ? vote.default_message : can_vote,
|
||||||
)
|
)
|
||||||
|
|
||||||
if(vote == current_vote)
|
if(vote == current_vote)
|
||||||
@@ -310,9 +337,15 @@ SUBSYSTEM_DEF(vote)
|
|||||||
all_vote_data += list(vote_data)
|
all_vote_data += list(vote_data)
|
||||||
|
|
||||||
data["possibleVotes"] = all_vote_data
|
data["possibleVotes"] = all_vote_data
|
||||||
|
data["LastVoteTime"] = last_vote_time - world.time
|
||||||
|
|
||||||
return data
|
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)
|
/datum/controller/subsystem/vote/ui_act(action, params)
|
||||||
. = ..()
|
. = ..()
|
||||||
if(.)
|
if(.)
|
||||||
@@ -323,19 +356,37 @@ SUBSYSTEM_DEF(vote)
|
|||||||
switch(action)
|
switch(action)
|
||||||
if("cancel")
|
if("cancel")
|
||||||
if(!voter.client?.holder)
|
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
|
return
|
||||||
|
|
||||||
voter.log_message("cancelled a vote.", LOG_ADMIN)
|
voter.log_message("cancelled a vote.", LOG_ADMIN)
|
||||||
message_admins("[key_name_admin(voter)] has cancelled the current vote.")
|
message_admins("[key_name_admin(voter)] has cancelled the current vote.")
|
||||||
|
SStgui.close_uis(src)
|
||||||
reset()
|
reset()
|
||||||
return TRUE
|
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")
|
if("toggleVote")
|
||||||
var/datum/vote/selected = possible_votes[params["voteName"]]
|
var/datum/vote/selected = possible_votes[params["voteName"]]
|
||||||
if(!istype(selected))
|
if(!istype(selected))
|
||||||
return
|
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")
|
if("callVote")
|
||||||
var/datum/vote/selected = possible_votes[params["voteName"]]
|
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,
|
// 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
|
// 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")
|
if("voteSingle")
|
||||||
return submit_single_vote(voter, params["voteOption"])
|
return submit_single_vote(voter, params["voteOption"])
|
||||||
@@ -352,6 +408,15 @@ SUBSYSTEM_DEF(vote)
|
|||||||
if("voteMulti")
|
if("voteMulti")
|
||||||
return submit_multi_vote(voter, params["voteOption"])
|
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)
|
/datum/controller/subsystem/vote/ui_close(mob/user)
|
||||||
voting -= user.client?.ckey
|
voting -= user.client?.ckey
|
||||||
|
|
||||||
@@ -360,6 +425,10 @@ SUBSYSTEM_DEF(vote)
|
|||||||
set category = "OOC"
|
set category = "OOC"
|
||||||
set name = "Vote"
|
set name = "Vote"
|
||||||
|
|
||||||
|
if(!SSvote.initialized)
|
||||||
|
to_chat(usr, span_notice("<i>Voting is not set up yet!</i>"))
|
||||||
|
return
|
||||||
|
|
||||||
SSvote.ui_interact(usr)
|
SSvote.ui_interact(usr)
|
||||||
|
|
||||||
/// Datum action given to mobs that allows players to vote on the current vote.
|
/// Datum action given to mobs that allows players to vote on the current vote.
|
||||||
|
|||||||
@@ -15,19 +15,8 @@
|
|||||||
var/list/default_choices
|
var/list/default_choices
|
||||||
/// Does the name of this vote contain the word "vote"?
|
/// Does the name of this vote contain the word "vote"?
|
||||||
var/contains_vote_in_name = FALSE
|
var/contains_vote_in_name = FALSE
|
||||||
/// What message do we want to pass to the player-side vote panel as a tooltip?
|
/// What message do we show as the tooltip of this vote if the vote can be initiated?
|
||||||
var/message = "Click to initiate a vote."
|
var/default_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
|
|
||||||
/// The counting method we use for votes.
|
/// The counting method we use for votes.
|
||||||
var/count_method = VOTE_COUNT_METHOD_SINGLE
|
var/count_method = VOTE_COUNT_METHOD_SINGLE
|
||||||
/// The method for selecting a winner.
|
/// The method for selecting a winner.
|
||||||
@@ -35,6 +24,17 @@
|
|||||||
/// Should we show details about the number of votes submitted for each option?
|
/// Should we show details about the number of votes submitted for each option?
|
||||||
var/display_statistics = TRUE
|
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
|
* Used to determine if this vote is a possible
|
||||||
* vote type for the vote subsystem.
|
* vote type for the vote subsystem.
|
||||||
@@ -55,14 +55,13 @@
|
|||||||
choices.Cut()
|
choices.Cut()
|
||||||
choices_by_ckey.Cut()
|
choices_by_ckey.Cut()
|
||||||
started_time = null
|
started_time = null
|
||||||
time_remaining = null
|
time_remaining = -1
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If this vote has a config associated, toggles it between enabled and disabled.
|
* 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)
|
/datum/vote/proc/toggle_votable()
|
||||||
return FALSE
|
return
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If this vote has a config associated, returns its value (True or False, usually).
|
* 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.
|
* 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.
|
* * forced - if being invoked by someone who is an admin
|
||||||
* Return FALSE if the mob cannot initiate the vote.
|
*
|
||||||
|
* 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)
|
SHOULD_CALL_PARENT(TRUE)
|
||||||
|
|
||||||
if(started_time)
|
if(!forced && !is_config_enabled())
|
||||||
var/next_allowed_time = (started_time + CONFIG_GET(number/vote_delay))
|
return "This vote is currently disabled by the server configuration."
|
||||||
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
|
|
||||||
|
|
||||||
message = initial(message)
|
return VOTE_AVAILABLE
|
||||||
return TRUE
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called prior to the vote being initiated.
|
* Called prior to the vote being initiated.
|
||||||
|
|||||||
@@ -1,14 +1,9 @@
|
|||||||
/// The max amount of options someone can have in a custom vote.
|
/// The max amount of options someone can have in a custom vote.
|
||||||
#define MAX_CUSTOM_VOTE_OPTIONS 10
|
#define MAX_CUSTOM_VOTE_OPTIONS 10
|
||||||
|
|
||||||
/datum/vote/custom_vote/single
|
/datum/vote/custom_vote
|
||||||
name = "Custom Standard"
|
name = "Custom"
|
||||||
message = "Click here to start a custom vote (one selection per voter)"
|
default_message = "Click here to start a custom vote."
|
||||||
|
|
||||||
/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
|
|
||||||
|
|
||||||
// Custom votes ares always accessible.
|
// Custom votes ares always accessible.
|
||||||
/datum/vote/custom_vote/is_accessible_vote()
|
/datum/vote/custom_vote/is_accessible_vote()
|
||||||
@@ -17,23 +12,45 @@
|
|||||||
/datum/vote/custom_vote/reset()
|
/datum/vote/custom_vote/reset()
|
||||||
default_choices = null
|
default_choices = null
|
||||||
override_question = null
|
override_question = null
|
||||||
|
count_method = VOTE_COUNT_METHOD_SINGLE
|
||||||
return ..()
|
return ..()
|
||||||
|
|
||||||
/datum/vote/custom_vote/can_be_initiated(mob/by_who, forced = FALSE)
|
/datum/vote/custom_vote/can_be_initiated(forced)
|
||||||
. = ..()
|
. = ..()
|
||||||
if(!.)
|
if(. != VOTE_AVAILABLE)
|
||||||
return FALSE
|
return .
|
||||||
|
if(forced)
|
||||||
|
return .
|
||||||
|
|
||||||
// Custom votes can only be created if they're forced to be made.
|
// Custom votes can only be created if they're forced to be made.
|
||||||
// (Either an admin makes it, or otherwise.)
|
// (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)
|
/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(
|
var/custom_win_method = tgui_input_list(
|
||||||
vote_creator,
|
user = vote_creator,
|
||||||
"How should the vote winner be determined?",
|
message = "How should the vote winner be determined?",
|
||||||
"Winner Method",
|
title = "Winner Method",
|
||||||
list("Simple", "Weighted Random", "No Winner"),
|
items = list("Simple", "Weighted Random", "No Winner"),
|
||||||
default = "Simple",
|
default = "Simple",
|
||||||
)
|
)
|
||||||
switch(custom_win_method)
|
switch(custom_win_method)
|
||||||
@@ -43,7 +60,10 @@
|
|||||||
winner_method = VOTE_WINNER_METHOD_WEIGHTED_RANDOM
|
winner_method = VOTE_WINNER_METHOD_WEIGHTED_RANDOM
|
||||||
if("No Winner")
|
if("No Winner")
|
||||||
winner_method = VOTE_WINNER_METHOD_NONE
|
winner_method = VOTE_WINNER_METHOD_NONE
|
||||||
|
if(null)
|
||||||
|
return FALSE
|
||||||
else
|
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."))
|
to_chat(vote_creator, span_boldwarning("Unknown winner method. Contact a coder."))
|
||||||
return FALSE
|
return FALSE
|
||||||
|
|
||||||
@@ -54,7 +74,7 @@
|
|||||||
list("Yes", "No"),
|
list("Yes", "No"),
|
||||||
)
|
)
|
||||||
|
|
||||||
if(display_stats == null)
|
if(isnull(display_stats))
|
||||||
return FALSE
|
return FALSE
|
||||||
display_statistics = display_stats == "Yes"
|
display_statistics = display_stats == "Yes"
|
||||||
|
|
||||||
@@ -74,6 +94,9 @@
|
|||||||
|
|
||||||
if(!length(default_choices))
|
if(!length(default_choices))
|
||||||
return FALSE
|
return FALSE
|
||||||
|
// Sanity for all the tgui input stalling we are doing
|
||||||
|
if(isnull(vote_creator.client?.holder))
|
||||||
|
return FALSE
|
||||||
|
|
||||||
return ..()
|
return ..()
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/datum/vote/map_vote
|
/datum/vote/map_vote
|
||||||
name = "Map"
|
name = "Map"
|
||||||
message = "Vote for next round's map!"
|
default_message = "Vote for next round's map!"
|
||||||
count_method = VOTE_COUNT_METHOD_SINGLE
|
count_method = VOTE_COUNT_METHOD_SINGLE
|
||||||
winner_method = VOTE_WINNER_METHOD_WEIGHTED_RANDOM
|
winner_method = VOTE_WINNER_METHOD_WEIGHTED_RANDOM
|
||||||
display_statistics = FALSE
|
display_statistics = FALSE
|
||||||
@@ -30,44 +30,26 @@
|
|||||||
SSmapping.map_voted = TRUE // voted by not voting, very sad.
|
SSmapping.map_voted = TRUE // voted by not voting, very sad.
|
||||||
return FALSE
|
return FALSE
|
||||||
|
|
||||||
/datum/vote/map_vote/toggle_votable(mob/toggler)
|
/datum/vote/map_vote/toggle_votable()
|
||||||
if(!toggler)
|
|
||||||
CRASH("[type] wasn't passed a \"toggler\" mob to toggle_votable.")
|
|
||||||
if(!check_rights_for(toggler.client, R_ADMIN))
|
|
||||||
return FALSE
|
|
||||||
|
|
||||||
CONFIG_SET(flag/allow_vote_map, !CONFIG_GET(flag/allow_vote_map))
|
CONFIG_SET(flag/allow_vote_map, !CONFIG_GET(flag/allow_vote_map))
|
||||||
return TRUE
|
|
||||||
|
|
||||||
/datum/vote/map_vote/is_config_enabled()
|
/datum/vote/map_vote/is_config_enabled()
|
||||||
return CONFIG_GET(flag/allow_vote_map)
|
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(!.)
|
if(. != VOTE_AVAILABLE)
|
||||||
return FALSE
|
return .
|
||||||
|
|
||||||
if(forced)
|
if(forced)
|
||||||
return TRUE
|
return VOTE_AVAILABLE
|
||||||
|
|
||||||
var/number_of_choices = length(check_population())
|
var/number_of_choices = length(check_population())
|
||||||
if(number_of_choices < 2)
|
if(number_of_choices < 2)
|
||||||
message = "There [number_of_choices == 1 ? "is only one map" : "are no maps"] to choose from."
|
return "There [number_of_choices == 1 ? "is only one map" : "are no maps"] to choose from."
|
||||||
return FALSE
|
|
||||||
|
|
||||||
if(SSmapping.map_vote_rocked)
|
if(SSmapping.map_vote_rocked)
|
||||||
return TRUE
|
return VOTE_AVAILABLE
|
||||||
|
|
||||||
if(!CONFIG_GET(flag/allow_vote_map))
|
|
||||||
message = "Map voting is disabled by server configuration settings."
|
|
||||||
return FALSE
|
|
||||||
|
|
||||||
if(SSmapping.map_voted)
|
if(SSmapping.map_voted)
|
||||||
message = "The next map has already been selected."
|
return "The next map has already been selected."
|
||||||
return FALSE
|
return VOTE_AVAILABLE
|
||||||
|
|
||||||
message = initial(message)
|
|
||||||
return TRUE
|
|
||||||
|
|
||||||
/// Before we create a vote, remove all maps from our choices that are outside of our population range.
|
/// 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).
|
/// 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_RESTART,
|
||||||
CHOICE_CONTINUE,
|
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.
|
/// 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()
|
/datum/vote/restart_vote/proc/admins_present()
|
||||||
@@ -19,36 +20,24 @@
|
|||||||
|
|
||||||
return FALSE
|
return FALSE
|
||||||
|
|
||||||
/datum/vote/restart_vote/toggle_votable(mob/toggler)
|
/datum/vote/restart_vote/toggle_votable()
|
||||||
if(!toggler)
|
|
||||||
CRASH("[type] wasn't passed a \"toggler\" mob to toggle_votable.")
|
|
||||||
|
|
||||||
if(!check_rights_for(toggler.client, R_ADMIN))
|
|
||||||
return FALSE
|
|
||||||
|
|
||||||
CONFIG_SET(flag/allow_vote_restart, !CONFIG_GET(flag/allow_vote_restart))
|
CONFIG_SET(flag/allow_vote_restart, !CONFIG_GET(flag/allow_vote_restart))
|
||||||
return TRUE
|
|
||||||
|
|
||||||
/datum/vote/restart_vote/is_config_enabled()
|
/datum/vote/restart_vote/is_config_enabled()
|
||||||
return CONFIG_GET(flag/allow_vote_restart)
|
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(!.)
|
if(!.)
|
||||||
return FALSE
|
return
|
||||||
|
if(!admins_present())
|
||||||
|
return
|
||||||
|
async_alert_about_admins(vote_creator)
|
||||||
|
|
||||||
if(!forced && !CONFIG_GET(flag/allow_vote_restart))
|
/datum/vote/restart_vote/proc/async_alert_about_admins(mob/vote_creator)
|
||||||
message = "Restart voting is disabled by server configuration settings."
|
set waitfor = FALSE
|
||||||
return 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.")
|
||||||
// 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/get_vote_result(list/non_voters)
|
/datum/vote/restart_vote/get_vote_result(list/non_voters)
|
||||||
if(!CONFIG_GET(flag/default_no_vote))
|
if(!CONFIG_GET(flag/default_no_vote))
|
||||||
|
|||||||
@@ -10,58 +10,40 @@
|
|||||||
CHOICE_TO_ROCK,
|
CHOICE_TO_ROCK,
|
||||||
CHOICE_NOT_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.
|
/// The number of times we have rocked the vote thus far.
|
||||||
var/rocking_votes = 0
|
var/rocking_votes = 0
|
||||||
|
|
||||||
/datum/vote/rock_the_vote/toggle_votable(mob/toggler)
|
/datum/vote/rock_the_vote/toggle_votable()
|
||||||
if(!toggler)
|
|
||||||
CRASH("[type] wasn't passed a \"toggler\" mob to toggle_votable.")
|
|
||||||
if(!check_rights_for(toggler.client, R_ADMIN))
|
|
||||||
return FALSE
|
|
||||||
|
|
||||||
CONFIG_SET(flag/allow_rock_the_vote, !CONFIG_GET(flag/allow_rock_the_vote))
|
CONFIG_SET(flag/allow_rock_the_vote, !CONFIG_GET(flag/allow_rock_the_vote))
|
||||||
return TRUE
|
|
||||||
|
|
||||||
/datum/vote/rock_the_vote/is_config_enabled()
|
/datum/vote/rock_the_vote/is_config_enabled()
|
||||||
return CONFIG_GET(flag/allow_rock_the_vote)
|
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(. != VOTE_AVAILABLE)
|
||||||
if(!.)
|
return .
|
||||||
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(SSticker.current_state == GAME_STATE_FINISHED)
|
if(SSticker.current_state == GAME_STATE_FINISHED)
|
||||||
message = "The game is finished, no map votes can be initiated."
|
return "The game is finished, no map votes can be initiated."
|
||||||
return FALSE
|
|
||||||
|
|
||||||
if(rocking_votes >= CONFIG_GET(number/max_rocking_votes))
|
if(rocking_votes >= CONFIG_GET(number/max_rocking_votes))
|
||||||
message = "The maximum number of times to rock the vote has been reached."
|
return "The maximum number of times to rock the vote has been reached."
|
||||||
return FALSE
|
|
||||||
|
|
||||||
if(SSmapping.map_vote_rocked)
|
if(SSmapping.map_vote_rocked)
|
||||||
message = "The vote has already been rocked! Initiate a map vote!"
|
return "The vote has already been rocked! Initiate a map vote!"
|
||||||
return FALSE
|
|
||||||
|
|
||||||
if(!SSmapping.map_voted)
|
if(!SSmapping.map_voted)
|
||||||
message = "Rocking the vote is disabled because no map has been voted on yet!"
|
return "Rocking the vote is disabled because no map has been voted on yet!"
|
||||||
return FALSE
|
|
||||||
|
|
||||||
if(SSmapping.map_force_chosen)
|
if(SSmapping.map_force_chosen)
|
||||||
message = "Rocking the vote is disabled because an admin has forcibly set the map!"
|
return "Rocking the vote is disabled because an admin has forcibly set the map!"
|
||||||
return FALSE
|
|
||||||
|
|
||||||
if(EMERGENCY_ESCAPED_OR_ENDGAMED && SSmapping.map_voted)
|
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 "The emergency shuttle has already left the station and the next map has already been chosen!"
|
||||||
return FALSE
|
|
||||||
|
|
||||||
message = initial(message)
|
return VOTE_AVAILABLE
|
||||||
return TRUE
|
|
||||||
|
|
||||||
/datum/vote/rock_the_vote/finalize_vote(winning_option)
|
/datum/vote/rock_the_vote/finalize_vote(winning_option)
|
||||||
rocking_votes++
|
rocking_votes++
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
Box,
|
Box,
|
||||||
Button,
|
Button,
|
||||||
Collapsible,
|
Collapsible,
|
||||||
|
Dimmer,
|
||||||
Icon,
|
Icon,
|
||||||
LabeledList,
|
LabeledList,
|
||||||
NoticeBox,
|
NoticeBox,
|
||||||
@@ -59,11 +60,13 @@ type Data = {
|
|||||||
possibleVotes: Vote[];
|
possibleVotes: Vote[];
|
||||||
user: UserData;
|
user: UserData;
|
||||||
voting: string[];
|
voting: string[];
|
||||||
|
LastVoteTime: number;
|
||||||
|
VoteCD: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const VotePanel = (props) => {
|
export const VotePanel = (props) => {
|
||||||
const { data } = useBackend<Data>();
|
const { act, data } = useBackend<Data>();
|
||||||
const { currentVote, user } = data;
|
const { currentVote, user, LastVoteTime, VoteCD } = data;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds the voting type to title if there is an ongoing vote.
|
* 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 title={windowTitle} width={400} height={500}>
|
||||||
<Window.Content>
|
<Window.Content>
|
||||||
<Stack fill vertical>
|
<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 />
|
<VoteOptions />
|
||||||
{!!user.isLowerAdmin && currentVote && <VotersList />}
|
{!!user.isLowerAdmin && currentVote && <VotersList />}
|
||||||
</Section>
|
</Section>
|
||||||
@@ -93,51 +108,86 @@ 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.
|
* The create vote options menu. Only upper admins can disable voting.
|
||||||
* @returns A section visible to everyone with vote options.
|
* @returns A section visible to everyone with vote options.
|
||||||
*/
|
*/
|
||||||
const VoteOptions = (props) => {
|
const VoteOptions = (props) => {
|
||||||
const { act, data } = useBackend<Data>();
|
const { act, data } = useBackend<Data>();
|
||||||
const { possibleVotes, user } = data;
|
const { possibleVotes, user, LastVoteTime, VoteCD } = data;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack.Item>
|
<Stack.Item>
|
||||||
<Collapsible title="Start a Vote">
|
<Collapsible title="Start a Vote">
|
||||||
<Stack vertical justify="space-between">
|
<Section>
|
||||||
{possibleVotes.map((option) => (
|
{LastVoteTime + VoteCD > 0 && <VoteOptionDimmer />}
|
||||||
<Stack.Item key={option.name}>
|
<Stack vertical justify="space-between">
|
||||||
{!!user.isLowerAdmin && option.config !== VoteConfig.None && (
|
{possibleVotes.map((option) => (
|
||||||
<Button.Checkbox
|
<Stack.Item key={option.name}>
|
||||||
mr={option.config === VoteConfig.Disabled ? 1 : 1.6}
|
<Stack>
|
||||||
color="red"
|
{!!user.isLowerAdmin && (
|
||||||
checked={option.config === VoteConfig.Enabled}
|
<Stack.Item>
|
||||||
disabled={!user.isUpperAdmin}
|
<Button.Checkbox
|
||||||
content={
|
width={7}
|
||||||
option.config === VoteConfig.Enabled
|
color="red"
|
||||||
? 'Enabled'
|
checked={option.config === VoteConfig.Enabled}
|
||||||
: 'Disabled'
|
disabled={
|
||||||
}
|
!user.isUpperAdmin ||
|
||||||
onClick={() =>
|
option.config === VoteConfig.None
|
||||||
act('toggleVote', {
|
}
|
||||||
voteName: option.name,
|
tooltip={
|
||||||
})
|
option.config === VoteConfig.None
|
||||||
}
|
? 'This vote cannot be disabled.'
|
||||||
/>
|
: null
|
||||||
)}
|
}
|
||||||
<Button
|
content={
|
||||||
disabled={!option.canBeInitiated}
|
option.config === VoteConfig.Enabled
|
||||||
tooltip={option.message}
|
? 'Enabled'
|
||||||
content={option.name}
|
: 'Disabled'
|
||||||
onClick={() =>
|
}
|
||||||
act('callVote', {
|
onClick={() =>
|
||||||
voteName: option.name,
|
act('toggleVote', {
|
||||||
})
|
voteName: option.name,
|
||||||
}
|
})
|
||||||
/>
|
}
|
||||||
</Stack.Item>
|
/>
|
||||||
))}
|
</Stack.Item>
|
||||||
</Stack>
|
)}
|
||||||
|
<Stack.Item>
|
||||||
|
<Button
|
||||||
|
width={12}
|
||||||
|
textAlign={'center'}
|
||||||
|
disabled={!option.canBeInitiated}
|
||||||
|
tooltip={option.message}
|
||||||
|
content={option.name}
|
||||||
|
onClick={() =>
|
||||||
|
act('callVote', {
|
||||||
|
voteName: option.name,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Stack.Item>
|
||||||
|
</Stack>
|
||||||
|
</Stack.Item>
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
|
</Section>
|
||||||
</Collapsible>
|
</Collapsible>
|
||||||
</Stack.Item>
|
</Stack.Item>
|
||||||
);
|
);
|
||||||
@@ -153,11 +203,11 @@ const VotersList = (props) => {
|
|||||||
return (
|
return (
|
||||||
<Stack.Item>
|
<Stack.Item>
|
||||||
<Collapsible
|
<Collapsible
|
||||||
title={`View Voters${
|
title={`View Active Voters${
|
||||||
data.voting.length ? `: ${data.voting.length}` : ''
|
data.voting.length ? ` (${data.voting.length})` : ''
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<Section height={8} fill scrollable>
|
<Section height={4} fill scrollable>
|
||||||
{data.voting.map((voter) => {
|
{data.voting.map((voter) => {
|
||||||
return <Box key={voter}>{voter}</Box>;
|
return <Box key={voter}>{voter}</Box>;
|
||||||
})}
|
})}
|
||||||
@@ -275,13 +325,26 @@ const TimePanel = (props) => {
|
|||||||
{currentVote?.timeRemaining || 0}s
|
{currentVote?.timeRemaining || 0}s
|
||||||
</Box>
|
</Box>
|
||||||
{!!user.isLowerAdmin && (
|
{!!user.isLowerAdmin && (
|
||||||
<Button
|
<Stack>
|
||||||
color="red"
|
<Stack.Item>
|
||||||
disabled={!user.isLowerAdmin || !currentVote}
|
<Button
|
||||||
onClick={() => act('cancel')}
|
color="green"
|
||||||
>
|
disabled={!user.isLowerAdmin || !currentVote}
|
||||||
Cancel Vote
|
onClick={() => act('endNow')}
|
||||||
</Button>
|
>
|
||||||
|
End Now
|
||||||
|
</Button>
|
||||||
|
</Stack.Item>
|
||||||
|
<Stack.Item>
|
||||||
|
<Button
|
||||||
|
color="red"
|
||||||
|
disabled={!user.isLowerAdmin || !currentVote}
|
||||||
|
onClick={() => act('cancel')}
|
||||||
|
>
|
||||||
|
Cancel Vote
|
||||||
|
</Button>
|
||||||
|
</Stack.Item>
|
||||||
|
</Stack>
|
||||||
)}
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
</Section>
|
</Section>
|
||||||
|
|||||||
Reference in New Issue
Block a user