Skip to content

Commit

Permalink
[MIRROR] Votes now show the percentage of total votes each choice got…
Browse files Browse the repository at this point in the history
… | Adds weighted random vote type | Map votes are now weighted random [MDB IGNORE] (#25702)

* Votes now show the percentage of total votes each choice got | Adds weighted random vote type | Map votes are now weighted random (#80362)

## About The Pull Request

See the title.
## Why It's Good For The Game

Mothblocks wanted map votes to not be simple winner take all and wanted
it to be randomly chosen based on votes.

![image](https://github.com/tgstation/tgstation/assets/12817816/7435b444-dcd7-4aa5-a747-4bafe434ebdb)

## Changelog
:cl:
add: Map Votes are now weighted random.
add: Custom Votes can now take advantage of Weighted Random winner
selection
del: Removed Herobrine from the game
/:cl:

---------

Co-authored-by: Mothblocks <35135081+Mothblocks@ users.noreply.github.com>

* Votes now show the percentage of total votes each choice got | Adds weighted random vote type | Map votes are now weighted random

---------

Co-authored-by: Zephyr <[email protected]>
Co-authored-by: Mothblocks <35135081+Mothblocks@ users.noreply.github.com>
  • Loading branch information
3 people authored and FFMirrorBot committed Dec 18, 2023
1 parent 241becc commit b7cca4c
Show file tree
Hide file tree
Showing 6 changed files with 111 additions and 26 deletions.
4 changes: 4 additions & 0 deletions code/__DEFINES/maths.dm
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,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) )

Expand Down
7 changes: 7 additions & 0 deletions code/__DEFINES/subsystems.dm
Original file line number Diff line number Diff line change
Expand Up @@ -339,3 +339,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"
24 changes: 22 additions & 2 deletions code/controllers/subsystem/vote.dm
Original file line number Diff line number Diff line change
Expand Up @@ -86,11 +86,31 @@ 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/total_votes = 0
var/list/vote_choice_data = list()
for(var/choice in current_vote.choices)
var/choice_votes = current_vote.choices[choice]
vote_choice_data["[choice]"] = choice_votes

// stringify the winners to prevent potential unimplemented serialization errors.
// Perhaps this can be removed in the future and we assert that vote choices must implement serialization.
var/final_winner_string = final_winner && "[final_winner]"
var/list/winners_string = list()
for(var/winner in winners)
winners_string += "[winner]"

var/list/vote_log_data = list(
"choices" = vote_choice_data,
"total" = total_votes,
"winners" = winners_string,
"final_winner" = final_winner_string,
)
var/log_string = replacetext(to_display, "\n", "\\n") // 'keep' the newlines, but dont actually print them as newlines
log_vote(log_string, vote_log_data)
to_chat(world, span_infoplain(vote_font("\n[to_display]")))

// Finally, doing any effects on vote completion
if (final_winner) // if no one voted final_winner will be null
if (final_winner) // if no one voted, or the vote cannot be won, final_winner will be null
current_vote.finalize_vote(final_winner)

/**
Expand Down
79 changes: 59 additions & 20 deletions code/datums/votes/_vote_datum.dm
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,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
Expand Down Expand Up @@ -123,33 +125,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 = pick_weight(choices)
return winner ? list(winner) : list()

/**
* Gets the resulting text displayed when the vote is completed.
Expand All @@ -161,17 +171,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)
Expand Down
22 changes: 18 additions & 4 deletions code/datums/votes/custom_vote.dm
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,24 @@
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"),
default = "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 = tgui_input_text(vote_creator, "What is the vote for?", "Custom Vote")
if(!override_question)
return FALSE
Expand All @@ -52,8 +70,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
1 change: 1 addition & 0 deletions code/datums/votes/map_vote.dm
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
name = "Map"
message = "Vote for next round's map!"
count_method = VOTE_COUNT_METHOD_MULTI
winner_method = VOTE_WINNER_METHOD_WEIGHTED_RANDOM

/datum/vote/map_vote/New()
. = ..()
Expand Down

0 comments on commit b7cca4c

Please sign in to comment.