mirror of
https://github.com/ParadiseSS13/Paradise.git
synced 2026-08-22 11:37:40 +01:00
Refactors vote code (#18403)
* Vote refactor * Tweaks * Review tweaks * Tweak
This commit is contained in:
@@ -667,7 +667,7 @@ so as to remain in compliance with the most up-to-date laws."
|
||||
icon_state = "map_vote"
|
||||
|
||||
/obj/screen/alert/notify_mapvote/Click()
|
||||
SSvote.browse_to(usr.client)
|
||||
usr.client.vote()
|
||||
|
||||
//OBJECT-BASED
|
||||
|
||||
|
||||
@@ -1,17 +1,11 @@
|
||||
/// Config holder for stuff relating to the ingame vote system
|
||||
/datum/configuration_section/vote_configuration
|
||||
/// Allow players to start restart votes?
|
||||
var/allow_restart_votes = FALSE
|
||||
/// Allow players to start gamemode votes?
|
||||
var/allow_mode_votes = FALSE
|
||||
/// Minimum delay between each vote (deciseconds)
|
||||
var/vote_delay = 18000 // 30 mins
|
||||
/// How long will a vote last for (deciseconds)
|
||||
var/vote_time = 600 // 60 seconds
|
||||
var/vote_time = 60 SECONDS // 60 seconds
|
||||
/// Time before the first shuttle vote (deciseconds)
|
||||
var/autotransfer_initial_time = 72000 // 2 hours
|
||||
var/autotransfer_initial_time = 2 HOURS // 2 hours
|
||||
/// Time between subsequent shuttle votes if the first one is not successful (deciseconds)
|
||||
var/autotransfer_interval_time = 18000 // 30 mins
|
||||
var/autotransfer_interval_time = 30 MINUTES // 30 mins
|
||||
/// Prevent dead players from voting
|
||||
var/prevent_dead_voting = FALSE
|
||||
/// Default to players not voting
|
||||
@@ -21,13 +15,10 @@
|
||||
|
||||
/datum/configuration_section/vote_configuration/load_data(list/data)
|
||||
// Use the load wrappers here. That way the default isnt made 'null' if you comment out the config line
|
||||
CONFIG_LOAD_BOOL(allow_restart_votes, data["allow_vote_restart"])
|
||||
CONFIG_LOAD_BOOL(allow_mode_votes, data["allow_vote_mode"])
|
||||
CONFIG_LOAD_BOOL(prevent_dead_voting, data["prevent_dead_voting"])
|
||||
CONFIG_LOAD_BOOL(disable_default_vote, data["disable_default_vote"])
|
||||
CONFIG_LOAD_BOOL(enable_map_voting, data["enable_map_voting"])
|
||||
|
||||
CONFIG_LOAD_NUM(vote_delay, data["vote_delay"])
|
||||
CONFIG_LOAD_NUM(vote_time, data["vote_time"])
|
||||
CONFIG_LOAD_NUM(autotransfer_initial_time, data["autotransfer_initial_time"])
|
||||
CONFIG_LOAD_NUM(autotransfer_interval_time, data["autotransfer_interval_time"])
|
||||
|
||||
@@ -116,7 +116,7 @@ SUBSYSTEM_DEF(ticker)
|
||||
mode.process_job_tasks()
|
||||
|
||||
if(world.time > next_autotransfer)
|
||||
SSvote.autotransfer()
|
||||
SSvote.start_vote(new /datum/vote/crew_transfer)
|
||||
next_autotransfer = world.time + GLOB.configuration.vote.autotransfer_interval_time
|
||||
|
||||
var/game_finished = SSshuttle.emergency.mode >= SHUTTLE_ENDGAME || mode.station_was_nuked
|
||||
@@ -133,7 +133,7 @@ SUBSYSTEM_DEF(ticker)
|
||||
declare_completion()
|
||||
addtimer(CALLBACK(src, .proc/call_reboot), 5 SECONDS)
|
||||
if(GLOB.configuration.vote.enable_map_voting)
|
||||
SSvote.initiate_vote("map", "the server", TRUE) // Start a map vote. Timing is a little tight here but we should be good.
|
||||
SSvote.start_vote(new /datum/vote/map)
|
||||
|
||||
/datum/controller/subsystem/ticker/proc/call_reboot()
|
||||
if(mode.station_was_nuked)
|
||||
|
||||
@@ -5,437 +5,18 @@ SUBSYSTEM_DEF(vote)
|
||||
runlevels = RUNLEVEL_LOBBY | RUNLEVELS_DEFAULT
|
||||
offline_implications = "Votes (Endround shuttle) will no longer function. Shuttle call recommended."
|
||||
|
||||
var/initiator = null
|
||||
var/started_time = null
|
||||
var/time_remaining = 0
|
||||
var/mode = null
|
||||
var/question = null
|
||||
var/list/choices = list()
|
||||
var/list/voted = list()
|
||||
var/list/voting = list()
|
||||
var/list/current_votes = list()
|
||||
var/list/round_voters = list()
|
||||
var/auto_muted = 0
|
||||
/// Active vote, if any
|
||||
var/datum/vote/active_vote
|
||||
|
||||
/datum/controller/subsystem/vote/fire()
|
||||
if(mode)
|
||||
// No more change mode votes after the game has started.
|
||||
if(mode == "gamemode" && SSticker.current_state >= GAME_STATE_SETTING_UP)
|
||||
to_chat(world, "<b>Voting aborted due to game start.</b>")
|
||||
reset()
|
||||
return
|
||||
if(active_vote)
|
||||
active_vote.tick()
|
||||
|
||||
// Calculate how much time is remaining by comparing current time, to time of vote start,
|
||||
// plus vote duration
|
||||
time_remaining = round((started_time + GLOB.configuration.vote.vote_time - world.time)/10)
|
||||
/datum/controller/subsystem/vote/proc/start_vote(datum/vote/V)
|
||||
// This will be fun if DM ever gets concurrency
|
||||
active_vote = V
|
||||
active_vote.start()
|
||||
|
||||
if(time_remaining < 0)
|
||||
result()
|
||||
for(var/client/C in voting)
|
||||
if(C)
|
||||
C << browse(null,"window=vote")
|
||||
reset()
|
||||
else
|
||||
for(var/client/C in voting)
|
||||
update_panel(C)
|
||||
CHECK_TICK
|
||||
|
||||
/datum/controller/subsystem/vote/proc/autotransfer()
|
||||
initiate_vote("crew transfer", "the server")
|
||||
|
||||
/datum/controller/subsystem/vote/proc/reset()
|
||||
initiator = null
|
||||
time_remaining = 0
|
||||
mode = null
|
||||
question = null
|
||||
choices.Cut()
|
||||
voted.Cut()
|
||||
voting.Cut()
|
||||
current_votes.Cut()
|
||||
|
||||
if(auto_muted && !GLOB.ooc_enabled && !(GLOB.configuration.general.auto_disable_ooc && SSticker.current_state == GAME_STATE_PLAYING))
|
||||
auto_muted = 0
|
||||
GLOB.ooc_enabled = TRUE
|
||||
to_chat(world, "<b>The OOC channel has been automatically enabled due to vote end.</b>")
|
||||
log_admin("OOC was toggled automatically due to vote end.")
|
||||
message_admins("OOC has been toggled on automatically.")
|
||||
|
||||
|
||||
/datum/controller/subsystem/vote/proc/get_result()
|
||||
var/greatest_votes = 0
|
||||
var/total_votes = 0
|
||||
var/list/sorted_choices = list()
|
||||
var/sorted_highest
|
||||
var/sorted_votes = -1
|
||||
//get the highest number of votes, while also sorting the list
|
||||
while(choices.len)
|
||||
// This is a very inefficient sorting method, but that's okay
|
||||
for(var/option in choices)
|
||||
var/votes = choices[option]
|
||||
if(sorted_votes < votes)
|
||||
sorted_highest = option
|
||||
sorted_votes = votes
|
||||
if(votes > greatest_votes)
|
||||
greatest_votes = votes
|
||||
sorted_votes = -1
|
||||
total_votes += choices[sorted_highest]
|
||||
sorted_choices[sorted_highest] = choices[sorted_highest] || 0
|
||||
choices -= sorted_highest
|
||||
choices = sorted_choices
|
||||
//default-vote for everyone who didn't vote
|
||||
if(!GLOB.configuration.vote.disable_default_vote && choices.len)
|
||||
var/non_voters = (GLOB.clients.len - total_votes)
|
||||
if(non_voters > 0)
|
||||
if(mode == "restart")
|
||||
choices["Continue Playing"] += non_voters
|
||||
if(choices["Continue Playing"] >= greatest_votes)
|
||||
greatest_votes = choices["Continue Playing"]
|
||||
else if(mode == "gamemode")
|
||||
if(GLOB.master_mode in choices)
|
||||
choices[GLOB.master_mode] += non_voters
|
||||
if(choices[GLOB.master_mode] >= greatest_votes)
|
||||
greatest_votes = choices[GLOB.master_mode]
|
||||
else if(mode == "crew transfer")
|
||||
var/factor = 0.5
|
||||
switch(world.time / (10 * 60)) // minutes
|
||||
if(0 to 60)
|
||||
factor = 0.5
|
||||
if(61 to 120)
|
||||
factor = 0.8
|
||||
if(121 to 240)
|
||||
factor = 1
|
||||
if(241 to 300)
|
||||
factor = 1.2
|
||||
else
|
||||
factor = 1.4
|
||||
choices["Initiate Crew Transfer"] = round(choices["Initiate Crew Transfer"] * factor)
|
||||
to_chat(world, "<font color='purple'>Crew Transfer Factor: [factor]</font>")
|
||||
greatest_votes = max(choices["Initiate Crew Transfer"], choices["Continue The Round"])
|
||||
|
||||
|
||||
//get all options with that many votes and return them in a list
|
||||
. = list()
|
||||
if(greatest_votes)
|
||||
for(var/option in choices)
|
||||
if(choices[option] == greatest_votes)
|
||||
. += option
|
||||
return .
|
||||
|
||||
/datum/controller/subsystem/vote/proc/announce_result()
|
||||
var/list/winners = get_result()
|
||||
var/text
|
||||
if(winners.len > 0)
|
||||
if(winners.len > 1)
|
||||
if(mode != "gamemode" || SSticker.hide_mode == 0) // Here we are making sure we don't announce potential game modes
|
||||
text = "<b>Vote Tied Between:</b>\n"
|
||||
for(var/option in winners)
|
||||
text += "\t[option]\n"
|
||||
. = pick(winners)
|
||||
|
||||
for(var/key in current_votes)
|
||||
if(choices[current_votes[key]] == .)
|
||||
round_voters += key // Keep track of who voted for the winning round.
|
||||
if(mode == "gamemode" && (. == "extended" || SSticker.hide_mode == 0)) // Announce Extended gamemode, but not other gamemodes
|
||||
text += "<b>Vote Result: [.] ([choices[.]] vote\s)</b>"
|
||||
else
|
||||
if(mode == "custom")
|
||||
// Completely replace text to show all results in custom votes
|
||||
text = "<b><span style='text-decoration: underline;'>[question]</span></b>\n"
|
||||
for(var/option in winners)
|
||||
text += "\t<b>[option]: [choices[option]] vote\s</b>\n"
|
||||
for(var/option in (choices-winners))
|
||||
text += "\t[option]: [choices[option]] vote\s\n"
|
||||
else if(mode != "gamemode")
|
||||
text += "<b>Vote Result: [.] ([choices[.]] vote\s)</b>"
|
||||
else
|
||||
text += "<b>The vote has ended.</b>" // What will be shown if it is a gamemode vote that isn't extended
|
||||
|
||||
else
|
||||
text += "<b>Vote Result: Inconclusive - No Votes!</b>"
|
||||
log_vote(text)
|
||||
to_chat(world, "<font color='purple'>[text]</font>")
|
||||
return .
|
||||
|
||||
/datum/controller/subsystem/vote/proc/result()
|
||||
. = announce_result()
|
||||
var/restart = 0
|
||||
if(.)
|
||||
for(var/option in choices)
|
||||
SSblackbox.record_feedback("nested tally", "votes", choices[option], list(mode, option), ignore_seal = TRUE)
|
||||
switch(mode)
|
||||
if("restart")
|
||||
if(. == "Restart Round")
|
||||
restart = 1
|
||||
if("gamemode")
|
||||
if(GLOB.master_mode != .)
|
||||
world.save_mode(.)
|
||||
if(SSticker && SSticker.mode)
|
||||
restart = 1
|
||||
else
|
||||
GLOB.master_mode = .
|
||||
if(!SSticker.ticker_going)
|
||||
SSticker.ticker_going = TRUE
|
||||
to_chat(world, "<font color='red'><b>The round will start soon.</b></font>")
|
||||
if("crew transfer")
|
||||
if(. == "Initiate Crew Transfer")
|
||||
init_shift_change(null, TRUE)
|
||||
if("map")
|
||||
// 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(. == "[initial(M.fluff_name)] ([initial(M.technical_name)])")
|
||||
top_voted_map = M
|
||||
to_chat(world, "<font color='purple'>Map for next round: [initial(top_voted_map.fluff_name)] ([initial(top_voted_map.technical_name)])</font>")
|
||||
SSmapping.next_map = new top_voted_map
|
||||
|
||||
if(restart)
|
||||
SSticker.reboot_helper("Restart vote successful.", "restart vote")
|
||||
|
||||
return .
|
||||
|
||||
/datum/controller/subsystem/vote/proc/submit_vote(ckey, vote)
|
||||
if(mode)
|
||||
if(GLOB.configuration.vote.prevent_dead_voting && usr.stat == DEAD && !usr.client.holder)
|
||||
return 0
|
||||
if(current_votes[ckey])
|
||||
choices[choices[current_votes[ckey]]]--
|
||||
if(vote && 1<=vote && vote<=choices.len)
|
||||
voted += usr.ckey
|
||||
choices[choices[vote]]++ //check this
|
||||
current_votes[ckey] = vote
|
||||
return vote
|
||||
return 0
|
||||
|
||||
/datum/controller/subsystem/vote/proc/initiate_vote(vote_type, initiator_key, code_invoked = FALSE)
|
||||
if(!mode)
|
||||
if(usr && started_time != null && !check_rights(R_ADMIN)) // Allow the game to call votes whenever. But check other callers
|
||||
var/next_allowed_time = (started_time + GLOB.configuration.vote.vote_delay)
|
||||
if(next_allowed_time > world.time)
|
||||
return 0
|
||||
|
||||
reset()
|
||||
switch(vote_type)
|
||||
if("restart")
|
||||
choices.Add("Restart Round","Continue Playing")
|
||||
if("gamemode")
|
||||
if(SSticker.current_state >= 2)
|
||||
return 0
|
||||
choices.Add(GLOB.configuration.gamemode.votable_modes)
|
||||
if("crew transfer")
|
||||
if(check_rights(R_ADMIN|R_MOD))
|
||||
if(SSticker.current_state <= 2)
|
||||
return 0
|
||||
question = "End the shift?"
|
||||
choices.Add("Initiate Crew Transfer", "Continue The Round")
|
||||
else
|
||||
if(SSticker.current_state <= 2)
|
||||
return 0
|
||||
question = "End the shift?"
|
||||
choices.Add("Initiate Crew Transfer", "Continue The Round")
|
||||
if("map")
|
||||
if(!(check_rights(R_SERVER) || code_invoked))
|
||||
return FALSE
|
||||
question = "Map for next round"
|
||||
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)])")
|
||||
|
||||
if("custom")
|
||||
question = html_encode(input(usr,"What is the vote for?") as text|null)
|
||||
if(!question) return 0
|
||||
for(var/i=1,i<=10,i++)
|
||||
var/option = capitalize(html_encode(input(usr,"Please enter an option or hit cancel to finish") as text|null))
|
||||
if(!option || mode || !usr.client) break
|
||||
choices.Add(option)
|
||||
else
|
||||
return 0
|
||||
mode = vote_type
|
||||
initiator = initiator_key
|
||||
started_time = world.time
|
||||
var/text = "[capitalize(mode)] vote started by [initiator]."
|
||||
if(mode == "custom")
|
||||
text += "\n[question]"
|
||||
if(usr)
|
||||
log_admin("[capitalize(mode)] ([question]) vote started by [key_name(usr)].")
|
||||
else if(usr)
|
||||
log_admin("[capitalize(mode)] vote started by [key_name(usr)].")
|
||||
|
||||
log_vote(text)
|
||||
to_chat(world, {"<font color='purple'><b>[text]</b>
|
||||
<a href='?src=[UID()];vote=open'>Click here or type vote to place your vote.</a>
|
||||
You have [GLOB.configuration.vote.vote_time / 10] seconds to vote.</font>"})
|
||||
switch(vote_type)
|
||||
if("crew transfer", "gamemode", "custom")
|
||||
SEND_SOUND(world, sound('sound/ambience/alarm4.ogg'))
|
||||
if("map")
|
||||
SEND_SOUND(world, sound('sound/ambience/alarm4.ogg'))
|
||||
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)
|
||||
if(mode == "gamemode" && SSticker.ticker_going)
|
||||
SSticker.ticker_going = FALSE
|
||||
to_chat(world, "<font color='red'><b>Round start has been delayed.</b></font>")
|
||||
if(mode == "crew transfer" && GLOB.ooc_enabled)
|
||||
auto_muted = TRUE
|
||||
GLOB.ooc_enabled = FALSE
|
||||
to_chat(world, "<b>The OOC channel has been automatically disabled due to a crew transfer vote.</b>")
|
||||
log_admin("OOC was toggled automatically due to crew transfer vote.")
|
||||
message_admins("OOC has been toggled off automatically.")
|
||||
if(mode == "gamemode" && GLOB.ooc_enabled)
|
||||
auto_muted = TRUE
|
||||
GLOB.ooc_enabled = FALSE
|
||||
to_chat(world, "<b>The OOC channel has been automatically disabled due to the gamemode vote.</b>")
|
||||
log_admin("OOC was toggled automatically due to gamemode vote.")
|
||||
message_admins("OOC has been toggled off automatically.")
|
||||
if(mode == "custom" && GLOB.ooc_enabled)
|
||||
auto_muted = TRUE
|
||||
GLOB.ooc_enabled = FALSE
|
||||
to_chat(world, "<b>The OOC channel has been automatically disabled due to a custom vote.</b>")
|
||||
log_admin("OOC was toggled automatically due to custom vote.")
|
||||
message_admins("OOC has been toggled off automatically.")
|
||||
|
||||
time_remaining = round(GLOB.configuration.vote.vote_time / 10)
|
||||
return 1
|
||||
return 0
|
||||
|
||||
/datum/controller/subsystem/vote/proc/browse_to(client/C)
|
||||
if(!C)
|
||||
return
|
||||
var/admin = check_rights(R_ADMIN, 0, user = C.mob)
|
||||
voting |= C
|
||||
|
||||
var/dat = {"<script>
|
||||
function update_vote_div(new_content) {
|
||||
var votediv = document.getElementById("vote_div");
|
||||
if(votediv) {
|
||||
votediv.innerHTML = new_content;
|
||||
}
|
||||
}
|
||||
</script>"}
|
||||
if(mode)
|
||||
dat += "<div id='vote_div'>[vote_html(C)]</div><hr>"
|
||||
if(admin)
|
||||
dat += "(<a href='?src=[UID()];vote=cancel'>Cancel Vote</a>) "
|
||||
else
|
||||
dat += "<div id='vote_div'><h2>Start a vote:</h2><hr><ul><li>"
|
||||
// Crew transfer
|
||||
if(admin || GLOB.configuration.vote.allow_restart_votes)
|
||||
dat += "<a href='?src=[UID()];vote=crew_transfer'>Crew Transfer</a>"
|
||||
else
|
||||
dat += "<font color='grey'>Crew Transfer (Disallowed)</font>"
|
||||
dat += "</li><li>"
|
||||
|
||||
// Restart
|
||||
if(admin || GLOB.configuration.vote.allow_restart_votes)
|
||||
dat += "<a href='?src=[UID()];vote=restart'>Restart</a>"
|
||||
else
|
||||
dat += "<font color='grey'>Restart (Disallowed)</font>"
|
||||
if(admin)
|
||||
dat += "\t(<a href='?src=[UID()];vote=toggle_restart'>[GLOB.configuration.vote.allow_restart_votes ? "Allowed" : "Disallowed"]</a>)"
|
||||
dat += "</li><li>"
|
||||
|
||||
// Gamemode
|
||||
if(admin || GLOB.configuration.vote.allow_mode_votes)
|
||||
dat += "<a href='?src=[UID()];vote=gamemode'>Gamemode</a>"
|
||||
else
|
||||
dat += "<font color='grey'>Gamemode (Disallowed)</font>"
|
||||
if(admin)
|
||||
dat += "\t(<a href='?src=[UID()];vote=toggle_gamemode'>[GLOB.configuration.vote.allow_mode_votes ? "Allowed" : "Disallowed"]</a>)"
|
||||
dat += "</li><li>"
|
||||
|
||||
// Map
|
||||
if(admin)
|
||||
dat += "<a href='?src=[UID()];vote=map'>Map</a>"
|
||||
else
|
||||
dat += "<font color='grey'>Map (Disallowed)</font>"
|
||||
dat += "</li><li>"
|
||||
|
||||
// Custom
|
||||
if(admin)
|
||||
dat += "<a href='?src=[UID()];vote=custom'>Custom</a></li>"
|
||||
dat += "</ul></div><hr>"
|
||||
var/datum/browser/popup = new(C.mob, "vote", "Voting Panel", nref=src)
|
||||
popup.set_content(dat)
|
||||
popup.open()
|
||||
|
||||
/datum/controller/subsystem/vote/proc/update_panel(client/C)
|
||||
C << output(url_encode(vote_html(C)), "vote.browser:update_vote_div")
|
||||
|
||||
/datum/controller/subsystem/vote/proc/vote_html(client/C)
|
||||
. = ""
|
||||
if(question)
|
||||
. += "<h2>Vote: '[question]'</h2>"
|
||||
else
|
||||
. += "<h2>Vote: [capitalize(mode)]</h2>"
|
||||
. += "Time Left: [time_remaining] s<hr><ul>"
|
||||
for(var/i = 1, i <= choices.len, i++)
|
||||
var/votes = choices[choices[i]]
|
||||
if(!votes)
|
||||
votes = 0
|
||||
var/vote_count = null
|
||||
if(check_rights(R_ADMIN, FALSE, C.mob))
|
||||
vote_count = " ([votes] vote\s)"
|
||||
if(current_votes[C.ckey] == i)
|
||||
. += "<li><b><a href='?src=[UID()];vote=[i]'>[choices[i]][vote_count]</a></b></li>"
|
||||
else
|
||||
. += "<li><a href='?src=[UID()];vote=[i]'>[choices[i]][vote_count]</a></li>"
|
||||
|
||||
. += "</ul>"
|
||||
|
||||
|
||||
/datum/controller/subsystem/vote/Topic(href,href_list[],hsrc)
|
||||
if(!usr || !usr.client)
|
||||
return //not necessary but meh...just in-case somebody does something stupid
|
||||
var/admin = check_rights(R_ADMIN,0)
|
||||
if(href_list["close"])
|
||||
voting -= usr.client
|
||||
return
|
||||
switch(href_list["vote"])
|
||||
if("open")
|
||||
// vote proc will automatically get called after this switch ends
|
||||
if("cancel")
|
||||
if(admin && mode)
|
||||
var/votedesc = capitalize(mode)
|
||||
if(mode == "custom")
|
||||
votedesc += " ([question])"
|
||||
log_and_message_admins("cancelled the running '[votedesc]' vote.")
|
||||
reset()
|
||||
if("toggle_restart")
|
||||
if(admin)
|
||||
GLOB.configuration.vote.allow_restart_votes = !GLOB.configuration.vote.allow_restart_votes
|
||||
log_and_message_admins("has [GLOB.configuration.vote.allow_restart_votes ? "enabled" : "disabled"] public restart voting.")
|
||||
if("toggle_gamemode")
|
||||
if(admin)
|
||||
GLOB.configuration.vote.allow_mode_votes = !GLOB.configuration.vote.allow_mode_votes
|
||||
log_and_message_admins("has [GLOB.configuration.vote.allow_mode_votes ? "enabled" : "disabled"] public gamemode voting.")
|
||||
if("restart")
|
||||
if(GLOB.configuration.vote.allow_restart_votes || admin)
|
||||
initiate_vote("restart",usr.key)
|
||||
if("gamemode")
|
||||
if(GLOB.configuration.vote.allow_mode_votes || admin)
|
||||
initiate_vote("gamemode",usr.key)
|
||||
if("map")
|
||||
if(admin)
|
||||
initiate_vote("map", usr.key)
|
||||
if("crew_transfer")
|
||||
if(GLOB.configuration.vote.allow_restart_votes || admin)
|
||||
initiate_vote("crew transfer", usr.key)
|
||||
if("custom")
|
||||
if(admin)
|
||||
initiate_vote("custom",usr.key)
|
||||
else
|
||||
submit_vote(usr.ckey, round(text2num(href_list["vote"])))
|
||||
update_panel(usr.client)
|
||||
return
|
||||
usr.vote()
|
||||
|
||||
|
||||
/mob/verb/vote()
|
||||
set category = "OOC"
|
||||
set name = "Vote"
|
||||
|
||||
if(SSvote)
|
||||
SSvote.browse_to(client)
|
||||
/datum/controller/subsystem/vote/Topic(href, list/href_list)
|
||||
if(href_list["vote"] == "open")
|
||||
usr.client.vote()
|
||||
|
||||
@@ -22,6 +22,9 @@
|
||||
SHOULD_CALL_PARENT(TRUE)
|
||||
tag = null
|
||||
|
||||
// Close our open TGUIs
|
||||
SStgui.close_uis(src)
|
||||
|
||||
var/list/timers = active_timers
|
||||
active_timers = null
|
||||
for(var/thing in timers)
|
||||
|
||||
@@ -256,16 +256,6 @@
|
||||
candidates += player.mind
|
||||
players -= player
|
||||
|
||||
// If we don't have enough antags, draft people who voted for the round.
|
||||
if(candidates.len < recommended_enemies)
|
||||
for(var/key in SSvote.round_voters)
|
||||
for(var/mob/new_player/player in players)
|
||||
if(player.ckey == key)
|
||||
player_draft_log += "[player.key] voted for this round, so we are drafting them."
|
||||
candidates += player.mind
|
||||
players -= player
|
||||
break
|
||||
|
||||
// Remove candidates who want to be antagonist but have a job that precludes it
|
||||
if(restricted_jobs)
|
||||
for(var/datum/mind/player in candidates)
|
||||
|
||||
@@ -81,7 +81,6 @@
|
||||
STOP_PROCESSING(SSobj, src) // TODO: Have a processing bitflag to reduce on unnecessary loops through the processing lists
|
||||
else
|
||||
STOP_PROCESSING(SSfastprocess, src)
|
||||
SStgui.close_uis(src)
|
||||
return ..()
|
||||
|
||||
//user: The mob that is suiciding
|
||||
|
||||
@@ -226,9 +226,6 @@ GLOBAL_LIST_EMPTY(world_topic_handlers)
|
||||
if(GLOB.configuration.general.server_features)
|
||||
features += GLOB.configuration.general.server_features
|
||||
|
||||
if(GLOB.configuration.vote.allow_restart_votes)
|
||||
features += "vote"
|
||||
|
||||
if(GLOB.configuration.url.wiki_url)
|
||||
features += "<a href=\"[GLOB.configuration.url.wiki_url]\">Wiki</a>"
|
||||
|
||||
|
||||
@@ -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