Files
Yogstation/code/controllers/subsystem/vote.dm
adamsong 44cd06ecc6 [ADMIN] Chganges auth backend to use the forums instead of the DB (#15195)
* [ADMIN] Chganges auth backend to use the forums instead of the DB

* Remove dbranks flag

* I'm dumb

* re-promotes myself

* Re-use datums, rather than continually re-making them

* Delete the datum instead, easier to manage

* Moved to an inhertiance based system for permissions management

* Proccall protection and logging

* Linter

* Update config

* Fixes pp I hope

* Two letters made it do a bad, I am sad

* Clears forums admins when reloading

* Adds db support

* Update config

* Re-enables forum integration

* No editing the funny datum

* Allow me to do the funny during the test merge

* Didn't commit the changes

* Copying and pasting hard

* Sanitize ckey

* Var is unnecessary

* Small debug log to debug dono chat

* Fixes wrong proc call

* Move log, will be a bit much, but is fine because its temporary

* Made better log message

* Fixed reload admins, added debug log to json_decode that was erroring

* Expanded forums integration error handling

* Fully protects funny lists
2022-08-28 14:26:46 +01:00

361 lines
11 KiB
Plaintext

SUBSYSTEM_DEF(vote)
name = "Vote"
wait = 10
flags = SS_KEEP_TIMING|SS_NO_INIT
runlevels = RUNLEVEL_LOBBY | RUNLEVELS_DEFAULT
var/list/choices = list()
var/list/choice_by_ckey = list()
var/list/generated_actions = list()
var/initiator
var/mode
var/question
var/started_time
var/time_remaining
var/list/voted = list()
var/list/voting = list()
// Called by master_controller
/datum/controller/subsystem/vote/fire()
if(!mode)
return
time_remaining = round((started_time + CONFIG_GET(number/vote_period) - world.time)/10)
if(time_remaining < 0)
result()
SStgui.close_uis(src)
reset()
/datum/controller/subsystem/vote/proc/reset()
choices.Cut()
choice_by_ckey.Cut()
initiator = null
mode = null
question = null
time_remaining = 0
voted.Cut()
voting.Cut()
remove_action_buttons()
/datum/controller/subsystem/vote/proc/get_result()
//get the highest number of votes
var/greatest_votes = 0
var/total_votes = 0
for(var/option in choices)
var/votes = choices[option]
total_votes += votes
if(votes > greatest_votes)
greatest_votes = votes
//default-vote for everyone who didn't vote
if(!CONFIG_GET(flag/default_no_vote) && choices.len)
var/list/non_voters = GLOB.directory.Copy()
non_voters -= voted
for (var/non_voter_ckey in non_voters)
var/client/C = non_voters[non_voter_ckey]
if (!C || C.is_afk())
non_voters -= non_voter_ckey
if(non_voters.len > 0)
if(mode == "restart")
choices["Continue Playing"] += non_voters.len
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.len
if(choices[GLOB.master_mode] >= greatest_votes)
greatest_votes = choices[GLOB.master_mode]
else if(mode == "map")
for (var/non_voter_ckey in non_voters)
var/client/C = non_voters[non_voter_ckey]
var/preferred_map = C.prefs.preferred_map
if(isnull(global.config.defaultmap))
continue
if(!preferred_map)
if(global.config.defaultmap.map_name)
preferred_map = global.config.defaultmap.map_name
if(preferred_map)
choices[preferred_map] += 1
greatest_votes = max(greatest_votes, choices[preferred_map])
. = 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(question)
text += "<b>[question]</b>"
else
text += "<b>[capitalize(mode)] Vote</b>"
for(var/i=1,i<=choices.len,i++)
var/votes = choices[choices[i]]
if(!votes)
votes = 0
text += "\n<b>[choices[i]]:</b> [votes]"
if(mode != "custom")
if(winners.len > 1)
text = "\n<b>Vote Tied Between:</b>"
for(var/option in winners)
text += "\n\t[option]"
. = pick(winners)
text += "\n<b>Vote Result: [.]</b>"
else
text += "\n<b>Did not vote:</b> [GLOB.clients.len-voted.len]"
else
text += "<b>Vote Result: Inconclusive - No Votes!</b>"
log_vote(text)
remove_action_buttons()
to_chat(world, "\n<span class='infoplain'><font color='purple'>[text]</font></span>")
return .
/datum/controller/subsystem/vote/proc/result()
. = announce_result()
var/restart = FALSE
if(.)
switch(mode)
if("restart")
if(. == "Restart Round")
restart = TRUE
if("gamemode")
if(GLOB.master_mode != .)
SSticker.save_mode(.)
if(SSticker.HasRoundStarted())
restart = TRUE
else
GLOB.master_mode = .
if("map")
SSmapping.changemap(global.config.maplist[.])
SSmapping.map_voted = TRUE
if(restart)
var/active_admins = FALSE
for(var/client/C in GLOB.permissions.admins + GLOB.permissions.deadmins)
if(!C.is_afk() && check_rights_for(C, R_SERVER))
active_admins = TRUE
break
if(!active_admins)
// No delay in case the restart is due to lag
SSticker.Reboot("Restart vote successful.", "restart vote", 1)
else
to_chat(world, "<span style='boldannounce'>Notice:Restart vote will not restart the server automatically because there are active admins on.</span>")
message_admins("A restart vote has passed, but there are active admins on with +server, so it has been canceled. If you wish, you may restart the server.")
return .
/datum/controller/subsystem/vote/proc/submit_vote(vote)
if(!mode)
return FALSE
if(CONFIG_GET(flag/no_dead_vote) && usr.stat == DEAD && !usr.client.holder)
return FALSE
if(!vote || vote < 1 || vote > choices.len)
return FALSE
// If user has already voted, remove their specific vote
if(usr.ckey in voted)
choices[choices[choice_by_ckey[usr.ckey]]]--
else
voted += usr.ckey
choice_by_ckey[usr.ckey] = vote
choices[choices[vote]]++
return vote
/datum/controller/subsystem/vote/proc/initiate_vote(vote_type, initiator_key)
//Server is still intializing.
if(!Master.current_runlevel)
to_chat(usr, span_warning("Cannot start vote, server is not done initializing."))
return FALSE
var/lower_admin = FALSE
var/ckey = ckey(initiator_key)
if(GLOB.permissions.admin_datums[ckey])
lower_admin = TRUE
if(!mode)
if(started_time)
var/next_allowed_time = (started_time + CONFIG_GET(number/vote_delay))
if(mode)
to_chat(usr, span_warning("There is already a vote in progress! please wait for it to finish."))
return FALSE
if(next_allowed_time > world.time && !lower_admin)
to_chat(usr, span_warning("A vote was initiated recently, you must wait [DisplayTimeText(next_allowed_time-world.time)] before a new vote can be started!"))
return FALSE
reset()
switch(vote_type)
if("restart")
choices.Add("Restart Round","Continue Playing")
if("gamemode")
choices.Add(config.votable_modes)
if("map")
if(!lower_admin && SSmapping.map_voted)
to_chat(usr, span_warning("The next map has already been selected."))
return FALSE
// Randomizes the list so it isn't always METASTATION
var/list/maps = list()
for(var/map in global.config.maplist)
var/datum/map_config/VM = config.maplist[map]
if(!VM.votable)
continue
if(VM.config_min_users > 0 && GLOB.clients.len < VM.config_min_users)
continue
if(VM.config_max_users > 0 && GLOB.clients.len > VM.config_max_users)
continue
maps += VM.map_name
shuffle_inplace(maps)
for(var/valid_map in maps)
choices.Add(valid_map)
if("custom")
question = stripped_input(usr,"What is the vote for?")
if(!question)
return FALSE
for(var/i=1,i<=10,i++)
var/option = capitalize(stripped_input(usr,"Please enter an option or hit cancel to finish"))
if(!option || mode || !usr.client)
break
choices.Add(option)
else
return FALSE
mode = vote_type
initiator = initiator_key
started_time = world.time
var/text = "[capitalize(mode)] vote started by [initiator || "CentCom"]."
if(mode == "custom")
text += "\n[question]"
log_vote(text)
var/vp = CONFIG_GET(number/vote_period)
to_chat(world, "\n<span class='infoplain'><font color='purple'><b>[text]</b>\nType <b>vote</b> or click <a href='byond://winset?command=vote'>here</a> to place your votes.\nYou have [DisplayTimeText(vp)] to vote.</font></span>")
time_remaining = round(vp/10)
for(var/c in GLOB.clients)
var/client/C = c
var/datum/action/vote/V = new
if(question)
V.name = "Vote: [question]"
C.player_details.player_actions += V
V.Grant(C.mob)
generated_actions += V
if(C.prefs.toggles & SOUND_ANNOUNCEMENTS)
SEND_SOUND(C, sound('sound/misc/bloop.ogg'))
return TRUE
return FALSE
/mob/verb/vote()
set category = "OOC"
set name = "Vote"
SSvote.ui_interact(usr)
/datum/controller/subsystem/vote/ui_state()
return GLOB.always_state
/datum/controller/subsystem/vote/ui_interact(mob/user, datum/tgui/ui)
// Tracks who is voting
if(!(user.client?.ckey in voting))
voting += user.client?.ckey
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "Vote")
ui.open()
/datum/controller/subsystem/vote/ui_data(mob/user)
var/list/data = list(
"allow_vote_map" = CONFIG_GET(flag/allow_vote_map),
"allow_vote_mode" = CONFIG_GET(flag/allow_vote_mode),
"allow_vote_restart" = CONFIG_GET(flag/allow_vote_restart),
"choices" = list(),
"lower_admin" = !!user.client?.holder,
"mode" = mode,
"question" = question,
"selected_choice" = choice_by_ckey[user.client?.ckey],
"time_remaining" = time_remaining,
"upper_admin" = check_rights_for(user.client, R_ADMIN),
"voting" = list(),
)
if(!!user.client?.holder)
data["voting"] = voting
for(var/key in choices)
data["choices"] += list(list(
"name" = key,
"votes" = choices[key] || 0
))
return data
/datum/controller/subsystem/vote/ui_act(action, params)
. = ..()
if(.)
return
var/upper_admin = FALSE
if(usr.client.holder)
if(check_rights_for(usr.client, R_ADMIN))
upper_admin = TRUE
switch(action)
if("cancel")
if(usr.client.holder)
usr.log_message("[key_name_admin(usr)] cancelled a vote.", LOG_ADMIN)
message_admins("[key_name_admin(usr)] has cancelled the current vote.")
reset()
if("toggle_restart")
if(usr.client.holder && upper_admin)
CONFIG_SET(flag/allow_vote_restart, !CONFIG_GET(flag/allow_vote_restart))
if("toggle_gamemode")
if(usr.client.holder && upper_admin)
CONFIG_SET(flag/allow_vote_mode, !CONFIG_GET(flag/allow_vote_mode))
if("toggle_map")
if(usr.client.holder && upper_admin)
CONFIG_SET(flag/allow_vote_map, !CONFIG_GET(flag/allow_vote_map))
if("restart")
if(CONFIG_GET(flag/allow_vote_restart) || usr.client.holder)
initiate_vote("restart",usr.key)
if("gamemode")
if(CONFIG_GET(flag/allow_vote_mode) || usr.client.holder)
initiate_vote("gamemode",usr.key)
if("map")
if(CONFIG_GET(flag/allow_vote_map) || usr.client.holder)
initiate_vote("map",usr.key)
if("custom")
if(usr.client.holder)
initiate_vote("custom",usr.key)
if("vote")
submit_vote(round(text2num(params["index"])))
return TRUE
/datum/controller/subsystem/vote/proc/remove_action_buttons()
for(var/v in generated_actions)
var/datum/action/vote/V = v
if(!QDELETED(V))
V.remove_from_client()
V.Remove(V.owner)
generated_actions = list()
/datum/controller/subsystem/vote/ui_close(mob/user)
voting -= user.client?.ckey
/datum/action/vote
name = "Vote!"
button_icon_state = "vote"
/datum/action/vote/Trigger()
if(owner)
owner.vote()
remove_from_client()
Remove(owner)
/datum/action/vote/IsAvailable()
return TRUE
/datum/action/vote/proc/remove_from_client()
if(!owner)
return
if(owner.client)
owner.client.player_details.player_actions -= src
else if(owner.ckey)
var/datum/player_details/P = GLOB.player_details[owner.ckey]
if(P)
P.player_actions -= src