From 12be50f5d1e18f0baa1c549550ca33ee3d7e1b0c Mon Sep 17 00:00:00 2001 From: Mark Suckerberg Date: Wed, 24 Jan 2024 21:14:45 -0600 Subject: [PATCH] more stuff --- code/__DEFINES/maths.dm | 4 + code/__DEFINES/subsystems.dm | 7 ++ .../configuration/entries/general.dm | 4 +- code/controllers/subsystem/autotransfer.dm | 3 + code/controllers/subsystem/vote.dm | 6 +- code/datums/votes/_vote_datum.dm | 79 ++++++++++++++----- code/datums/votes/custom_vote.dm | 23 +++++- code/datums/votes/transfer_vote.dm | 4 +- code/game/world.dm | 4 - config/config.txt | 7 +- 10 files changed, 102 insertions(+), 39 deletions(-) diff --git a/code/__DEFINES/maths.dm b/code/__DEFINES/maths.dm index 77c32e0ff653..719f06f2a812 100644 --- a/code/__DEFINES/maths.dm +++ b/code/__DEFINES/maths.dm @@ -31,6 +31,10 @@ #define ROUND_UP(x) (-round(-(x))) +/// Returns the number of digits in a number. Only works on whole numbers. +/// This is marginally faster than string interpolation -> length +#define DIGITS(x) (ROUND_UP(log(10, x))) + // round() acts like floor(x, 1) by default but can't handle other values #define FLOOR(x, y) (round((x) / (y)) * (y)) diff --git a/code/__DEFINES/subsystems.dm b/code/__DEFINES/subsystems.dm index 59537e561f1e..edade85c5671 100644 --- a/code/__DEFINES/subsystems.dm +++ b/code/__DEFINES/subsystems.dm @@ -263,3 +263,10 @@ #define VOTE_COUNT_METHOD_SINGLE 1 /// Approval voting. Any number of selections per person, and the selection with the most votes wins. #define VOTE_COUNT_METHOD_MULTI 2 + +/// The choice with the most votes wins. Ties are broken by the first choice to reach that number of votes. +#define VOTE_WINNER_METHOD_SIMPLE "Simple" +/// The winning choice is selected randomly based on the number of votes each choice has. +#define VOTE_WINNER_METHOD_WEIGHTED_RANDOM "Weighted Random" +/// There is no winner for this vote. +#define VOTE_WINNER_METHOD_NONE "None" diff --git a/code/controllers/configuration/entries/general.dm b/code/controllers/configuration/entries/general.dm index 7fa437b14da2..a59d14cce4d3 100644 --- a/code/controllers/configuration/entries/general.dm +++ b/code/controllers/configuration/entries/general.dm @@ -102,9 +102,9 @@ /datum/config_entry/flag/allow_admin_asaycolor //Allows admins with relevant permissions to have a personalized asay color -/datum/config_entry/flag/allow_vote_restart // allow votes to restart +/datum/config_entry/flag/allow_vote_restart // allow player votes to restart -/datum/config_entry/flag/allow_vote_mode // allow votes to change mode +/datum/config_entry/flag/allow_vote_transfer // allow player votes to initiate a transfer /datum/config_entry/flag/auth_only // server can only be used for authentication diff --git a/code/controllers/subsystem/autotransfer.dm b/code/controllers/subsystem/autotransfer.dm index 07e3eccce3ff..2b1259ef4f5f 100644 --- a/code/controllers/subsystem/autotransfer.dm +++ b/code/controllers/subsystem/autotransfer.dm @@ -11,6 +11,9 @@ SUBSYSTEM_DEF(autotransfer) /datum/controller/subsystem/autotransfer/fire() if(COOLDOWN_FINISHED(src, next_vote)) + //Delay the vote if there's already a vote in progress + if(SSvote.current_vote) + COOLDOWN_START(src, next_vote, SSvote.current_vote.time_remaining + 10 SECONDS) SSvote.initiate_vote(/datum/vote/transfer_vote, "The Server", forced = TRUE) COOLDOWN_START(src, next_vote, CONFIG_GET(number/vote_autotransfer_interval)) diff --git a/code/controllers/subsystem/vote.dm b/code/controllers/subsystem/vote.dm index 0d142861e1a2..d1d1edceb7e7 100644 --- a/code/controllers/subsystem/vote.dm +++ b/code/controllers/subsystem/vote.dm @@ -85,11 +85,13 @@ SUBSYSTEM_DEF(vote) // Announce the results of the vote to the world. var/to_display = current_vote.get_result_text(winners, final_winner, non_voters) - log_vote(to_display) + var/log_string = replacetext(to_display, "\n", "\\n") // 'keep' the newlines, but dont actually print them as newlines + log_vote(log_string) to_chat(world, span_infoplain(vote_font("\n[to_display]"))) // Finally, doing any effects on vote completion - current_vote.finalize_vote(final_winner) + if (final_winner) // if no one voted, or the vote cannot be won, final_winner will be null + current_vote.finalize_vote(final_winner) /** * One selection per person, and the selection with the most votes wins. diff --git a/code/datums/votes/_vote_datum.dm b/code/datums/votes/_vote_datum.dm index 72b58e25fce4..cc35d3d2f841 100644 --- a/code/datums/votes/_vote_datum.dm +++ b/code/datums/votes/_vote_datum.dm @@ -28,6 +28,8 @@ var/time_remaining /// 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 /** * Used to determine if this vote is a possible @@ -121,33 +123,41 @@ */ /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() + + stack_trace("invalid select winner method: [winner_method]. Defaulting to simple.") + return get_simple_winner() - var/list/winners = list() +/// 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 we currently have no winners... - if(!length(winners)) - // And the current option has any votes, it's the new highest. - if(vote_count > 0) - winners += option - highest_vote = vote_count + if(vote_count < highest_vote) continue - // If we're greater than, and NOT equal to, the highest vote, - // we are the new supreme winner - clear all others if(vote_count > highest_vote) - winners.Cut() - winners += option highest_vote = vote_count + current_winners = list(option) + continue + current_winners += option - // If we're equal to the highest vote, we tie for winner - else if(vote_count == highest_vote) - winners += option + return length(current_winners) ? current_winners : list() - return winners +/// 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 = pickweight(choices) + return winner ? list(winner) : list() /** * Gets the resulting text displayed when the vote is completed. @@ -159,17 +169,46 @@ * 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) - if(length(all_winners) <= 0 || !real_winner) - return span_bold("Vote Result: Inconclusive - No Votes!") - var/returned_text = "" if(override_question) returned_text += span_bold(override_question) else returned_text += span_bold("[capitalize(name)] Vote") + returned_text += "\nWinner Selection: " + switch(winner_method) + if(VOTE_WINNER_METHOD_NONE) + returned_text += "None" + if(VOTE_WINNER_METHOD_WEIGHTED_RANDOM) + returned_text += "Weighted Random" + 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!") + + returned_text += "\nResults:" for(var/option in choices) - returned_text += "\n[span_bold(option)]: [choices[option]]" + 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) diff --git a/code/datums/votes/custom_vote.dm b/code/datums/votes/custom_vote.dm index b610724c4aa8..4dbc984759c8 100644 --- a/code/datums/votes/custom_vote.dm +++ b/code/datums/votes/custom_vote.dm @@ -29,6 +29,25 @@ return forced /datum/vote/custom_vote/create_vote(mob/vote_creator) + var/custom_win_method = tgui_input_list( + vote_creator, + "How should the vote winner be determined?", + "Winner Method", + list("Simple", "Weighted Random", "No Winner"), + ) + if(!custom_win_method) + custom_win_method = "Simple" + switch(custom_win_method) + if("Simple") + winner_method = VOTE_WINNER_METHOD_SIMPLE + if("Weighted Random") + winner_method = VOTE_WINNER_METHOD_WEIGHTED_RANDOM + if("No Winner") + winner_method = VOTE_WINNER_METHOD_NONE + else + to_chat(vote_creator, span_boldwarning("Unknown winner method. Contact a coder.")) + return FALSE + override_question = input(vote_creator, "What is the vote for?", "Custom Vote") as text|null if(!override_question) return FALSE @@ -53,8 +72,4 @@ . = ..() . += "\n[override_question]" -// There are no winners or losers for custom votes -/datum/vote/custom_vote/get_winner_text(list/all_winners, real_winner, list/non_voters) - return "[span_bold("Did not vote:")] [length(non_voters)]" - #undef MAX_CUSTOM_VOTE_OPTIONS diff --git a/code/datums/votes/transfer_vote.dm b/code/datums/votes/transfer_vote.dm index a3a95649be04..aff2fec0d388 100644 --- a/code/datums/votes/transfer_vote.dm +++ b/code/datums/votes/transfer_vote.dm @@ -45,9 +45,9 @@ if(2 to 3) factor = 1 if(3 to 4) - factor = 1.2 + factor = 1.5 else - factor = 1.4 + factor = 2 choices[CHOICE_TRANSFER] += round(length(non_voters) * factor) return ..() diff --git a/code/game/world.dm b/code/game/world.dm index a9881d19d977..aae1ea9d6307 100644 --- a/code/game/world.dm +++ b/code/game/world.dm @@ -304,10 +304,6 @@ GLOBAL_VAR(restart_counter) if (server_name) s += "[server_name] — " features += "[CONFIG_GET(flag/norespawn) ? "no " : ""]respawn" - if(CONFIG_GET(flag/allow_vote_mode)) - features += "vote" - if(CONFIG_GET(flag/allow_ai)) - features += "AI allowed" hostedby = CONFIG_GET(string/hostedby) var/discord_url diff --git a/config/config.txt b/config/config.txt index 7a579123028b..7a348b2c9cc0 100644 --- a/config/config.txt +++ b/config/config.txt @@ -186,11 +186,8 @@ ID_CONSOLE_JOBSLOT_DELAY 30 ## allow players to initiate a restart vote #ALLOW_VOTE_RESTART -## allow players to initiate a mode-change vote -#ALLOW_VOTE_MODE - -## allow players to initiate a map-change vote -#ALLOW_VOTE_MAP +## allow players to initiate a transfer vote +#ALLOW_VOTE_TRANSFER ## min delay (deciseconds) between voting sessions (default 10 minutes) VOTE_DELAY 6000