mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2025-12-11 18:22:14 +00:00
## About The Pull Request Implements ranked choice voting for storyteller and map vote ## Why It's Good For The Game I can close the secret storyteller PR ## Changelog 🆑 LT3 qol: Ranked choice voting is now available for storyteller and map votes /🆑
260 lines
7.9 KiB
Plaintext
260 lines
7.9 KiB
Plaintext
|
|
/**
|
|
* # Vote Singleton
|
|
*
|
|
* A singleton datum that represents a type of vote for the voting subsystem.
|
|
*/
|
|
/datum/vote
|
|
/// The name of the vote.
|
|
var/name
|
|
/// If supplied, an override question will be displayed instead of the name of the vote.
|
|
var/override_question
|
|
/// The sound effect played to everyone when this vote is initiated.
|
|
var/vote_sound = 'sound/misc/bloop.ogg'
|
|
/// A list of default choices we have for this vote.
|
|
var/list/default_choices
|
|
/// Does the name of this vote contain the word "vote"?
|
|
var/contains_vote_in_name = FALSE
|
|
/// 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.
|
|
var/winner_method = VOTE_WINNER_METHOD_SIMPLE
|
|
/// 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.
|
|
*
|
|
* If FALSE is returned, this vote singleton
|
|
* will not be created when the vote subsystem initializes,
|
|
* meaning no one will be able to hold this vote.
|
|
*/
|
|
/datum/vote/proc/is_accessible_vote()
|
|
return !!length(default_choices)
|
|
|
|
/**
|
|
* Resets our vote to its default state.
|
|
*/
|
|
/datum/vote/proc/reset()
|
|
SHOULD_CALL_PARENT(TRUE)
|
|
|
|
choices.Cut()
|
|
choices_by_ckey.Cut()
|
|
started_time = null
|
|
time_remaining = -1
|
|
|
|
/**
|
|
* If this vote has a config associated, toggles it between enabled and disabled.
|
|
*/
|
|
/datum/vote/proc/toggle_votable()
|
|
return
|
|
|
|
/**
|
|
* If this vote has a config associated, returns its value (True or False, usually).
|
|
* If it has no config, returns -1.
|
|
*/
|
|
/datum/vote/proc/is_config_enabled()
|
|
return -1
|
|
|
|
/**
|
|
* Checks if the passed mob can initiate this 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(forced = FALSE)
|
|
SHOULD_CALL_PARENT(TRUE)
|
|
|
|
if(!forced && !is_config_enabled())
|
|
return "This vote is currently disabled by the server configuration."
|
|
|
|
return VOTE_AVAILABLE
|
|
|
|
/**
|
|
* Called prior to the vote being initiated.
|
|
*
|
|
* Return FALSE to prevent the vote from being initiated.
|
|
*/
|
|
/datum/vote/proc/create_vote(mob/vote_creator)
|
|
SHOULD_CALL_PARENT(TRUE)
|
|
|
|
for(var/key in default_choices)
|
|
choices[key] = 0
|
|
|
|
return TRUE
|
|
|
|
/**
|
|
* Called when this vote is actually initiated.
|
|
*
|
|
* Return a string - the text displayed to the world when the vote is initiated.
|
|
*/
|
|
/datum/vote/proc/initiate_vote(initiator, duration)
|
|
SHOULD_CALL_PARENT(TRUE)
|
|
|
|
started_time = world.time
|
|
time_remaining = round(duration / 10)
|
|
|
|
return "[contains_vote_in_name ? "[capitalize(name)]" : "[capitalize(name)] vote"] started by [initiator || "Central Command"]."
|
|
|
|
/**
|
|
* Gets the result of the vote.
|
|
*
|
|
* non_voters - a list of all ckeys who didn't vote in the vote.
|
|
*
|
|
* Returns a list of all options that won.
|
|
* If there were no votes at all, the list will be length = 0, non-null.
|
|
* If only one option one, the list will be length = 1.
|
|
* If there was a tie, the list will be length > 1.
|
|
*/
|
|
/datum/vote/proc/get_vote_result(list/non_voters)
|
|
RETURN_TYPE(/list)
|
|
SHOULD_CALL_PARENT(TRUE)
|
|
|
|
switch(winner_method)
|
|
if(VOTE_WINNER_METHOD_NONE)
|
|
return list()
|
|
if(VOTE_WINNER_METHOD_SIMPLE)
|
|
return get_simple_winner()
|
|
if(VOTE_WINNER_METHOD_WEIGHTED_RANDOM)
|
|
return get_random_winner()
|
|
// BUBBER EDIT ADDITION BEGIN - RANKED CHOICE VOTING
|
|
if(VOTE_WINNER_METHOD_RANKED)
|
|
return get_ranked_winner()
|
|
// BUBBER EDIT ADDITION END
|
|
|
|
stack_trace("invalid select winner method: [winner_method]. Defaulting to simple.")
|
|
return get_simple_winner()
|
|
|
|
/// Gets the winner of the vote, selecting the choice with the most votes.
|
|
/datum/vote/proc/get_simple_winner()
|
|
var/highest_vote = 0
|
|
var/list/current_winners = list()
|
|
|
|
for(var/option in choices)
|
|
var/vote_count = choices[option]
|
|
if(vote_count < highest_vote)
|
|
continue
|
|
|
|
if(vote_count > highest_vote)
|
|
highest_vote = vote_count
|
|
current_winners = list(option)
|
|
continue
|
|
current_winners += option
|
|
|
|
return length(current_winners) ? current_winners : list()
|
|
|
|
/// Gets the winner of the vote, selecting a random choice from all choices based on their vote count.
|
|
/datum/vote/proc/get_random_winner()
|
|
var/winner = pick_weight(choices)
|
|
return winner ? list(winner) : list()
|
|
|
|
/**
|
|
* Gets the resulting text displayed when the vote is completed.
|
|
*
|
|
* all_winners - list of all options that won. Can be multiple, in the event of ties.
|
|
* real_winner - the option that actually won.
|
|
* non_voters - a list of all ckeys who didn't vote in the vote.
|
|
*
|
|
* Return a formatted string of text to be displayed to everyone.
|
|
*/
|
|
/datum/vote/proc/get_result_text(list/all_winners, real_winner, list/non_voters)
|
|
var/title_text = ""
|
|
var/returned_text = ""
|
|
if(override_question)
|
|
title_text += span_bold(override_question)
|
|
else
|
|
title_text += span_bold("[capitalize(name)] Vote")
|
|
|
|
returned_text += "Winner Selection: "
|
|
switch(winner_method)
|
|
if(VOTE_WINNER_METHOD_NONE)
|
|
returned_text += "None"
|
|
if(VOTE_WINNER_METHOD_WEIGHTED_RANDOM)
|
|
returned_text += "Weighted Random"
|
|
// BUBBER EDIT ADDITION BEGIN - RANKED CHOICE VOTING
|
|
if(VOTE_WINNER_METHOD_RANKED)
|
|
returned_text += "Ranked"
|
|
// BUBBER EDIT ADDITION END
|
|
else
|
|
returned_text += "Simple"
|
|
|
|
var/total_votes = 0 // for determining percentage of votes
|
|
for(var/option in choices)
|
|
total_votes += choices[option]
|
|
|
|
if(total_votes <= 0)
|
|
return span_bold("Vote Result: Inconclusive - No Votes!")
|
|
|
|
if (display_statistics)
|
|
returned_text += "\nResults:"
|
|
for(var/option in choices)
|
|
returned_text += "\n"
|
|
var/votes = choices[option]
|
|
var/percentage_text = ""
|
|
if(votes > 0)
|
|
var/actual_percentage = round((votes / total_votes) * 100, 0.1)
|
|
var/text = "[actual_percentage]"
|
|
var/spaces_needed = 5 - length(text)
|
|
for(var/_ in 1 to spaces_needed)
|
|
returned_text += " "
|
|
percentage_text += "[text]%"
|
|
else
|
|
percentage_text = " 0%"
|
|
returned_text += "[percentage_text] | [span_bold(option)]: [choices[option]]"
|
|
|
|
if(!real_winner) // vote has no winner or cannot be won, but still had votes
|
|
return returned_text
|
|
|
|
returned_text += "\n"
|
|
returned_text += get_winner_text(all_winners, real_winner, non_voters)
|
|
|
|
return fieldset_block(title_text, returned_text, "boxed_message purple_box")
|
|
|
|
/**
|
|
* Gets the text that displays the winning options within the result text.
|
|
*
|
|
* all_winners - list of all options that won. Can be multiple, in the event of ties.
|
|
* real_winner - the option that actually won.
|
|
* non_voters - a list of all ckeys who didn't vote in the vote.
|
|
*
|
|
* Return a formatted string of text to be displayed to everyone.
|
|
*/
|
|
/datum/vote/proc/get_winner_text(list/all_winners, real_winner, list/non_voters)
|
|
var/returned_text = ""
|
|
if(length(all_winners) > 1)
|
|
returned_text += "\n[span_bold("Vote Tied Between:")]"
|
|
for(var/a_winner in all_winners)
|
|
returned_text += "\n\t[a_winner]"
|
|
|
|
returned_text += span_bold("\nVote Result: [real_winner]")
|
|
return returned_text
|
|
|
|
/**
|
|
* How this vote handles a tiebreaker between multiple winners.
|
|
*/
|
|
/datum/vote/proc/tiebreaker(list/winners)
|
|
return pick(winners)
|
|
|
|
/**
|
|
* Called when a vote is actually all said and done.
|
|
* Apply actual vote effects here.
|
|
*/
|
|
/datum/vote/proc/finalize_vote(winning_option)
|
|
return
|