#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
var/winner_amount = 0
for(var/map in valid_maps)
if(!winner_amount)
winner = map
winner_amount = map_vote_cache[map]
continue
if(map_vote_cache[map] <= winner_amount)
continue
winner = map
winner_amount = map_vote_cache[map]
ASSERT(winner, "No winner found in map vote.")
set_next_map(config.maplist[winner])
var/list/messages = list("Map Selected - [span_bold(next_map_config.map_name)]")
messages += "Tallies at the time of selection:"
messages += tally_printout
// 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()
else
messages += "Only one map was possible, tallies were not reset."
send_map_vote_notice(arglist(messages))
/// 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")]")