From 0ef5c3d049015ecbbb1d59fdf2aefd21b9742fc2 Mon Sep 17 00:00:00 2001
From: Zephyr <12817816+ZephyrTFA@users.noreply.github.com>
Date: Sun, 29 Sep 2024 20:09:08 -0400
Subject: [PATCH] Persistent Map Vote Tallies (#86788)
## About The Pull Request
Changes map votes to be based on a persistent tally count.
Tallies for maps are cached between rounds and are added to by map
votes.
When a map is chosen, and it wasn't the only valid one, the tallies for
said chosen map will be reset.
Refactors map vote handling and moves it from SSmapping to SSmap_vote.
Rock the Vote has been removed as a result of this refactor.
## Why It's Good For The Game
Makes it more likely that all maps will be played over the course of a
server instead of always being truly random.
Removes some clutter off of SSmapping.
:cl:
refactor: Map Votes are now carried over between rounds. When a map vote
is actually a contest, the winning map will have its votes reset.
/:cl:
---
code/__DEFINES/maps.dm | 2 +-
code/__DEFINES/memory_defines.dm | 2 +-
.../configuration/entries/general.dm | 24 +++
code/controllers/subsystem/blackbox.dm | 2 +-
code/controllers/subsystem/library.dm | 6 +-
code/controllers/subsystem/map_vote.dm | 160 ++++++++++++++++++
code/controllers/subsystem/mapping.dm | 145 +++-------------
.../subsystem/persistence/_persistence.dm | 4 +-
.../subsystem/persistence/engravings.dm | 4 +-
code/controllers/subsystem/statpanel.dm | 4 +-
code/controllers/subsystem/ticker.dm | 16 +-
code/controllers/subsystem/time_track.dm | 2 +-
code/controllers/subsystem/title.dm | 2 +-
code/controllers/subsystem/vote.dm | 20 ++-
code/datums/id_trim/jobs.dm | 2 +-
code/datums/votes/map_vote.dm | 62 +------
code/datums/votes/restart_vote.dm | 4 +-
code/datums/votes/rock_the_vote.dm | 62 -------
code/datums/world_topic.dm | 2 +-
.../game/machinery/computer/communications.dm | 2 +-
code/game/world.dm | 8 +-
code/modules/admin/verbs/maprotation.dm | 17 +-
.../wizard/grand_ritual/finales/armageddon.dm | 2 +-
code/modules/client/verbs/ooc.dm | 6 +
code/modules/discord/tgs_commands.dm | 2 +-
code/modules/escape_menu/details.dm | 2 +-
code/modules/photography/photos/frame.dm | 4 +-
.../mapGenerators/repair.dm | 2 +-
code/modules/shuttle/emergency.dm | 2 +-
code/modules/shuttle/shuttle.dm | 2 +-
code/modules/unit_tests/unit_test.dm | 6 +-
config/map_vote.txt | 12 ++
tgstation.dme | 2 +-
33 files changed, 291 insertions(+), 303 deletions(-)
create mode 100644 code/controllers/subsystem/map_vote.dm
delete mode 100644 code/datums/votes/rock_the_vote.dm
create mode 100644 config/map_vote.txt
diff --git a/code/__DEFINES/maps.dm b/code/__DEFINES/maps.dm
index 3c87195e990..33147916f4e 100644
--- a/code/__DEFINES/maps.dm
+++ b/code/__DEFINES/maps.dm
@@ -221,7 +221,7 @@ Always compile, always use that verb, and always make sure that it works for wha
#define CLUSTER_CHECK_ALL 30 //!Don't let anything cluster, like, at all
/// Checks the job changes in the map config for the passed change key.
-#define CHECK_MAP_JOB_CHANGE(job, change) SSmapping.config.job_changes?[job]?[change]
+#define CHECK_MAP_JOB_CHANGE(job, change) SSmapping.current_map.job_changes?[job]?[change]
///Identifiers for away mission spawnpoints
#define AWAYSTART_BEACH "AWAYSTART_BEACH"
diff --git a/code/__DEFINES/memory_defines.dm b/code/__DEFINES/memory_defines.dm
index 2b07ab6270d..f6c537f9e81 100644
--- a/code/__DEFINES/memory_defines.dm
+++ b/code/__DEFINES/memory_defines.dm
@@ -1,7 +1,7 @@
///name of the file that has all the memory strings
#define MEMORY_FILE "memories.json"
///name of the file that has all the saved engravings
-#define ENGRAVING_SAVE_FILE "data/engravings/[SSmapping.config.map_name]_engravings.json"
+#define ENGRAVING_SAVE_FILE "data/engravings/[SSmapping.current_map.map_name]_engravings.json"
///name of the file that has all the prisoner tattoos
#define PRISONER_TATTOO_SAVE_FILE "data/engravings/prisoner_tattoos.json"
///Current version of the engraving persistence json
diff --git a/code/controllers/configuration/entries/general.dm b/code/controllers/configuration/entries/general.dm
index d256ce5a584..e1ed3284441 100644
--- a/code/controllers/configuration/entries/general.dm
+++ b/code/controllers/configuration/entries/general.dm
@@ -738,3 +738,27 @@
/datum/config_entry/number/upload_limit_admin
default = 5242880
min_val = 0
+
+/// The minimum number of tallies a map vote entry can have.
+/datum/config_entry/number/map_vote_minimum_tallies
+ default = 1
+ min_val = 0
+ max_val = 50
+
+/// The flat amount all maps get by default
+/datum/config_entry/number/map_vote_flat_bonus
+ default = 5
+ min_val = 0
+ max_val = INFINITY
+
+/// The maximum number of tallies a map vote entry can have.
+/datum/config_entry/number/map_vote_maximum_tallies
+ default = 200
+ min_val = 0
+ max_val = INFINITY
+
+/// The number of tallies that are carried over between rounds.
+/datum/config_entry/number/map_vote_tally_carryover_percentage
+ default = 100
+ min_val = 0
+ max_val = 100
diff --git a/code/controllers/subsystem/blackbox.dm b/code/controllers/subsystem/blackbox.dm
index 18ba37eb137..83c666de64a 100644
--- a/code/controllers/subsystem/blackbox.dm
+++ b/code/controllers/subsystem/blackbox.dm
@@ -357,7 +357,7 @@ Versioning
"z_coord" = L.z,
"last_words" = L.last_words,
"suicide" = did_they_suicide,
- "map" = SSmapping.config.map_name,
+ "map" = SSmapping.current_map.map_name,
"internet_address" = world.internet_address || "0",
"port" = "[world.port]",
"round_id" = GLOB.round_id,
diff --git a/code/controllers/subsystem/library.dm b/code/controllers/subsystem/library.dm
index a657e442748..bfe77f70f02 100644
--- a/code/controllers/subsystem/library.dm
+++ b/code/controllers/subsystem/library.dm
@@ -26,14 +26,14 @@ SUBSYSTEM_DEF(library)
/datum/controller/subsystem/library/proc/load_shelves()
var/list/datum/callback/load_callbacks = list()
-
+
for(var/obj/structure/bookcase/case_to_load as anything in shelves_to_load)
if(!case_to_load)
stack_trace("A null bookcase somehow ended up in SSlibrary's shelves_to_load list. Did something harddel?")
continue
load_callbacks += CALLBACK(case_to_load, TYPE_PROC_REF(/obj/structure/bookcase, load_shelf))
shelves_to_load = null
-
+
//Load all of the shelves asyncronously at the same time, blocking until the last one is finished.
callback_select(load_callbacks, savereturns = FALSE)
@@ -59,6 +59,6 @@ SUBSYSTEM_DEF(library)
/datum/controller/subsystem/library/proc/prepare_library_areas()
library_areas = typesof(/area/station/service/library) - /area/station/service/library/abandoned
- var/list/additional_areas = SSmapping.config.library_areas
+ var/list/additional_areas = SSmapping.current_map.library_areas
if(additional_areas)
library_areas += additional_areas
diff --git a/code/controllers/subsystem/map_vote.dm b/code/controllers/subsystem/map_vote.dm
new file mode 100644
index 00000000000..7d0be38f920
--- /dev/null
+++ b/code/controllers/subsystem/map_vote.dm
@@ -0,0 +1,160 @@
+#define MAP_VOTE_CACHE_LOCATION "data/map_vote_cache.json"
+
+SUBSYSTEM_DEF(map_vote)
+ name = "Map Vote"
+ flags = SS_NO_FIRE
+
+ /// Has an admin specifically set a map.
+ var/admin_override = FALSE
+
+ /// Have we already done a vote.
+ var/already_voted = FALSE
+
+ /// The map that has been chosen for next round.
+ var/datum/map_config/next_map_config
+
+ /// Stores the current map vote cache, so that players can look at the current tally.
+ var/list/map_vote_cache
+
+ /// Stores the previous map vote cache, used when a map vote is reverted.
+ var/list/previous_cache
+
+ /// Stores a formatted html string of the tally counts
+ var/tally_printout = span_red("Loading...")
+
+/datum/controller/subsystem/map_vote/Initialize()
+ if(rustg_file_exists(MAP_VOTE_CACHE_LOCATION))
+ map_vote_cache = json_decode(file2text(MAP_VOTE_CACHE_LOCATION))
+ var/carryover = CONFIG_GET(number/map_vote_tally_carryover_percentage)
+ for(var/map_id in map_vote_cache)
+ map_vote_cache[map_id] = round(map_vote_cache[map_id] * (carryover / 100))
+ sanitize_cache()
+ else
+ map_vote_cache = list()
+ update_tally_printout()
+ return SS_INIT_SUCCESS
+
+/datum/controller/subsystem/map_vote/proc/write_cache()
+ rustg_file_write(json_encode(map_vote_cache), MAP_VOTE_CACHE_LOCATION)
+
+/datum/controller/subsystem/map_vote/proc/sanitize_cache()
+ var/max = CONFIG_GET(number/map_vote_maximum_tallies)
+ for(var/map_id in map_vote_cache)
+ if(!(map_id in config.maplist))
+ map_vote_cache -= map_id
+ var/count = map_vote_cache[map_id]
+ if(count > max)
+ map_vote_cache[map_id] = max
+
+/datum/controller/subsystem/map_vote/proc/send_map_vote_notice(...)
+ var/static/last_message_at
+ if(last_message_at == world.time)
+ message_admins("Call to send_map_vote_notice twice in one game tick. Yell at someone to condense messages.")
+ last_message_at = world.time
+
+ var/list/messages = args.Copy()
+ to_chat(world, span_purple(examine_block("Map Vote\n
\n[messages.Join("\n")]")))
+
+/datum/controller/subsystem/map_vote/proc/finalize_map_vote(datum/vote/map_vote/map_vote)
+ if(already_voted)
+ message_admins("Attempted to finalize a map vote after a map vote has already been finalized.")
+ return
+ already_voted = TRUE
+
+ var/flat = CONFIG_GET(number/map_vote_flat_bonus)
+ previous_cache = map_vote_cache.Copy()
+ for(var/map_id in map_vote.choices)
+ var/datum/map_config/map = config.maplist[map_id]
+ map_vote_cache[map_id] += (map_vote.choices[map_id] * map.voteweight) + flat
+ sanitize_cache()
+ write_cache()
+ update_tally_printout()
+
+ if(admin_override)
+ send_map_vote_notice("Admin Override is in effect. Map will not be changed.", "Tallies are recorded and saved.")
+ return
+
+ var/list/valid_maps = filter_cache_to_valid_maps()
+ if(!length(valid_maps))
+ send_map_vote_notice("No valid maps.")
+ return
+
+ var/winner = pick_weight(filter_cache_to_valid_maps())
+ set_next_map(config.maplist[winner])
+ send_map_vote_notice("Map Selected - [span_bold(next_map_config.map_name)]")
+
+ // do not reset tallies if only one map is even possible
+ if(length(valid_maps) > 1)
+ map_vote_cache[winner] = CONFIG_GET(number/map_vote_minimum_tallies)
+ write_cache()
+ update_tally_printout()
+
+/// Returns a list of all map options that are invalid for the current population.
+/datum/controller/subsystem/map_vote/proc/get_valid_map_vote_choices()
+ var/list/valid_maps = list()
+
+ // Fill in our default choices with all of the maps in our map config, if they are votable and not blocked.
+ var/list/maps = shuffle(global.config.maplist)
+ for(var/map in maps)
+ var/datum/map_config/possible_config = config.maplist[map]
+ if(!possible_config.votable || (possible_config.map_name in SSpersistence.blocked_maps))
+ continue
+ valid_maps += possible_config.map_name
+
+ var/filter_threshold = 0
+ if(SSticker.HasRoundStarted())
+ filter_threshold = get_active_player_count(alive_check = FALSE, afk_check = TRUE, human_check = FALSE)
+ else
+ filter_threshold = length(GLOB.clients)
+
+ for(var/map in valid_maps)
+ var/datum/map_config/possible_config = config.maplist[map]
+ if(possible_config.config_min_users > 0 && filter_threshold < possible_config.config_min_users)
+ valid_maps -= map
+
+ else if(possible_config.config_max_users > 0 && filter_threshold > possible_config.config_max_users)
+ valid_maps -= map
+
+ return valid_maps
+
+/datum/controller/subsystem/map_vote/proc/filter_cache_to_valid_maps()
+ var/connected_players = length(GLOB.player_list)
+ var/list/valid_maps = list()
+ for(var/map_id in map_vote_cache)
+ var/datum/map_config/map = config.maplist[map_id]
+ if(!map.votable)
+ continue
+ if(map.config_min_users > 0 && (connected_players < map.config_min_users))
+ continue
+ if(map.config_max_users > 0 && (connected_players > map.config_max_users))
+ continue
+ valid_maps[map_id] = map_vote_cache[map_id]
+ return valid_maps
+
+/datum/controller/subsystem/map_vote/proc/set_next_map(datum/map_config/change_to)
+ if(!change_to.MakeNextMap())
+ message_admins("Failed to set new map with next_map.json for [change_to.map_name]!")
+ return FALSE
+
+ next_map_config = change_to
+ return TRUE
+
+/datum/controller/subsystem/map_vote/proc/revert_next_map()
+ if(!next_map_config)
+ return
+ if(previous_cache)
+ map_vote_cache = previous_cache
+ previous_cache = null
+
+ already_voted = FALSE
+ admin_override = FALSE
+ send_map_vote_notice("Next map reverted. Voting re-enabled.")
+
+#undef MAP_VOTE_CACHE_LOCATION
+
+/datum/controller/subsystem/map_vote/proc/update_tally_printout()
+ var/list/data = list()
+ for(var/map_id in map_vote_cache)
+ var/datum/map_config/map = config.maplist[map_id]
+ data += "[map.map_name] - [map_vote_cache[map_id]]"
+ tally_printout = examine_block("Current Tallies\n
\n[data.Join("\n")]")
diff --git a/code/controllers/subsystem/mapping.dm b/code/controllers/subsystem/mapping.dm
index 4d9d9b142ee..05f0746690b 100644
--- a/code/controllers/subsystem/mapping.dm
+++ b/code/controllers/subsystem/mapping.dm
@@ -6,15 +6,8 @@ SUBSYSTEM_DEF(mapping)
var/list/nuke_tiles = list()
var/list/nuke_threats = list()
- var/datum/map_config/config
- var/datum/map_config/next_map_config
-
- /// Has the map for the next round been voted for already?
- var/map_voted = FALSE
- /// Has the map for the next round been deliberately chosen by an admin?
- var/map_force_chosen = FALSE
- /// Has the map vote been rocked?
- var/map_vote_rocked = FALSE
+ /// The current map config the server loaded at round start.
+ var/datum/map_config/current_map
var/list/map_templates = list()
@@ -95,20 +88,20 @@ SUBSYSTEM_DEF(mapping)
/datum/controller/subsystem/mapping/PreInit()
..()
#ifdef FORCE_MAP
- config = load_map_config(FORCE_MAP, FORCE_MAP_DIRECTORY)
+ current_map = load_map_config(FORCE_MAP, FORCE_MAP_DIRECTORY)
#else
- config = load_map_config(error_if_missing = FALSE)
+ current_map = load_map_config(error_if_missing = FALSE)
#endif
/datum/controller/subsystem/mapping/Initialize()
if(initialized)
return SS_INIT_SUCCESS
- if(config.defaulted)
- var/old_config = config
- config = global.config.defaultmap
- if(!config || config.defaulted)
- to_chat(world, span_boldannounce("Unable to load next or default map config, defaulting to MetaStation."))
- config = old_config
+ if(current_map.defaulted)
+ var/datum/map_config/old_config = current_map
+ current_map = config.defaultmap
+ if(!current_map || current_map.defaulted)
+ to_chat(world, span_boldannounce("Unable to load next or default map config, defaulting to [old_config.map_name]."))
+ current_map = old_config
plane_offset_to_true = list()
true_to_offset_planes = list()
plane_to_offset = list()
@@ -132,11 +125,11 @@ SUBSYSTEM_DEF(mapping)
#ifndef LOWMEMORYMODE
// Create space ruin levels
- while (space_levels_so_far < config.space_ruin_levels)
+ while (space_levels_so_far < current_map.space_ruin_levels)
add_new_zlevel("Ruin Area [space_levels_so_far+1]", ZTRAITS_SPACE)
++space_levels_so_far
// Create empty space levels
- while (space_levels_so_far < config.space_empty_levels + config.space_ruin_levels)
+ while (space_levels_so_far < current_map.space_empty_levels + current_map.space_ruin_levels)
empty_space = add_new_zlevel("Empty Area [space_levels_so_far+1]", list(ZTRAIT_LINKAGE = CROSSLINKED))
++space_levels_so_far
@@ -144,7 +137,7 @@ SUBSYSTEM_DEF(mapping)
if(CONFIG_GET(flag/roundstart_away))
createRandomZlevel(prob(CONFIG_GET(number/config_gateway_chance)))
- else if (SSmapping.config.load_all_away_missions) // we're likely in a local testing environment, so punch it.
+ else if (SSmapping.current_map.load_all_away_missions) // we're likely in a local testing environment, so punch it.
load_all_away_missions()
loading_ruins = TRUE
@@ -363,9 +356,7 @@ Used by the AI doomsday and the self-destruct nuke.
holodeck_templates = SSmapping.holodeck_templates
areas_in_z = SSmapping.areas_in_z
- config = SSmapping.config
- next_map_config = SSmapping.next_map_config
-
+ current_map = SSmapping.current_map
clearing_reserved_turfs = SSmapping.clearing_reserved_turfs
z_list = SSmapping.z_list
@@ -431,22 +422,22 @@ Used by the AI doomsday and the self-destruct nuke.
// load the station
station_start = world.maxz + 1
- INIT_ANNOUNCE("Loading [config.map_name]...")
- LoadGroup(FailedZs, "Station", config.map_path, config.map_file, config.traits, ZTRAITS_STATION)
+ INIT_ANNOUNCE("Loading [current_map.map_name]...")
+ LoadGroup(FailedZs, "Station", current_map.map_path, current_map.map_file, current_map.traits, ZTRAITS_STATION)
if(SSdbcore.Connect())
var/datum/db_query/query_round_map_name = SSdbcore.NewQuery({"
UPDATE [format_table_name("round")] SET map_name = :map_name WHERE id = :round_id
- "}, list("map_name" = config.map_name, "round_id" = GLOB.round_id))
+ "}, list("map_name" = current_map.map_name, "round_id" = GLOB.round_id))
query_round_map_name.Execute()
qdel(query_round_map_name)
#ifndef LOWMEMORYMODE
- if(config.minetype == "lavaland")
+ if(current_map.minetype == "lavaland")
LoadGroup(FailedZs, "Lavaland", "map_files/Mining", "Lavaland.dmm", default_traits = ZTRAITS_LAVALAND)
- else if (!isnull(config.minetype) && config.minetype != "none")
- INIT_ANNOUNCE("WARNING: An unknown minetype '[config.minetype]' was set! This is being ignored! Update the maploader code!")
+ else if (!isnull(current_map.minetype) && current_map.minetype != "none")
+ INIT_ANNOUNCE("WARNING: An unknown minetype '[current_map.minetype]' was set! This is being ignored! Update the maploader code!")
#endif
if(LAZYLEN(FailedZs)) //but seriously, unless the server's filesystem is messed up this will never happen
@@ -459,10 +450,8 @@ Used by the AI doomsday and the self-destruct nuke.
#undef INIT_ANNOUNCE
// Custom maps are removed after station loading so the map files does not persist for no reason.
- if(config.map_path == CUSTOM_MAP_PATH)
- fdel("_maps/custom/[config.map_file]")
- // And as the file is now removed set the next map to default.
- next_map_config = load_default_map_config()
+ if(current_map.map_path == CUSTOM_MAP_PATH)
+ fdel("_maps/custom/[current_map.map_file]")
/**
* Global list of AREA TYPES that are associated with the station.
@@ -494,88 +483,6 @@ GLOBAL_LIST_EMPTY(the_station_areas)
for(var/area/A as anything in GLOB.areas)
A.RunTerrainPopulation()
-/datum/controller/subsystem/mapping/proc/maprotate()
- if(map_voted || SSmapping.next_map_config) //If voted or set by other means.
- return
-
- var/players = GLOB.clients.len
- var/list/mapvotes = list()
- //count votes
- var/pmv = CONFIG_GET(flag/preference_map_voting)
- if(pmv)
- for (var/client/c in GLOB.clients)
- var/vote = c.prefs.read_preference(/datum/preference/choiced/preferred_map)
- if (!vote)
- if (global.config.defaultmap)
- mapvotes[global.config.defaultmap.map_name] += 1
- continue
- mapvotes[vote] += 1
- else
- for(var/M in global.config.maplist)
- mapvotes[M] = 1
-
- //filter votes
- for (var/map in mapvotes)
- if (!map)
- mapvotes.Remove(map)
- continue
- if (!(map in global.config.maplist))
- mapvotes.Remove(map)
- continue
- if(map in SSpersistence.blocked_maps)
- mapvotes.Remove(map)
- continue
- var/datum/map_config/VM = global.config.maplist[map]
- if (!VM)
- mapvotes.Remove(map)
- continue
- if (VM.voteweight <= 0)
- mapvotes.Remove(map)
- continue
- if (VM.config_min_users > 0 && players < VM.config_min_users)
- mapvotes.Remove(map)
- continue
- if (VM.config_max_users > 0 && players > VM.config_max_users)
- mapvotes.Remove(map)
- continue
-
- if(pmv)
- mapvotes[map] = mapvotes[map]*VM.voteweight
-
- var/pickedmap = pick_weight(mapvotes)
- if (!pickedmap)
- return
- var/datum/map_config/VM = global.config.maplist[pickedmap]
- message_admins("Randomly rotating map to [VM.map_name]")
- . = changemap(VM)
- if (. && VM.map_name != config.map_name)
- to_chat(world, span_boldannounce("Map rotation has chosen [VM.map_name] for next round!"))
-
-/datum/controller/subsystem/mapping/proc/mapvote()
- if(map_voted || SSmapping.next_map_config) //If voted or set by other means.
- return
- if(SSvote.current_vote) //Theres already a vote running, default to rotation.
- maprotate()
- return
- SSvote.initiate_vote(/datum/vote/map_vote, "automatic map rotation", forced = TRUE)
-
-/datum/controller/subsystem/mapping/proc/changemap(datum/map_config/change_to)
- if(!change_to.MakeNextMap())
- next_map_config = load_default_map_config()
- message_admins("Failed to set new map with next_map.json for [change_to.map_name]! Using default as backup!")
- return
-
- var/filter_threshold = get_active_player_count(alive_check = FALSE, afk_check = TRUE, human_check = FALSE)
- if (change_to.config_min_users > 0 && filter_threshold != 0 && filter_threshold < change_to.config_min_users)
- message_admins("[change_to.map_name] was chosen for the next map, despite there being less current players than its set minimum population range!")
- log_game("[change_to.map_name] was chosen for the next map, despite there being less current players than its set minimum population range!")
- if (change_to.config_max_users > 0 && filter_threshold > change_to.config_max_users)
- message_admins("[change_to.map_name] was chosen for the next map, despite there being more current players than its set maximum population range!")
- log_game("[change_to.map_name] was chosen for the next map, despite there being more current players than its set maximum population range!")
-
- next_map_config = change_to
- return TRUE
-
/datum/controller/subsystem/mapping/proc/preloadTemplates(path = "_maps/templates/") //see master controller setup
var/list/filelist = flist(path)
for(var/map in filelist)
@@ -590,10 +497,10 @@ GLOBAL_LIST_EMPTY(the_station_areas)
/datum/controller/subsystem/mapping/proc/preloadRuinTemplates()
// Still supporting bans by filename
var/list/banned = generateMapList("spaceruinblacklist.txt")
- if(config.minetype == "lavaland")
+ if(current_map.minetype == "lavaland")
banned += generateMapList("lavaruinblacklist.txt")
- else if(config.blacklist_file)
- banned += generateMapList(config.blacklist_file)
+ else if(current_map.blacklist_file)
+ banned += generateMapList(current_map.blacklist_file)
for(var/item in sort_list(subtypesof(/datum/map_template/ruin), GLOBAL_PROC_REF(cmp_ruincost_priority)))
var/datum/map_template/ruin/ruin_type = item
@@ -953,7 +860,7 @@ ADMIN_VERB(load_away_mission, R_FUN, "Load Away Mission", "Load a specific away
/// Returns true if the map we're playing on is on a planet
/datum/controller/subsystem/mapping/proc/is_planetary()
- return config.planetary
+ return current_map.planetary
/// For debug purposes, will add every single away mission present in a given directory.
/// You can optionally pass in a string directory to load from instead of the default.
diff --git a/code/controllers/subsystem/persistence/_persistence.dm b/code/controllers/subsystem/persistence/_persistence.dm
index 6438015d11d..d38d7fa372e 100644
--- a/code/controllers/subsystem/persistence/_persistence.dm
+++ b/code/controllers/subsystem/persistence/_persistence.dm
@@ -111,7 +111,7 @@ SUBSYSTEM_DEF(persistence)
for(var/map in config.maplist)
var/datum/map_config/VM = config.maplist[map]
var/run = 0
- if(VM.map_name == SSmapping.config.map_name)
+ if(VM.map_name == SSmapping.current_map.map_name)
run++
for(var/name in SSpersistence.saved_maps)
if(VM.map_name == name)
@@ -128,7 +128,7 @@ SUBSYSTEM_DEF(persistence)
saved_maps += mapstosave
for(var/i = mapstosave; i > 1; i--)
saved_maps[i] = saved_maps[i-1]
- saved_maps[1] = SSmapping.config.map_name
+ saved_maps[1] = SSmapping.current_map.map_name
var/json_file = file(FILE_RECENT_MAPS)
var/list/file_data = list()
file_data["data"] = saved_maps
diff --git a/code/controllers/subsystem/persistence/engravings.dm b/code/controllers/subsystem/persistence/engravings.dm
index 6808a101bb8..ad00c7909d7 100644
--- a/code/controllers/subsystem/persistence/engravings.dm
+++ b/code/controllers/subsystem/persistence/engravings.dm
@@ -14,7 +14,7 @@
saved_engravings = json["entries"]
if(!saved_engravings.len)
- log_world("Failed to load engraved messages on map [SSmapping.config.map_name]")
+ log_world("Failed to load engraved messages on map [SSmapping.current_map.map_name]")
return
var/list/viable_turfs = get_area_turfs(/area/station/maintenance, subtypes = TRUE) + get_area_turfs(/area/station/security/prison, subtypes = TRUE)
@@ -42,7 +42,7 @@
successfully_loaded_engravings++
turfs_to_pick_from -= engraved_wall
- log_world("Loaded [successfully_loaded_engravings] engraved messages on map [SSmapping.config.map_name]")
+ log_world("Loaded [successfully_loaded_engravings] engraved messages on map [SSmapping.current_map.map_name]")
///Saves all new engravings in the world.
/datum/controller/subsystem/persistence/proc/save_wall_engravings()
diff --git a/code/controllers/subsystem/statpanel.dm b/code/controllers/subsystem/statpanel.dm
index 020598a573b..cf158586ce4 100644
--- a/code/controllers/subsystem/statpanel.dm
+++ b/code/controllers/subsystem/statpanel.dm
@@ -22,9 +22,9 @@ SUBSYSTEM_DEF(statpanels)
/datum/controller/subsystem/statpanels/fire(resumed = FALSE)
if (!resumed)
num_fires++
- var/datum/map_config/cached = SSmapping.next_map_config
+ var/datum/map_config/cached = SSmap_vote.next_map_config
global_data = list(
- "Map: [SSmapping.config?.map_name || "Loading..."]",
+ "Map: [SSmapping.current_map?.map_name || "Loading..."]",
cached ? "Next Map: [cached.map_name]" : null,
"Round ID: [GLOB.round_id ? GLOB.round_id : "NULL"]",
"Server Time: [time2text(world.timeofday, "YYYY-MM-DD hh:mm:ss")]",
diff --git a/code/controllers/subsystem/ticker.dm b/code/controllers/subsystem/ticker.dm
index 800eeaa41c9..bcd33f04c98 100644
--- a/code/controllers/subsystem/ticker.dm
+++ b/code/controllers/subsystem/ticker.dm
@@ -93,12 +93,12 @@ SUBSYSTEM_DEF(ticker)
switch(L.len)
if(3) //rare+MAP+sound.ogg or MAP+rare.sound.ogg -- Rare Map-specific sounds
if(use_rare_music)
- if(L[1] == "rare" && L[2] == SSmapping.config.map_name)
+ if(L[1] == "rare" && L[2] == SSmapping.current_map.map_name)
music += S
- else if(L[2] == "rare" && L[1] == SSmapping.config.map_name)
+ else if(L[2] == "rare" && L[1] == SSmapping.current_map.map_name)
music += S
if(2) //rare+sound.ogg or MAP+sound.ogg -- Rare sounds or Map-specific sounds
- if((use_rare_music && L[1] == "rare") || (L[1] == SSmapping.config.map_name))
+ if((use_rare_music && L[1] == "rare") || (L[1] == SSmapping.current_map.map_name))
music += S
if(1) //sound.ogg -- common sound
if(L[1] == "exclude")
@@ -157,7 +157,7 @@ SUBSYSTEM_DEF(ticker)
for(var/client/C in GLOB.clients)
window_flash(C, ignorepref = TRUE) //let them know lobby has opened up.
to_chat(world, span_notice("Welcome to [station_name()]!"))
- send2chat(new /datum/tgs_message_content("New round starting on [SSmapping.config.map_name]!"), CONFIG_GET(string/channel_announce_new_game))
+ send2chat(new /datum/tgs_message_content("New round starting on [SSmapping.current_map.map_name]!"), CONFIG_GET(string/channel_announce_new_game))
current_state = GAME_STATE_PREGAME
SEND_SIGNAL(src, COMSIG_TICKER_ENTER_PREGAME)
@@ -211,7 +211,6 @@ SUBSYSTEM_DEF(ticker)
toggle_ooc(TRUE) // Turn it on
toggle_dooc(TRUE)
declare_completion(force_ending)
- check_maprotate()
Master.SetRunLevel(RUNLEVEL_POSTGAME)
/// Checks if the round should be ending, called every ticker tick
@@ -514,13 +513,6 @@ SUBSYSTEM_DEF(ticker)
queued_players -= next_in_line
queue_delay = 0
-/datum/controller/subsystem/ticker/proc/check_maprotate()
- if(!CONFIG_GET(flag/maprotation))
- return
- if(world.time - SSticker.round_start_time < 10 MINUTES) //Not forcing map rotation for very short rounds.
- return
- INVOKE_ASYNC(SSmapping, TYPE_PROC_REF(/datum/controller/subsystem/mapping/, maprotate))
-
/datum/controller/subsystem/ticker/proc/HasRoundStarted()
return current_state >= GAME_STATE_PLAYING
diff --git a/code/controllers/subsystem/time_track.dm b/code/controllers/subsystem/time_track.dm
index b3a4fe7e869..4c706fdaf6d 100644
--- a/code/controllers/subsystem/time_track.dm
+++ b/code/controllers/subsystem/time_track.dm
@@ -42,7 +42,7 @@ SUBSYSTEM_DEF(time_track)
)
/datum/controller/subsystem/time_track/Initialize()
- GLOB.perf_log = "[GLOB.log_directory]/perf-[GLOB.round_id ? GLOB.round_id : "NULL"]-[SSmapping.config?.map_name].csv"
+ GLOB.perf_log = "[GLOB.log_directory]/perf-[GLOB.round_id ? GLOB.round_id : "NULL"]-[SSmapping.current_map.map_name].csv"
world.Profile(PROFILE_RESTART, type = "sendmaps")
//Need to do the sendmaps stuff in its own file, since it works different then everything else
var/list/sendmaps_headers = list()
diff --git a/code/controllers/subsystem/title.dm b/code/controllers/subsystem/title.dm
index dbaa7ddeb48..8df9349ff03 100644
--- a/code/controllers/subsystem/title.dm
+++ b/code/controllers/subsystem/title.dm
@@ -25,7 +25,7 @@ SUBSYSTEM_DEF(title)
for(var/S in provisional_title_screens)
var/list/L = splittext(S,"+")
- if((L.len == 1 && (L[1] != "exclude" && L[1] != "blank.png")) || (L.len > 1 && ((use_rare_screens && LOWER_TEXT(L[1]) == "rare") || (LOWER_TEXT(L[1]) == LOWER_TEXT(SSmapping.config.map_name)))))
+ if((L.len == 1 && (L[1] != "exclude" && L[1] != "blank.png")) || (L.len > 1 && ((use_rare_screens && LOWER_TEXT(L[1]) == "rare") || (LOWER_TEXT(L[1]) == LOWER_TEXT(SSmapping.current_map.map_name)))))
title_screens += S
if(length(title_screens))
diff --git a/code/controllers/subsystem/vote.dm b/code/controllers/subsystem/vote.dm
index 7bed63cc36f..d0e642bd3aa 100644
--- a/code/controllers/subsystem/vote.dm
+++ b/code/controllers/subsystem/vote.dm
@@ -101,24 +101,28 @@ SUBSYSTEM_DEF(vote)
// stringify the winners to prevent potential unimplemented serialization errors.
// Perhaps this can be removed in the future and we assert that vote choices must implement serialization.
- var/final_winner_string = final_winner && "[final_winner]"
+ var/final_winner_string = (final_winner && "[final_winner]") || "NO WINNER"
var/list/winners_string = list()
- for(var/winner in winners)
- winners_string += "[winner]"
+
+ if(length(winners))
+ for(var/winner in winners)
+ winners_string += "[winner]"
+ else
+ winners_string = list("NO WINNER")
var/list/vote_log_data = list(
+ "type" = "[current_vote.type]",
"choices" = vote_choice_data,
"total" = total_votes,
"winners" = winners_string,
"final_winner" = final_winner_string,
)
- var/log_string = replacetext(to_display, "\n", "\\n") // 'keep' the newlines, but dont actually print them as newlines
- log_vote(log_string, vote_log_data)
- to_chat(world, span_infoplain(vote_font("\n[to_display]")))
+ log_vote("vote finalized", vote_log_data)
+ if(to_display)
+ to_chat(world, span_infoplain(vote_font("\n[to_display]")))
// Finally, doing any effects on vote completion
- if (final_winner) // if no one voted, or the vote cannot be won, final_winner will be null
- current_vote.finalize_vote(final_winner)
+ current_vote.finalize_vote(final_winner)
/**
* One selection per person, and the selection with the most votes wins.
diff --git a/code/datums/id_trim/jobs.dm b/code/datums/id_trim/jobs.dm
index a7bc17d7943..34432a638db 100644
--- a/code/datums/id_trim/jobs.dm
+++ b/code/datums/id_trim/jobs.dm
@@ -27,7 +27,7 @@
job = SSjob.get_job_type(job)
if(isnull(job_changes))
- job_changes = SSmapping.config.job_changes
+ job_changes = SSmapping.current_map.job_changes
if(!length(job_changes))
refresh_trim_access()
diff --git a/code/datums/votes/map_vote.dm b/code/datums/votes/map_vote.dm
index abe452ce4fe..b4f938a42e4 100644
--- a/code/datums/votes/map_vote.dm
+++ b/code/datums/votes/map_vote.dm
@@ -2,29 +2,18 @@
name = "Map"
default_message = "Vote for next round's map!"
count_method = VOTE_COUNT_METHOD_SINGLE
- winner_method = VOTE_WINNER_METHOD_WEIGHTED_RANDOM
+ winner_method = VOTE_WINNER_METHOD_NONE
display_statistics = FALSE
/datum/vote/map_vote/New()
. = ..()
-
- default_choices = list()
-
- // Fill in our default choices with all of the maps in our map config, if they are votable and not blocked.
- var/list/maps = shuffle(global.config.maplist)
- for(var/map in maps)
- var/datum/map_config/possible_config = config.maplist[map]
- if(!possible_config.votable || (possible_config.map_name in SSpersistence.blocked_maps))
- continue
-
- default_choices += possible_config.map_name
+ default_choices = SSmap_vote.get_valid_map_vote_choices()
/datum/vote/map_vote/create_vote()
. = ..()
if(!.)
return FALSE
- choices -= get_choices_invalid_for_population()
if(length(choices) == 1) // Only one choice, no need to vote. Let's just auto-rotate it to the only remaining map because it would just happen anyways.
var/datum/map_config/change_me_out = global.config.maplist[choices[1]]
finalize_vote(choices[1])// voted by not voting, very sad.
@@ -48,35 +37,16 @@
. = ..()
if(. != VOTE_AVAILABLE)
return .
- if(forced)
- return VOTE_AVAILABLE
- var/num_choices = length(default_choices - get_choices_invalid_for_population())
+
+ var/num_choices = length(default_choices)
if(num_choices <= 1)
return "There [num_choices == 1 ? "is only one map" : "are no maps"] to choose from."
- if(SSmapping.map_vote_rocked)
- return VOTE_AVAILABLE
- if(SSmapping.map_voted)
+ if(SSmap_vote.next_map_config)
return "The next map has already been selected."
return VOTE_AVAILABLE
-/// Returns a list of all map options that are invalid for the current population.
-/datum/vote/map_vote/proc/get_choices_invalid_for_population()
- var/filter_threshold = 0
- if(SSticker.HasRoundStarted())
- filter_threshold = get_active_player_count(alive_check = FALSE, afk_check = TRUE, human_check = FALSE)
- else
- filter_threshold = GLOB.clients.len
-
- var/list/invalid_choices = list()
- for(var/map in default_choices)
- var/datum/map_config/possible_config = config.maplist[map]
- if(possible_config.config_min_users > 0 && filter_threshold < possible_config.config_min_users)
- invalid_choices += map
-
- else if(possible_config.config_max_users > 0 && filter_threshold > possible_config.config_max_users)
- invalid_choices += map
-
- return invalid_choices
+/datum/vote/map_vote/get_result_text(list/all_winners, real_winner, list/non_voters)
+ return null
/datum/vote/map_vote/get_vote_result(list/non_voters)
// Even if we have default no vote off,
@@ -97,20 +67,4 @@
return ..()
/datum/vote/map_vote/finalize_vote(winning_option)
- var/datum/map_config/winning_map = global.config.maplist[winning_option]
- if(!istype(winning_map))
- CRASH("[type] wasn't passed a valid winning map choice. (Got: [winning_option || "null"] - [winning_map || "null"])")
-
- SSmapping.changemap(winning_map)
- SSmapping.map_voted = TRUE
- if(SSmapping.map_vote_rocked)
- SSmapping.map_vote_rocked = FALSE
-
-/proc/revert_map_vote()
- var/datum/map_config/override_map = SSmapping.config
- if(isnull(override_map))
- return
-
- SSmapping.changemap(override_map)
- log_game("The next map has been reset to [override_map.map_name].")
- send_to_playing_players(span_boldannounce("The next map is: [override_map.map_name]."))
+ SSmap_vote.finalize_map_vote(src)
diff --git a/code/datums/votes/restart_vote.dm b/code/datums/votes/restart_vote.dm
index 3c74d7e518e..ba0fdf78083 100644
--- a/code/datums/votes/restart_vote.dm
+++ b/code/datums/votes/restart_vote.dm
@@ -57,10 +57,10 @@
return
// If there was a previous map vote, we revert the change.
- if(!isnull(SSmapping.next_map_config))
+ if(!isnull(SSmap_vote.next_map_config))
log_game("The next map has been reset due to successful restart vote.")
send_to_playing_players(span_boldannounce("The next map has been reset due to successful restart vote."))
- revert_map_vote()
+ SSmap_vote.revert_next_map()
SSticker.force_ending = FORCE_END_ROUND
log_game("End round forced by successful restart vote.")
diff --git a/code/datums/votes/rock_the_vote.dm b/code/datums/votes/rock_the_vote.dm
deleted file mode 100644
index 6c7ac4ff257..00000000000
--- a/code/datums/votes/rock_the_vote.dm
+++ /dev/null
@@ -1,62 +0,0 @@
-#define CHOICE_TO_ROCK "Yes, re-do the map vote."
-#define CHOICE_NOT_TO_ROCK "No, keep the currently selected map."
-
-/// If a map vote is called before the emergency shuttle leaves the station, the players can call another vote to re-run the vote on the shuttle leaving.
-/datum/vote/rock_the_vote
- name = "Rock the Vote"
- override_question = "Rock the Vote?"
- contains_vote_in_name = TRUE //lol
- default_choices = list(
- CHOICE_TO_ROCK,
- CHOICE_NOT_TO_ROCK,
- )
- default_message = "Override the current map vote."
- /// The number of times we have rocked the vote thus far.
- var/rocking_votes = 0
-
-/datum/vote/rock_the_vote/toggle_votable()
- CONFIG_SET(flag/allow_rock_the_vote, !CONFIG_GET(flag/allow_rock_the_vote))
-
-/datum/vote/rock_the_vote/is_config_enabled()
- return CONFIG_GET(flag/allow_rock_the_vote)
-
-/datum/vote/rock_the_vote/can_be_initiated(forced)
- . = ..()
- if(. != VOTE_AVAILABLE)
- return .
-
- if(SSticker.current_state == GAME_STATE_FINISHED)
- return "The game is finished, no map votes can be initiated."
-
- if(rocking_votes >= CONFIG_GET(number/max_rocking_votes))
- return "The maximum number of times to rock the vote has been reached."
-
- if(SSmapping.map_vote_rocked)
- return "The vote has already been rocked! Initiate a map vote!"
-
- if(!SSmapping.map_voted)
- return "Rocking the vote is disabled because no map has been voted on yet!"
-
- if(SSmapping.map_force_chosen)
- return "Rocking the vote is disabled because an admin has forcibly set the map!"
-
- if(EMERGENCY_ESCAPED_OR_ENDGAMED && SSmapping.map_voted)
- return "The emergency shuttle has already left the station and the next map has already been chosen!"
-
- return VOTE_AVAILABLE
-
-/datum/vote/rock_the_vote/finalize_vote(winning_option)
- rocking_votes++
- if(winning_option == CHOICE_NOT_TO_ROCK)
- return
-
- if(winning_option == CHOICE_TO_ROCK)
- to_chat(world, span_boldannounce("The vote has been rocked! Players are now able to re-run the map vote once more."))
- message_admins("The players have successfully rocked the vote.")
- SSmapping.map_vote_rocked = TRUE
- return
-
- CRASH("[type] wasn't passed a valid winning choice. (Got: [winning_option || "null"])")
-
-#undef CHOICE_TO_ROCK
-#undef CHOICE_NOT_TO_ROCK
diff --git a/code/datums/world_topic.dm b/code/datums/world_topic.dm
index f2bf896bb10..a9971f6068c 100644
--- a/code/datums/world_topic.dm
+++ b/code/datums/world_topic.dm
@@ -212,7 +212,7 @@
.["admins"] = presentmins.len + afkmins.len //equivalent to the info gotten from adminwho
.["gamestate"] = SSticker.current_state
- .["map_name"] = SSmapping.config?.map_name || "Loading..."
+ .["map_name"] = SSmapping.current_map.map_name || "Loading..."
if(key_valid)
.["active_players"] = get_active_player_count()
diff --git a/code/game/machinery/computer/communications.dm b/code/game/machinery/computer/communications.dm
index 76701eca0d8..abbfb055e16 100644
--- a/code/game/machinery/computer/communications.dm
+++ b/code/game/machinery/computer/communications.dm
@@ -672,7 +672,7 @@
/// Returns TRUE if the user can buy shuttles.
/// If they cannot, returns FALSE or a string detailing why.
/obj/machinery/computer/communications/proc/can_buy_shuttles(mob/user)
- if (!SSmapping.config.allow_custom_shuttles)
+ if (!SSmapping.current_map.allow_custom_shuttles)
return FALSE
if (HAS_SILICON_ACCESS(user))
return FALSE
diff --git a/code/game/world.dm b/code/game/world.dm
index 765aa7c18ef..184b13be642 100644
--- a/code/game/world.dm
+++ b/code/game/world.dm
@@ -399,10 +399,10 @@ GLOBAL_VAR(tracy_log)
new_status += " | Shuttle: [SSshuttle.emergency.getModeStr()] [SSshuttle.emergency.getTimerStr()]"
else if(SSticker.current_state == GAME_STATE_FINISHED)
new_status += "
RESTARTING"
- if(SSmapping.config)
- new_status += "
Map: [SSmapping.config.map_path == CUSTOM_MAP_PATH ? "Uncharted Territory" : SSmapping.config.map_name]"
- if(SSmapping.next_map_config)
- new_status += "[SSmapping.config ? " | " : "
"]Next: [SSmapping.next_map_config.map_path == CUSTOM_MAP_PATH ? "Uncharted Territory" : SSmapping.next_map_config.map_name]"
+ if(SSmapping.current_map)
+ new_status += "
Map: [SSmapping.current_map.map_path == CUSTOM_MAP_PATH ? "Uncharted Territory" : SSmapping.current_map.map_name]"
+ if(SSmap_vote.next_map_config)
+ new_status += "[SSmapping.current_map ? " | " : "
"]Next: [SSmap_vote.next_map_config.map_path == CUSTOM_MAP_PATH ? "Uncharted Territory" : SSmap_vote.next_map_config.map_name]"
status = new_status
diff --git a/code/modules/admin/verbs/maprotation.dm b/code/modules/admin/verbs/maprotation.dm
index 09d6d93bee6..38d7535758f 100644
--- a/code/modules/admin/verbs/maprotation.dm
+++ b/code/modules/admin/verbs/maprotation.dm
@@ -1,12 +1,3 @@
-
-ADMIN_VERB(force_random_rotate, R_SERVER, "Trigger 'Random' Map Rotation", "Force a map vote.", ADMIN_CATEGORY_SERVER)
- var/rotate = tgui_alert(user,"Force a random map rotation to trigger?", "Rotate map?", list("Yes", "Cancel"))
- if (rotate != "Yes")
- return
- message_admins("[key_name_admin(user)] is forcing a random map rotation.")
- log_admin("[key_name(user)] is forcing a random map rotation.")
- SSmapping.maprotate()
-
ADMIN_VERB(admin_change_map, R_SERVER, "Change Map", "Set the next map.", ADMIN_CATEGORY_SERVER)
var/list/maprotatechoices = list()
for (var/map in config.maplist)
@@ -119,14 +110,14 @@ ADMIN_VERB(admin_change_map, R_SERVER, "Change Map", "Set the next map.", ADMIN_
fdel(PATH_TO_NEXT_MAP_JSON)
text2file(json_encode(json_value), PATH_TO_NEXT_MAP_JSON)
- if(SSmapping.changemap(virtual_map))
+ if(SSmap_vote.set_next_map(virtual_map))
message_admins("[key_name_admin(user)] has changed the map to [virtual_map.map_name]")
- SSmapping.map_force_chosen = TRUE
+ SSmap_vote.admin_override = TRUE
fdel("data/custom_map_json/[config_file]")
else
var/datum/map_config/virtual_map = maprotatechoices[chosenmap]
message_admins("[key_name_admin(user)] is changing the map to [virtual_map.map_name]")
log_admin("[key_name(user)] is changing the map to [virtual_map.map_name]")
- if (SSmapping.changemap(virtual_map))
+ if (SSmap_vote.set_next_map(virtual_map))
message_admins("[key_name_admin(user)] has changed the map to [virtual_map.map_name]")
- SSmapping.map_force_chosen = TRUE
+ SSmap_vote.admin_override = TRUE
diff --git a/code/modules/antagonists/wizard/grand_ritual/finales/armageddon.dm b/code/modules/antagonists/wizard/grand_ritual/finales/armageddon.dm
index 9b67aa24a53..3716a684c6a 100644
--- a/code/modules/antagonists/wizard/grand_ritual/finales/armageddon.dm
+++ b/code/modules/antagonists/wizard/grand_ritual/finales/armageddon.dm
@@ -38,7 +38,7 @@
var/static/list/doom_options = list()
if (!length(doom_options))
doom_options = list(DOOM_SINGULARITY, DOOM_TESLA)
- if (!SSmapping.config.planetary)
+ if (!SSmapping.is_planetary())
doom_options += DOOM_METEORS
switch(pick(doom_options))
diff --git a/code/modules/client/verbs/ooc.dm b/code/modules/client/verbs/ooc.dm
index 0bd1a9ea5a4..127840631d6 100644
--- a/code/modules/client/verbs/ooc.dm
+++ b/code/modules/client/verbs/ooc.dm
@@ -456,3 +456,9 @@ ADMIN_VERB(reset_ooc_color, R_FUN, "Reset Player OOC Color", "Returns player OOC
ASSERT(prefs, "User attempted to export preferences while preferences were null!") // what the fuck
prefs.savefile.export_json_to_client(usr, ckey)
+
+/client/verb/map_vote_tally_count()
+ set name = "Show Map Vote Tallies"
+ set desc = "View the current map vote tally counts."
+ set category = "Server"
+ to_chat(mob, SSmap_vote.tally_printout)
diff --git a/code/modules/discord/tgs_commands.dm b/code/modules/discord/tgs_commands.dm
index 42d6d19ba99..8c6796bc1ea 100644
--- a/code/modules/discord/tgs_commands.dm
+++ b/code/modules/discord/tgs_commands.dm
@@ -4,7 +4,7 @@
/datum/tgs_chat_command/tgscheck/Run(datum/tgs_chat_user/sender, params)
var/server = CONFIG_GET(string/server)
- return new /datum/tgs_message_content("[GLOB.round_id ? "Round #[GLOB.round_id]: " : ""][GLOB.clients.len] players on [SSmapping.config.map_name]; Round [SSticker.HasRoundStarted() ? (SSticker.IsRoundInProgress() ? "Active" : "Finishing") : "Starting"] -- [server ? server : "[world.internet_address]:[world.port]"]")
+ return new /datum/tgs_message_content("[GLOB.round_id ? "Round #[GLOB.round_id]: " : ""][GLOB.clients.len] players on [SSmapping.current_map.map_name]; Round [SSticker.HasRoundStarted() ? (SSticker.IsRoundInProgress() ? "Active" : "Finishing") : "Starting"] -- [server ? server : "[world.internet_address]:[world.port]"]")
/datum/tgs_chat_command/gameversion
name = "gameversion"
diff --git a/code/modules/escape_menu/details.dm b/code/modules/escape_menu/details.dm
index 49bd19ce97f..ab6ff05d3ea 100644
--- a/code/modules/escape_menu/details.dm
+++ b/code/modules/escape_menu/details.dm
@@ -35,7 +35,7 @@ GLOBAL_DATUM(escape_menu_details, /atom/movable/screen/escape_menu/details)
Round ID: [GLOB.round_id || "Unset"]
Round Time: [ROUND_TIME()]
- Map: [SSmapping.config?.map_name || "Loading..."]
+ Map: [SSmapping.current_map.map_name || "Loading..."]
Time Dilation: [round(SStime_track.time_dilation_current,1)]%
"}
diff --git a/code/modules/photography/photos/frame.dm b/code/modules/photography/photos/frame.dm
index 9efde283e07..989fbb596c6 100644
--- a/code/modules/photography/photos/frame.dm
+++ b/code/modules/photography/photos/frame.dm
@@ -278,6 +278,6 @@
///Generates a persistence id unique to the current map. Every bar should feel a little bit different after all.
/obj/structure/sign/picture_frame/portrait/bar/Initialize(mapload)
- if(SSmapping.config.map_path != CUSTOM_MAP_PATH) //skip adminloaded custom maps.
- persistence_id = "frame_bar_[SSmapping.config.map_name]"
+ if(SSmapping.current_map.map_path != CUSTOM_MAP_PATH) //skip adminloaded custom maps.
+ persistence_id = "frame_bar_[SSmapping.current_map.map_name]"
return ..()
diff --git a/code/modules/procedural_mapping/mapGenerators/repair.dm b/code/modules/procedural_mapping/mapGenerators/repair.dm
index 505dc36f02c..da086773591 100644
--- a/code/modules/procedural_mapping/mapGenerators/repair.dm
+++ b/code/modules/procedural_mapping/mapGenerators/repair.dm
@@ -27,7 +27,7 @@
// changed to allow Z cropping and that's a mess
var/z_offset = SSmapping.station_start
var/list/bounds
- for (var/path in SSmapping.config.GetFullMapPaths())
+ for (var/path in SSmapping.current_map.GetFullMapPaths())
var/datum/parsed_map/parsed = load_map(
file(path),
1,
diff --git a/code/modules/shuttle/emergency.dm b/code/modules/shuttle/emergency.dm
index 8e59c63cfbd..8987af97817 100644
--- a/code/modules/shuttle/emergency.dm
+++ b/code/modules/shuttle/emergency.dm
@@ -549,7 +549,7 @@
color_override = "orange",
)
INVOKE_ASYNC(SSticker, TYPE_PROC_REF(/datum/controller/subsystem/ticker, poll_hearts))
- SSmapping.mapvote() //If no map vote has been run yet, start one.
+ INVOKE_ASYNC(SSvote, TYPE_PROC_REF(/datum/controller/subsystem/vote, initiate_vote), /datum/vote/map_vote, initiator_name = "Map Rotation")
if(!is_reserved_level(z))
CRASH("Emergency shuttle did not move to transit z-level!")
diff --git a/code/modules/shuttle/shuttle.dm b/code/modules/shuttle/shuttle.dm
index 68a0a41a2e0..b5b0965ead9 100644
--- a/code/modules/shuttle/shuttle.dm
+++ b/code/modules/shuttle/shuttle.dm
@@ -264,7 +264,7 @@
/obj/docking_port/stationary/proc/load_roundstart()
if(json_key)
- var/sid = SSmapping.config.shuttles[json_key]
+ var/sid = SSmapping.current_map.shuttles[json_key]
roundstart_template = SSmapping.shuttle_templates[sid]
if(!roundstart_template)
CRASH("json_key:[json_key] value \[[sid]\] resulted in a null shuttle template for [src]")
diff --git a/code/modules/unit_tests/unit_test.dm b/code/modules/unit_tests/unit_test.dm
index 84c0fa20a67..b8b2b542f18 100644
--- a/code/modules/unit_tests/unit_test.dm
+++ b/code/modules/unit_tests/unit_test.dm
@@ -158,7 +158,7 @@ GLOBAL_VAR_INIT(focused_tests, focused_tests())
/// Logs a test message. Will use GitHub action syntax found at https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions
/datum/unit_test/proc/log_for_test(text, priority, file, line)
- var/map_name = SSmapping.config.map_name
+ var/map_name = SSmapping.current_map.map_name
// Need to escape the text to properly support newlines.
var/annotation_text = replacetext(text, "%", "%25")
@@ -177,14 +177,14 @@ GLOBAL_VAR_INIT(focused_tests, focused_tests())
GLOB.current_test = test
var/duration = REALTIMEOFDAY
- var/skip_test = (test_path in SSmapping.config.skipped_tests)
+ var/skip_test = (test_path in SSmapping.current_map.skipped_tests)
var/test_output_desc = "[test_path]"
var/message = ""
log_world("::group::[test_path]")
if(skip_test)
- log_world("[TEST_OUTPUT_YELLOW("SKIPPED")] Skipped run on map [SSmapping.config.map_name].")
+ log_world("[TEST_OUTPUT_YELLOW("SKIPPED")] Skipped run on map [SSmapping.current_map.map_name].")
else
diff --git a/config/map_vote.txt b/config/map_vote.txt
new file mode 100644
index 00000000000..edc5629df95
--- /dev/null
+++ b/config/map_vote.txt
@@ -0,0 +1,12 @@
+
+## A flat bonus to give to all maps after a map vote is concluded.
+MAP_VOTE_FLAT_BONUS 5
+
+## The minimum number of tallies a map can have for purposes of map rotation.
+MAP_VOTE_MINIMUM_TALLIES 1
+
+## The maximum number of tallies a map can have for purposes of keeping things sane.
+MAP_VOTE_MAXIMUM_TALLIES 200
+
+## The percentage of tallies that are carried over between rounds.
+MAP_VOTE_TALLY_CARRYOVER_PERCENTAGE 100
diff --git a/tgstation.dme b/tgstation.dme
index 9d5d9d705a1..c9efe12169b 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -659,6 +659,7 @@
#include "code\controllers\subsystem\lighting.dm"
#include "code\controllers\subsystem\lua.dm"
#include "code\controllers\subsystem\machines.dm"
+#include "code\controllers\subsystem\map_vote.dm"
#include "code\controllers\subsystem\mapping.dm"
#include "code\controllers\subsystem\market.dm"
#include "code\controllers\subsystem\materials.dm"
@@ -1924,7 +1925,6 @@
#include "code\datums\votes\custom_vote.dm"
#include "code\datums\votes\map_vote.dm"
#include "code\datums\votes\restart_vote.dm"
-#include "code\datums\votes\rock_the_vote.dm"
#include "code\datums\weather\weather.dm"
#include "code\datums\weather\weather_types\ash_storm.dm"
#include "code\datums\weather\weather_types\floor_is_lava.dm"