Merge pull request #16383 from TheCaramelion/TGUI-Vote

TGUI Vote Panel
This commit is contained in:
Heroman3003
2024-10-04 06:49:44 +10:00
committed by GitHub
11 changed files with 349 additions and 9 deletions
+2 -2
View File
@@ -117,8 +117,8 @@
if(statpanel("Lobby") && SSticker)
stat("Game Mode:", SSticker.hide_mode ? "Secret" : "[config.mode_names[master_mode]]")
if(SSvote.mode)
stat("Vote: [capitalize(SSvote.mode)]", "Time Left: [SSvote.time_remaining] s")
// if(SSvote.mode)
// stat("Vote: [capitalize(SSvote.mode)]", "Time Left: [SSvote.time_remaining] s")
if(SSticker.current_state == GAME_STATE_INIT)
stat("Time To Start:", "Server Initializing")
+200
View File
@@ -0,0 +1,200 @@
/datum/vote
// Person who started the vote
var/initiator = "the server"
// world.time the bote 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 count 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 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 don't want to hold clients refs.___callbackvarset(list_or_datum, var_name, var_value)
var/list/voted = list()
// For how long will it be up
var/vote_time = 60 SECONDS
/datum/vote/New(var/_initiator, var/_question, list/_choices, var/_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 + vote_time - world.time)/10), 0)
/datum/vote/proc/calculate_result()
if(!length(voted))
to_chat(world, span_interface("No votes were cast. Do you all hate democracy?!"))
return null
return calculate_vote_result(voted, choices, vote_result_type)
/datum/vote/proc/calculate_vote_result(var/list/voted, var/list/choices, var/vote_result_type)
var/list/results = list()
for(var/ck in voted)
if(voted[ck] in results)
results[voted[ck]]++
else
results[voted[ck]] = 1
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
for(var/res in results)
to_chat(world, span_interface("<code>[res]</code> - [results[res]] vote\s"))
switch(vote_result_type)
if(VOTE_RESULT_TYPE_MAJORITY)
if(length(winning_options) == 1)
var/res = winning_options[1]
if(res in choices)
to_chat(world, span_interface("<b><code>[res]</code> won the vote!</b>"))
return res
else
to_chat(world, span_interface("The winner of the vote ([sanitize(res)]) isn't a valid choice? What the heck?"))
stack_trace("Vote concluded with an invalid answer. Answer was [sanitize(res)], choices were [json_encode(choices)]")
return null
to_chat(world, span_interface("<b>No clear winner. The vote did not pass.</b>"))
return null
if(VOTE_RESULT_TYPE_SKEWED)
var/required_votes = ceil(length(voted) * 0.7) // 70% of total votes
if(maxvotes >= required_votes && length(winning_options) == 1)
var/res = winning_options[1]
if(res in choices)
to_chat(world, span_interface("<b><code>[res]</code> won the vote with a 70% majority!</b>"))
return res
else
to_chat(world, span_interface("The winner of the vote ([sanitize(res)]) isn't a valid choice? What the heck?"))
stack_trace("Vote concluded with an invalid answer. Answer was [sanitize(res)], choices were [json_encode(choices)]")
return null
to_chat(world, span_interface("<b>No option received 70% of the votes. The vote did not pass.</b>"))
return null
return null
/datum/vote/proc/announce(start_text, var/time = vote_time)
to_chat(world, span_lightpurple("Type <b>vote</b> or click <a href='?src=\ref[src];[HrefToken()];vote=open'>here</a> to place your vote. \
You have [time] seconds to vote."))
world << sound('sound/ambience/alarm4.ogg', repeat = 0, wait = 0, volume = 50, channel = 3)
/datum/vote/Topic(href, list/href_list)
if(href_list["vote"] == "open")
if(src)
tgui_interact(usr)
else
to_chat(usr, "There is no active vote to participate in.")
/datum/vote/proc/tick()
if(remaining() == 0)
var/result = calculate_result()
handle_result(result)
qdel(src)
/datum/vote/Destroy(force)
if(SSvote.active_vote == src)
SSvote.active_vote = null
return ..()
/datum/vote/proc/handle_result(result)
return
/datum/vote/proc/generate_choices()
return
/*
UI STUFFS
*/
/datum/vote/tgui_state(mob/user)
return GLOB.tgui_always_state
/datum/vote/tgui_interact(mob/user, datum/tgui/ui = null)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "VotePanel", "Vote Panel")
ui.open()
/datum/vote/tgui_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
if(show_counts || check_rights(R_ADMIN, FALSE, user))
data["show_counts"] = TRUE
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()
return data
/datum/vote/tgui_act(action, list/params, datum/tgui/ui, datum/tgui_state/state)
if(..())
return
. = TRUE
switch(action)
if("vote")
if(params["target"] in choices)
voted[usr.ckey] = params["target"]
else
message_admins(span_warning("User [key_name_admin(usr)] spoofed a vote in the vote panel!"))
+14
View File
@@ -0,0 +1,14 @@
/datum/vote/crew_transfer
question = "End the shift"
choices = list("Initiate Crew Transfer", "Extend The Shift")
vote_type_text = "crew transfer"
vote_result_type = VOTE_RESULT_TYPE_SKEWED
/datum/vote/crew_transfer/New()
if(SSticker.current_state < GAME_STATE_PLAYING)
CRASH("Attempted to call a shutle vote before the game starts!")
..()
/datum/vote/crew_transfer/handle_result(result)
if(result == "Initiate Crew Transfer")
init_shift_change(null, TRUE)
+56
View File
@@ -0,0 +1,56 @@
/client/verb/vote()
set category = "OOC"
set name = "Vote"
if(SSvote.active_vote)
SSvote.active_vote.tgui_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(!is_admin())
return
if(SSvote.active_vote)
to_chat(usr, "A vote is already in progress")
return
var/vote_types = subtypesof(/datum/vote)
vote_types |= "\[CUSTOM]"
var/list/votemap = list()
for(var/vtype in vote_types)
votemap["[vtype]"] = vtype
var/choice = tgui_input_list(usr, "Select a vote type", "Vote", vote_types)
if(choice == null)
return
if(choice != "\[CUSTOM]")
var/datum/votetype = votemap["[choice]"]
SSvote.start_vote(new votetype(usr.ckey))
return
var/question = tgui_input_text(usr, "What is the vote for?", "Create Vote", encode = FALSE)
if(isnull(question))
return
var/list/choices = list()
for(var/i in 1 to 10)
var/option = tgui_input_text(usr, "Please enter an option or hit cancel to finish", "Create Vote", encode = FALSE)
if(isnull(option) || !usr.client)
break
choices |= option
var/c2 = tgui_alert(usr, "Show counts while vote is happening?", "Counts", list("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)