From 1a37ba3d261e7275da16ecf9da85c33c083c35f2 Mon Sep 17 00:00:00 2001 From: Jaraci <57604458+Jaraci@users.noreply.github.com> Date: Thu, 6 Aug 2026 11:12:51 +0100 Subject: [PATCH] Allows crew transfer votes on red alert, weighted for participation in round (#22993) With massive thanks to @JohnWildkins for help in touching a lot of concepts I've never worked with before, as well as helping me get back into contributing in general. This PR allows crew transfers to be called during red alert and adds support for weighted voting to help favour active participants in these particular rounds. Observers and players still in the lobby are given only half a vote towards transfer/continue. There is also config support to define an active 'participation time' in minutes, which is used to stop potential abuse cases of players respawning/joining from the lobby just before (or even during) the transfer to gain a full vote. By default, this is set to 15 minutes, so anyone who's been actively playing in the round for at least that long is eligible. This is my first time touching things like the config file or any sort of subsystem more advanced than, like, the far more basic PRs I'm used to, but I was encouraged to look at this as a little project. It all appears to work well locally. --------- Signed-off-by: Matt Atlas Co-authored-by: Jaraci Co-authored-by: Matt Atlas Co-authored-by: hazelrat <83198434+hazelrat@users.noreply.github.com> --- code/controllers/configuration.dm | 4 ++ code/controllers/subsystems/vote.dm | 13 +++--- code/datums/mind.dm | 2 + code/datums/votes/_vote_datum.dm | 9 ++++ code/datums/votes/crew_transfer.dm | 16 +++++-- config/example/config.txt | 3 ++ html/changelogs/omi-redalertredalert.yml | 58 ++++++++++++++++++++++++ 7 files changed, 95 insertions(+), 10 deletions(-) create mode 100644 html/changelogs/omi-redalertredalert.yml diff --git a/code/controllers/configuration.dm b/code/controllers/configuration.dm index e6e5d8592e7..fde4e29d503 100644 --- a/code/controllers/configuration.dm +++ b/code/controllers/configuration.dm @@ -247,6 +247,7 @@ GLOBAL_LIST_EMPTY(gamemode_cache) var/allow_extra_antags = 0 var/guests_allowed = 1 var/debugparanoid = 0 + var/minimum_participation_time = 15 //Minimum time, in minutes, that a player must be active in round to count as an active participant. var/server var/banappeals @@ -531,6 +532,9 @@ GENERAL_PROTECT_DATUM(/datum/configuration) if(type == "config") switch (name) + if ("minimum_participation_time") + GLOB.config.minimum_participation_time = text2num(value) MINUTES + if ("auto_local_admin") GLOB.config.auto_local_admin = TRUE diff --git a/code/controllers/subsystems/vote.dm b/code/controllers/subsystems/vote.dm index 9256e18d765..0157553f9c7 100644 --- a/code/controllers/subsystems/vote.dm +++ b/code/controllers/subsystems/vote.dm @@ -120,21 +120,20 @@ SUBSYSTEM_DEF(vote) /datum/controller/subsystem/vote/proc/submit_single_vote(mob/voter, their_vote) if(!current_vote) return - if(!voter?.ckey) - return - if(FALSE && voter.stat == DEAD && !voter.client?.holder) //Used to be "CONFIG_GET(flag/no_dead_vote)" + var/vote_power = current_vote.get_voting_power(voter) + if(vote_power <= 0) return // If user has already voted, remove their specific vote if(voter.ckey in current_vote.choices_by_ckey) - var/their_old_vote = current_vote.choices_by_ckey[voter.ckey] - current_vote.choices[their_old_vote]-- + var/list/their_old_vote = current_vote.choices_by_ckey[voter.ckey] + current_vote.choices[their_old_vote["choice"]] -= their_old_vote["power"] else voted += voter.ckey - current_vote.choices_by_ckey[voter.ckey] = their_vote - current_vote.choices[their_vote]++ + current_vote.choices_by_ckey[voter.ckey] = list("choice" = their_vote, "power" = vote_power) + current_vote.choices[their_vote] += vote_power return TRUE diff --git a/code/datums/mind.dm b/code/datums/mind.dm index 1f1423f6e6e..e78333ef466 100644 --- a/code/datums/mind.dm +++ b/code/datums/mind.dm @@ -35,6 +35,7 @@ var/mob/living/current var/mob/living/original //This is being used now, don't remove it var/active = 0 + var/time_joined var/mob/living/admin_mob_placeholder = null @@ -538,6 +539,7 @@ mind.signature = client.prefs.signature if (client.prefs.signfont) mind.signfont = client.prefs.signfont + mind.time_joined = world.time mind.current = src //HUMAN diff --git a/code/datums/votes/_vote_datum.dm b/code/datums/votes/_vote_datum.dm index dc301a552c6..6a186bc55f6 100644 --- a/code/datums/votes/_vote_datum.dm +++ b/code/datums/votes/_vote_datum.dm @@ -113,6 +113,15 @@ return "[contains_vote_in_name ? "[capitalize(name)]" : "[capitalize(name)] vote"] started by [initiator || "Central Command"]." +/// Handles voting power application for each voter. Used in situations where some players should have more say in a vote than others. +/datum/vote/proc/get_voting_power(mob/voter) + SHOULD_CALL_PARENT(TRUE) + if(!voter?.ckey) + return 0 + if(FALSE && voter.stat == DEAD && !voter.client?.holder) //Used to be "CONFIG_GET(flag/no_dead_vote)" + return 0 + return 1 + /** * Gets the result of the vote. * diff --git a/code/datums/votes/crew_transfer.dm b/code/datums/votes/crew_transfer.dm index 54cd203ae13..fc1a9853cc3 100644 --- a/code/datums/votes/crew_transfer.dm +++ b/code/datums/votes/crew_transfer.dm @@ -12,7 +12,7 @@ GLOBAL_VAR(last_transfer_vote) . = ..() //Transfer already in progress, noone can call another vote if(SSatlas.current_map.shuttle_call_restart_timer || (GLOB.evacuation_controller.state != EVAC_IDLE)) - to_chat(by_who, SPAN_NOTICE("Transfer or evacuation are already in progress.")) + to_chat(by_who, SPAN_NOTICE("A crew transfer or evacuation is already in progress.")) return FALSE //If in lobby, roundend, setup or whatever else @@ -24,7 +24,7 @@ GLOBAL_VAR(last_transfer_vote) if(forced) return TRUE - if(GLOB.security_level >= SEC_LEVEL_RED) + if(GLOB.security_level >= SEC_LEVEL_DELTA) to_chat(by_who, "The current alert status is too high to call for a crew transfer!") return FALSE @@ -36,12 +36,22 @@ GLOBAL_VAR(last_transfer_vote) next_allowed_time = GLOB.config.transfer_timeout if(next_allowed_time > get_round_duration()) //Sorry, not the time yet - to_chat(by_who, SPAN_NOTICE("Time left until the crew transfer can be voted: [next_allowed_time - get_round_duration()]")) + to_chat(by_who, SPAN_NOTICE("Time left until a crew transfer can be voted for: [next_allowed_time - get_round_duration()]")) return FALSE else return TRUE //We did it bro, we can vote the transfer now! +// Crew transfer during red alert gives less voting power to lobby-sitters and observers. +/datum/vote/crewtransfer/get_voting_power(mob/voter) + var/vote_weight = ..() + if(vote_weight != 0 && GLOB.security_level == SEC_LEVEL_RED) + vote_weight = 0.5 + if(!isobserver(voter) && !isnewplayer(voter) && !((world.time - voter.mind.time_joined) < GLOB.config.minimum_participation_time)) + vote_weight = 1 + return vote_weight + + /datum/vote/crewtransfer/get_vote_result(list/non_voters) SHOULD_CALL_PARENT(FALSE) diff --git a/config/example/config.txt b/config/example/config.txt index a486343be07..e7d4f4c970f 100644 --- a/config/example/config.txt +++ b/config/example/config.txt @@ -608,3 +608,6 @@ CACHE_ASSETS 0 ## Tgui payloads larger than the 2kb limit for BYOND topic requests are split into roughly 1kb chunks and sent in sequence. ## This config option limits the maximum chunk count for which the server will accept a payload, default is 32 TGUI_MAX_CHUNK_COUNT 32 + +# Minimum time, in minutes, that a player must be active in round to count as an active participant. Used for determining vote power for crew transfers on red alert, for example. +MINIMUM_PARTICIPATION_TIME 15 diff --git a/html/changelogs/omi-redalertredalert.yml b/html/changelogs/omi-redalertredalert.yml new file mode 100644 index 00000000000..5479e2e80c9 --- /dev/null +++ b/html/changelogs/omi-redalertredalert.yml @@ -0,0 +1,58 @@ +################################ +# Example Changelog File +# +# Note: This file, and files beginning with ".", and files that don't end in ".yml" will not be read. If you change this file, you will look really dumb. +# +# Your changelog will be merged with a master changelog. (New stuff added only, and only on the date entry for the day it was merged.) +# When it is, any changes listed below will disappear. +# +# Valid Prefixes: +# bugfix +# - (fixes bugs) +# wip +# - (work in progress) +# qol +# - (quality of life) +# soundadd +# - (adds a sound) +# sounddel +# - (removes a sound) +# rscadd +# - (adds a feature) +# rscdel +# - (removes a feature) +# imageadd +# - (adds an image or sprite) +# imagedel +# - (removes an image or sprite) +# spellcheck +# - (fixes spelling or grammar) +# experiment +# - (experimental change) +# balance +# - (balance changes) +# code_imp +# - (misc internal code change) +# refactor +# - (refactors code) +# config +# - (makes a change to the config files) +# admin +# - (makes changes to administrator tools) +# server +# - (miscellaneous changes to server) +################################# + +# Your name. +author: Omicega + +# Optional: Remove this file after generating master changelog. Useful for PR changelogs that won't get used again. +delete-after: True + +# Any changes you've made. See valid prefix list above. +# INDENT WITH TWO SPACES. NOT TABS. SPACES. +# SCREW THIS UP AND IT WON'T WORK. +# Also, this gets changed to [] after reading. Just remove the brackets when you add new shit. +# Please surround your changes in double quotes ("). It works without them, but if you use certain characters it screws up compiling. The quotes will not show up in the changelog. +changes: + - rscadd: "Allows transfer votes during red alert, with support for weighting votes by round participation."