mirror of
https://github.com/CHOMPStation2/CHOMPStation2.git
synced 2025-12-10 18:22:39 +00:00
[MIRROR] TGUI Vote Panel (#9131)
Co-authored-by: Heroman3003 <31296024+Heroman3003@users.noreply.github.com> Co-authored-by: Kashargul <KashL@t-online.de>
This commit is contained in:
@@ -491,3 +491,7 @@ GLOBAL_LIST_INIT(all_volume_channels, list(
|
||||
#define SPECIES_SORT_WHITELISTED 2
|
||||
#define SPECIES_SORT_RESTRICTED 3
|
||||
#define SPECIES_SORT_CUSTOM 4
|
||||
|
||||
// Vote Types
|
||||
#define VOTE_RESULT_TYPE_MAJORITY "Majority"
|
||||
#define VOTE_RESULT_TYPE_SKEWED "Seventy"
|
||||
|
||||
@@ -19,13 +19,13 @@ var/datum/controller/transfer_controller/transfer_controller
|
||||
currenttick = currenttick + 1
|
||||
//VOREStation Edit START
|
||||
if (round_duration_in_ds >= shift_last_vote - 2 MINUTES)
|
||||
shift_last_vote = 1000000000000 //Setting to a stupidly high number since it'll be not used again.
|
||||
shift_last_vote = 1000000000000 //Setting to a stupidly high number since it'll be not used again. //CHOMPEdit
|
||||
to_world("<b>Warning: This upcoming round-extend vote will be your last chance to vote for shift extension. Wrap up your scenes in the next 60 minutes if the round is extended.</b>") //CHOMPStation Edit
|
||||
if (round_duration_in_ds >= shift_hard_end - 1 MINUTE)
|
||||
init_shift_change(null, 1)
|
||||
shift_hard_end = timerbuffer + CONFIG_GET(number/vote_autotransfer_interval) //If shuttle somehow gets recalled, let's force it to call again next time a vote would occur. // CHOMPEdit
|
||||
timerbuffer = timerbuffer + CONFIG_GET(number/vote_autotransfer_interval) //Just to make sure a vote doesn't occur immediately afterwords. // CHOMPEdit
|
||||
else if (round_duration_in_ds >= timerbuffer - 1 MINUTE)
|
||||
SSvote.autotransfer()
|
||||
new /datum/vote/crew_transfer
|
||||
//VOREStation Edit END
|
||||
timerbuffer = timerbuffer + CONFIG_GET(number/vote_autotransfer_interval) // CHOMPEdit
|
||||
|
||||
16
code/controllers/subsystems/SSvote.dm
Normal file
16
code/controllers/subsystems/SSvote.dm
Normal file
@@ -0,0 +1,16 @@
|
||||
SUBSYSTEM_DEF(vote)
|
||||
name = "Vote"
|
||||
wait = 10
|
||||
priority = FIRE_PRIORITY_VOTE
|
||||
runlevels = RUNLEVEL_LOBBY | RUNLEVELS_DEFAULT
|
||||
flags = SS_KEEP_TIMING | SS_NO_INIT
|
||||
|
||||
var/datum/vote/active_vote
|
||||
|
||||
/datum/controller/subsystem/vote/fire()
|
||||
if(active_vote)
|
||||
active_vote.tick()
|
||||
|
||||
/datum/controller/subsystem/vote/proc/start_vote(datum/vote/V)
|
||||
active_vote = V
|
||||
active_vote.start()
|
||||
@@ -87,7 +87,7 @@ var/global/datum/controller/subsystem/ticker/ticker
|
||||
|
||||
if(start_immediately)
|
||||
pregame_timeleft = 0
|
||||
else if(SSvote.time_remaining)
|
||||
else if(SSvote.active_vote)
|
||||
return // vote still going, wait for it.
|
||||
|
||||
// Time to start the game!
|
||||
@@ -98,8 +98,8 @@ var/global/datum/controller/subsystem/ticker/ticker
|
||||
fire() // Don't wait for next tick, do it now!
|
||||
return
|
||||
|
||||
if(pregame_timeleft <= CONFIG_GET(number/vote_autogamemode_timeleft) && !SSvote.gamemode_vote_called)
|
||||
SSvote.autogamemode() // Start the game mode vote (if we haven't had one already)
|
||||
//if(pregame_timeleft <= CONFIG_GET(number/vote_autogamemode_timeleft) && !SSvote.gamemode_vote_called) //CHOMPEdit
|
||||
//SSvote.autogamemode() // Start the game mode vote (if we haven't had one already) //CHOMPEdit
|
||||
|
||||
// Called during GAME_STATE_SETTING_UP (RUNLEVEL_SETUP)
|
||||
/datum/controller/subsystem/ticker/proc/setup_tick(resumed = FALSE)
|
||||
@@ -107,7 +107,7 @@ var/global/datum/controller/subsystem/ticker/ticker
|
||||
if(!setup_choose_gamemode())
|
||||
// It failed, go back to lobby state and re-send the welcome message
|
||||
pregame_timeleft = CONFIG_GET(number/pregame_time) // CHOMPEdit
|
||||
SSvote.gamemode_vote_called = FALSE // Allow another autogamemode vote
|
||||
// SSvote.gamemode_vote_called = FALSE // Allow another autogamemode vote
|
||||
current_state = GAME_STATE_PREGAME
|
||||
Master.SetRunLevel(RUNLEVEL_LOBBY)
|
||||
pregame_welcome()
|
||||
@@ -229,7 +229,7 @@ var/global/datum/controller/subsystem/ticker/ticker
|
||||
mode.cleanup()
|
||||
//call a transfer shuttle vote
|
||||
to_world("<span class='danger'>The round has ended!</span>")
|
||||
SSvote.autotransfer()
|
||||
new /datum/vote/crew_transfer
|
||||
|
||||
// Called during GAME_STATE_FINISHED (RUNLEVEL_POSTGAME)
|
||||
/datum/controller/subsystem/ticker/proc/post_game_tick()
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/*
|
||||
SUBSYSTEM_DEF(vote)
|
||||
name = "Vote"
|
||||
wait = 10
|
||||
@@ -404,3 +405,4 @@ SUBSYSTEM_DEF(vote)
|
||||
|
||||
if(SSvote)
|
||||
src << browse(SSvote.interface(src), "window=vote;size=500x[300 + SSvote.choices.len * 25]")
|
||||
*/
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/*
|
||||
SUBSYSTEM_DEF(vote)
|
||||
name = "Vote"
|
||||
wait = 10
|
||||
@@ -391,3 +392,4 @@ SUBSYSTEM_DEF(vote)
|
||||
|
||||
if(SSvote)
|
||||
src << browse(SSvote.interface(src), "window=vote;size=500x[300 + SSvote.choices.len * 25]")
|
||||
*/
|
||||
|
||||
@@ -127,6 +127,7 @@
|
||||
panel.set_content(output)
|
||||
panel.open()
|
||||
return
|
||||
|
||||
//CHOMPEdit Begin
|
||||
/mob/new_player/get_status_tab_items()
|
||||
. = ..()
|
||||
@@ -134,8 +135,8 @@
|
||||
|
||||
. += "Game Mode: [SSticker.hide_mode ? "Secret" : "[config.mode_names[master_mode]]"]"
|
||||
|
||||
if(SSvote.mode)
|
||||
. += "Vote: [capitalize(SSvote.mode)] Time Left: [SSvote.time_remaining] s"
|
||||
//if(SSvote.mode)
|
||||
// . += "Vote: [capitalize(SSvote.mode)] Time Left: [SSvote.time_remaining] s"
|
||||
|
||||
if(SSticker.current_state == GAME_STATE_INIT)
|
||||
. += "Time To Start: Server Initializing"
|
||||
|
||||
200
code/modules/vote/vote_datum.dm
Normal file
200
code/modules/vote/vote_datum.dm
Normal 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
code/modules/vote/vote_presets.dm
Normal file
14
code/modules/vote/vote_presets.dm
Normal 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
code/modules/vote/vote_verb.dm
Normal file
56
code/modules/vote/vote_verb.dm
Normal 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)
|
||||
44
tgui/packages/tgui/interfaces/VotePanel.tsx
Normal file
44
tgui/packages/tgui/interfaces/VotePanel.tsx
Normal file
@@ -0,0 +1,44 @@
|
||||
import { useBackend } from '../backend';
|
||||
import { Box, Button, Section } from '../components';
|
||||
import { Window } from '../layouts';
|
||||
|
||||
type Data = {
|
||||
remaining: number;
|
||||
question: string;
|
||||
choices: string[];
|
||||
user_vote: string | null;
|
||||
counts: Record<string, number>;
|
||||
show_counts: boolean;
|
||||
};
|
||||
|
||||
export const VotePanel = (props, context) => {
|
||||
const { act, data } = useBackend<Data>();
|
||||
const { remaining, question, choices, user_vote, counts, show_counts } = data;
|
||||
|
||||
return (
|
||||
<Window width={400} height={360}>
|
||||
<Window.Content>
|
||||
<Section fill scrollable title={question}>
|
||||
<Box mb={1.5} ml={0.5}>
|
||||
Time remaining: {remaining.toFixed(0)}s
|
||||
</Box>
|
||||
{choices.map((choice, i) => (
|
||||
<Box key={i}>
|
||||
<Button
|
||||
mb={1}
|
||||
fluid
|
||||
lineHeight={3}
|
||||
content={
|
||||
choice +
|
||||
(show_counts ? ' (' + (counts[choice] || 0) + ')' : '')
|
||||
}
|
||||
onClick={() => act('vote', { target: choice })}
|
||||
selected={choice === user_vote}
|
||||
/>
|
||||
</Box>
|
||||
))}
|
||||
</Section>
|
||||
</Window.Content>
|
||||
</Window>
|
||||
);
|
||||
};
|
||||
@@ -400,6 +400,7 @@
|
||||
#include "code\controllers\subsystems\sounds.dm"
|
||||
#include "code\controllers\subsystems\speech_controller_ch.dm"
|
||||
#include "code\controllers\subsystems\sqlite.dm"
|
||||
#include "code\controllers\subsystems\SSvote.dm"
|
||||
#include "code\controllers\subsystems\statpanel_ch.dm"
|
||||
#include "code\controllers\subsystems\sun.dm"
|
||||
#include "code\controllers\subsystems\supply.dm"
|
||||
@@ -4554,6 +4555,9 @@
|
||||
#include "code\modules\vore\resizing\sizegun_vr.dm"
|
||||
#include "code\modules\vore\smoleworld\smoleworld_vr.dm"
|
||||
#include "code\modules\vore\weight\fitness_machines_vr.dm"
|
||||
#include "code\modules\vote\vote_datum.dm"
|
||||
#include "code\modules\vote\vote_presets.dm"
|
||||
#include "code\modules\vote\vote_verb.dm"
|
||||
#include "code\modules\webhooks\_webhook.dm"
|
||||
#include "code\modules\webhooks\webhook_ahelp2discord.dm"
|
||||
#include "code\modules\webhooks\webhook_custom_event.dm"
|
||||
|
||||
Reference in New Issue
Block a user