mirror of
https://github.com/ParadiseSS13/Paradise.git
synced 2026-07-18 18:44:48 +01:00
Refactors vote code (#18403)
* Vote refactor * Tweaks * Review tweaks * Tweak
This commit is contained in:
@@ -68,7 +68,8 @@ GLOBAL_LIST_INIT(admin_verbs_admin, list(
|
||||
/client/proc/toggle_advanced_interaction, /*toggle admin ability to interact with not only machines, but also atoms such as buttons and doors*/
|
||||
/client/proc/list_ssds_afks,
|
||||
/client/proc/ccbdb_lookup_ckey,
|
||||
/client/proc/view_instances
|
||||
/client/proc/view_instances,
|
||||
/client/proc/start_vote
|
||||
))
|
||||
GLOBAL_LIST_INIT(admin_verbs_ban, list(
|
||||
/client/proc/ban_panel,
|
||||
|
||||
@@ -0,0 +1,199 @@
|
||||
#define VOTE_RESULT_TYPE_MAJORITY "Majority"
|
||||
|
||||
/datum/vote
|
||||
/// Person who started the vote
|
||||
var/initiator = "the server"
|
||||
/// world.time the vote started at
|
||||
var/started_time
|
||||
/// The question being asked
|
||||
var/question
|
||||
/// Vote type text, for showing in UIs and stuff
|
||||
var/vote_type_text = "unset"
|
||||
/// Do we want to show the vote counts as it goes
|
||||
var/show_counts = FALSE
|
||||
/// Vote result type. This determines how a winner is picked
|
||||
var/vote_result_type = VOTE_RESULT_TYPE_MAJORITY
|
||||
/// Was this vote custom started?
|
||||
var/is_custom = FALSE
|
||||
/// Choices available in the vote
|
||||
var/list/choices = list()
|
||||
// Assoc list of [ckeys => choice] who have voted. We dont want to hold client refs.
|
||||
var/list/voted = list()
|
||||
|
||||
|
||||
/datum/vote/New(_initiator, _question, list/_choices, _is_custom = FALSE)
|
||||
if(SSvote.active_vote)
|
||||
CRASH("Attempted to start another vote with one already in progress!")
|
||||
|
||||
if(_initiator)
|
||||
initiator = _initiator
|
||||
if(_question)
|
||||
question = _question
|
||||
if(_choices)
|
||||
choices = _choices
|
||||
|
||||
is_custom = _is_custom
|
||||
|
||||
// If we have no choices, dynamically generate them
|
||||
if(!length(choices))
|
||||
generate_choices()
|
||||
|
||||
/datum/vote/proc/start()
|
||||
var/text = "[capitalize(vote_type_text)] vote started by [initiator]."
|
||||
if(is_custom)
|
||||
vote_type_text = "custom"
|
||||
text += "\n[question]"
|
||||
if(usr)
|
||||
log_admin("[capitalize(vote_type_text)] ([question]) vote started by [key_name(usr)].")
|
||||
|
||||
else if(usr)
|
||||
log_admin("[capitalize(vote_type_text)] vote started by [key_name(usr)].")
|
||||
|
||||
log_vote(text)
|
||||
started_time = world.time
|
||||
announce(text)
|
||||
|
||||
/datum/vote/proc/remaining()
|
||||
return max(((started_time + GLOB.configuration.vote.vote_time) - world.time), 0)
|
||||
|
||||
|
||||
// Returns the result
|
||||
/datum/vote/proc/calculate_result()
|
||||
switch(vote_result_type)
|
||||
if(VOTE_RESULT_TYPE_MAJORITY)
|
||||
if(!length(voted))
|
||||
to_chat(world, "<span class='interface'>No votes were cast. Do you all hate democracy?!</span>") // shame them
|
||||
return null
|
||||
|
||||
var/list/results = list()
|
||||
|
||||
// Count up all votes
|
||||
for(var/ck in voted)
|
||||
if(voted[ck] in results)
|
||||
results[voted[ck]]++
|
||||
else
|
||||
results[voted[ck]] = 1
|
||||
|
||||
// Get the biggest vote count, since we can also use this to pick tiebreaks
|
||||
var/maxvotes = 0
|
||||
for(var/res in results)
|
||||
maxvotes = max(results[res], maxvotes)
|
||||
|
||||
var/list/winning_options = list()
|
||||
|
||||
for(var/res in results)
|
||||
if(results[res] == maxvotes)
|
||||
winning_options |= res
|
||||
|
||||
// Print all results
|
||||
for(var/res in results)
|
||||
if(res in winning_options)
|
||||
// Make it stand out
|
||||
to_chat(world, "<span class='info'><code>[res]</code> - [results[res]] vote\s</span>")
|
||||
else
|
||||
// Make it normal
|
||||
to_chat(world, "<span class='interface'><code>[res]</code> - [results[res]] vote\s</span>")
|
||||
|
||||
// And log it to the DB
|
||||
if(!is_custom)
|
||||
SSblackbox.record_feedback("nested tally", "votes", results[res], list(vote_type_text, res), ignore_seal = TRUE)
|
||||
|
||||
if(length(winning_options) > 1)
|
||||
var/random_dictator = pick(winning_options)
|
||||
to_chat(world, "<span class='interface'><b>Its a tie between [english_list(winning_options)]. Picking <code>[random_dictator]</code> at random.</b></span>") // shame them
|
||||
return random_dictator
|
||||
|
||||
// If we got here there must only be one thing in the list
|
||||
var/res = winning_options[1]
|
||||
|
||||
if(res in choices)
|
||||
to_chat(world, "<span class='interface'><b><code>[res]</code> won the vote.</b></span>")
|
||||
return res
|
||||
|
||||
to_chat(world, "<span class='interface'>The winner of the vote ([sanitize(res)]) isnt a valid choice? What the heck?</span>")
|
||||
stack_trace("Vote of type [type] concluded with an invalid answer. Answer was [sanitize(res)], choices were [json_encode(choices)]")
|
||||
return null
|
||||
|
||||
|
||||
|
||||
/datum/vote/proc/announce(start_text)
|
||||
to_chat(world, {"<font color='purple'><b>[start_text]</b>
|
||||
<a href='?src=[SSvote.UID()];vote=open'>Click here or type <code>Vote</code> to place your vote.</a>
|
||||
You have [GLOB.configuration.vote.vote_time / 10] seconds to vote.</font>"})
|
||||
SEND_SOUND(world, sound('sound/ambience/alarm4.ogg'))
|
||||
|
||||
|
||||
/datum/vote/proc/tick()
|
||||
if(remaining() == 0)
|
||||
// Announce result
|
||||
var/result = calculate_result()
|
||||
handle_result(result)
|
||||
qdel(src)
|
||||
|
||||
|
||||
/datum/vote/Destroy(force)
|
||||
// Should always be true but ehhhhhhh
|
||||
if(SSvote.active_vote == src)
|
||||
SSvote.active_vote = null
|
||||
return ..()
|
||||
|
||||
|
||||
// Override on children
|
||||
/datum/vote/proc/handle_result(result)
|
||||
return
|
||||
|
||||
/datum/vote/proc/generate_choices()
|
||||
return
|
||||
|
||||
|
||||
/*
|
||||
UI STUFFS
|
||||
*/
|
||||
/datum/vote/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.always_state)
|
||||
ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
|
||||
if(!ui)
|
||||
ui = new(user, src, ui_key, "VotePanel", "VotePanel", 400, 500, master_ui, state)
|
||||
ui.open()
|
||||
|
||||
/datum/vote/ui_data(mob/user)
|
||||
var/list/data = list()
|
||||
data["remaining"] = remaining()
|
||||
data["user_vote"] = null
|
||||
if(user.ckey in voted)
|
||||
data["user_vote"] = voted[user.ckey]
|
||||
|
||||
data["question"] = question
|
||||
data["choices"] = choices
|
||||
|
||||
// Admins see counts anyway
|
||||
if(show_counts || check_rights(R_ADMIN, FALSE, user))
|
||||
data["show_counts"] = TRUE
|
||||
|
||||
// Show counts
|
||||
var/list/counts = list()
|
||||
for(var/ck in voted)
|
||||
if(voted[ck] in counts)
|
||||
counts[voted[ck]]++
|
||||
else
|
||||
counts[voted[ck]] = 1
|
||||
|
||||
data["counts"] = counts
|
||||
else
|
||||
data["show_counts"] = FALSE
|
||||
data["counts"] = list() // No TGUI exploiting for you
|
||||
|
||||
return data
|
||||
|
||||
/datum/vote/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
|
||||
if(..())
|
||||
return
|
||||
|
||||
. = TRUE
|
||||
|
||||
switch(action)
|
||||
if("vote")
|
||||
if(params["target"] in choices)
|
||||
voted[usr.ckey] = params["target"]
|
||||
else
|
||||
message_admins("<span class='boldannounce'>\[EXPLOIT]</span> User [key_name_admin(usr)] spoofed a vote in the vote panel!")
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
// Crew transfer vote
|
||||
/datum/vote/crew_transfer
|
||||
question = "End the shift"
|
||||
choices = list("Initiate Crew Transfer", "Continue The Round")
|
||||
vote_type_text = "crew transfer"
|
||||
|
||||
/datum/vote/crew_transfer/New()
|
||||
if(SSticker.current_state < GAME_STATE_PLAYING)
|
||||
CRASH("Attempted to call a shuttle vote before the game starts!")
|
||||
..()
|
||||
|
||||
/datum/vote/crew_transfer/handle_result(result)
|
||||
if(result == "Initiate Crew Transfer")
|
||||
init_shift_change(null, TRUE)
|
||||
|
||||
// Map vote
|
||||
/datum/vote/map
|
||||
question = "Map Vote"
|
||||
vote_type_text = "map"
|
||||
|
||||
/datum/vote/map/generate_choices()
|
||||
for(var/x in subtypesof(/datum/map))
|
||||
var/datum/map/M = x
|
||||
if(initial(M.voteable))
|
||||
choices.Add("[initial(M.fluff_name)] ([initial(M.technical_name)])")
|
||||
|
||||
/datum/vote/map/announce()
|
||||
..()
|
||||
for(var/mob/M in GLOB.player_list)
|
||||
M.throw_alert("Map Vote", /obj/screen/alert/notify_mapvote, timeout_override = GLOB.configuration.vote.vote_time)
|
||||
|
||||
/datum/vote/map/handle_result(result)
|
||||
// Find target map.
|
||||
var/datum/map/top_voted_map
|
||||
for(var/x in subtypesof(/datum/map))
|
||||
var/datum/map/M = x
|
||||
if(initial(M.voteable))
|
||||
// Set top voted map
|
||||
if(result == "[initial(M.fluff_name)] ([initial(M.technical_name)])")
|
||||
top_voted_map = M
|
||||
to_chat(world, "<span class='interface'>Map for next round: [initial(top_voted_map.fluff_name)] ([initial(top_voted_map.technical_name)])</span>")
|
||||
SSmapping.next_map = new top_voted_map
|
||||
@@ -0,0 +1,61 @@
|
||||
/client/verb/vote()
|
||||
set category = "OOC"
|
||||
set name = "Vote"
|
||||
|
||||
if(SSvote.active_vote)
|
||||
SSvote.active_vote.ui_interact(usr)
|
||||
else
|
||||
to_chat(usr, "There is no active vote")
|
||||
|
||||
/client/proc/start_vote()
|
||||
set category = "Admin"
|
||||
set name = "Start Vote"
|
||||
set desc = "Start a vote on the server"
|
||||
|
||||
if(!check_rights(R_ADMIN))
|
||||
return
|
||||
|
||||
if(SSvote.active_vote)
|
||||
to_chat(usr, "A vote is already in progress")
|
||||
return
|
||||
|
||||
// Ask admins which type of vote they want to start
|
||||
var/vote_types = subtypesof(/datum/vote)
|
||||
vote_types |= "\[CUSTOM]"
|
||||
|
||||
// This needs to be a map to instance it properly. I do hate it as well, dont worry.
|
||||
var/list/votemap = list()
|
||||
for(var/vtype in vote_types)
|
||||
votemap["[vtype]"] = vtype
|
||||
|
||||
var/choice = input(usr, "Select a vote type", "Vote") as null|anything in vote_types
|
||||
|
||||
if(choice == null)
|
||||
return
|
||||
|
||||
if(choice != "\[CUSTOM]")
|
||||
// Not custom, figure it out
|
||||
var/datum/vote/votetype = votemap["[choice]"]
|
||||
SSvote.start_vote(new votetype(usr.ckey))
|
||||
return
|
||||
|
||||
// Its custom, lets ask
|
||||
var/question = html_encode(input(usr, "What is the vote for?") as text|null)
|
||||
if(!question)
|
||||
return
|
||||
|
||||
var/list/choices = list()
|
||||
for(var/i in 1 to 10)
|
||||
var/option = capitalize(html_encode(input(usr, "Please enter an option or hit cancel to finish") as text|null))
|
||||
if(!option || !usr.client)
|
||||
break
|
||||
choices |= option
|
||||
|
||||
var/c2 = alert(usr, "Show counts while vote is happening?", "Counts", "Yes", "No")
|
||||
var/c3 = input(usr, "Select a result calculation type", "Vote", VOTE_RESULT_TYPE_MAJORITY) as anything in list(VOTE_RESULT_TYPE_MAJORITY)
|
||||
|
||||
var/datum/vote/V = new /datum/vote(usr.ckey, question, choices, TRUE)
|
||||
V.show_counts = (c2 == "Yes")
|
||||
V.vote_result_type = c3
|
||||
SSvote.start_vote(V)
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
status_info["mode"] = GLOB.master_mode
|
||||
status_info["respawn"] = GLOB.configuration.general.respawn_enabled
|
||||
status_info["enter"] = GLOB.enter_allowed
|
||||
status_info["vote"] = GLOB.configuration.vote.allow_mode_votes
|
||||
status_info["ai"] = GLOB.configuration.jobs.allow_ai
|
||||
status_info["host"] = world.host ? world.host : null
|
||||
status_info["players"] = list()
|
||||
@@ -32,7 +31,7 @@
|
||||
status_info["admins"] = admin_count
|
||||
status_info["map_name"] = SSmapping.map_datum.fluff_name
|
||||
status_info["round_id"] = GLOB.round_id
|
||||
|
||||
|
||||
// Add more info if we are authed
|
||||
if(key_valid)
|
||||
if(SSticker && SSticker.mode)
|
||||
|
||||
Reference in New Issue
Block a user