Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Impliments a new voting system for weekends and wednesday: Hybrid FPTP weighted random from preferences #26879

Merged
1 change: 1 addition & 0 deletions SQL/paradise_schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,7 @@ CREATE TABLE `player` (
`server_region` VARCHAR(32) NULL DEFAULT NULL COLLATE 'utf8mb4_general_ci',
`muted_adminsounds_ckeys` MEDIUMTEXT NULL DEFAULT NULL COLLATE 'utf8mb4_general_ci',
`viewrange` VARCHAR(5) NOT NULL DEFAULT '19x15' COLLATE 'utf8mb4_general_ci',
`map_vote_pref_json` MEDIUMTEXT NULL DEFAULT NULL COLLATE 'utf8mb4_general_ci',
PRIMARY KEY (`id`),
UNIQUE KEY `ckey` (`ckey`),
KEY `lastseen` (`lastseen`),
Expand Down
6 changes: 6 additions & 0 deletions SQL/updates/60-61.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Updates the DB from 60 to 61 ~Qwertytoforty
# Makes a table for map picks

# Adds the table for it.
ALTER TABLE `player`
ADD COLUMN `map_vote_pref_json` MEDIUMTEXT NULL DEFAULT NULL AFTER `viewrange`;
6 changes: 3 additions & 3 deletions code/__DEFINES/misc_defines.dm
Original file line number Diff line number Diff line change
Expand Up @@ -423,7 +423,7 @@
#define INVESTIGATE_HOTMIC "hotmic"

// The SQL version required by this version of the code
#define SQL_VERSION 60
#define SQL_VERSION 61

// Vending machine stuff
#define CAT_NORMAL (1<<0)
Expand Down Expand Up @@ -536,11 +536,11 @@
#define LINDA_SPAWN_AIR (1<<8)
#define LINDA_SPAWN_COLD (1<<9)

// Throwing these defines here for the TM to minimise conflicts
// Throwing these defines here for the TM to minimise conflicts // How is that tm going?
Qwertytoforty marked this conversation as resolved.
Show resolved Hide resolved
#define MAPROTATION_MODE_NORMAL_VOTE "Vote"
#define MAPROTATION_MODE_NO_DUPLICATES "Nodupes"
#define MAPROTATION_MODE_FULL_RANDOM "Random"

#define MAPROTATION_MODE_HYBRID_FPTP_NO_DUPLICATES "FPTP"

/// Send to the primary Discord webhook
#define DISCORD_WEBHOOK_PRIMARY "PRIMARY"
Expand Down
3 changes: 3 additions & 0 deletions code/controllers/subsystem/SSticker.dm
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,9 @@ SUBSYSTEM_DEF(ticker)
// Start a map vote IF
// - Map rotate doesnt have a mode for today and map voting is enabled
// - Map rotate has a mode for the day and it ISNT full random
if(SSmaprotate.setup_done && (SSmaprotate.rotation_mode == MAPROTATION_MODE_HYBRID_FPTP_NO_DUPLICATES))
SSmaprotate.decide_next_map()
return
if(((!SSmaprotate.setup_done) && GLOB.configuration.vote.enable_map_voting) || (SSmaprotate.setup_done && (SSmaprotate.rotation_mode != MAPROTATION_MODE_FULL_RANDOM)))
SSvote.start_vote(new /datum/vote/map)
else
Expand Down
17 changes: 17 additions & 0 deletions code/controllers/subsystem/non_firing/SSmapping.dm
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ SUBSYSTEM_DEF(mapping)
var/datum/map/map_datum
/// What map will be used next round
var/datum/map/next_map
/// What map was used last round?
var/datum/map/last_map
/// List of all areas that can be accessed via IC means
var/list/teleportlocs
/// List of all areas that can be accessed via IC and OOC means
Expand Down Expand Up @@ -40,11 +42,26 @@ SUBSYSTEM_DEF(mapping)
fdel("data/next_map.txt") // Remove to avoid the same map existing forever
else
map_datum = new /datum/map/boxstation // Assume cyberiad if non-existent
if(fexists("data/last_map.txt"))
var/list/lines = file2list("data/last_map.txt")
// Check its valid
try
last_map = text2path(lines[1])
last_map = new last_map
catch
last_map = new /datum/map/cerestation // Assume cerestation if non-existent
fdel("data/last_map.txt") // Remove to avoid the same map existing forever
else
last_map = new /datum/map/cerestation // Assume cerestation if non-existent
DGamerL marked this conversation as resolved.
Show resolved Hide resolved

/datum/controller/subsystem/mapping/Shutdown()
if(next_map) // Save map for next round
var/F = file("data/next_map.txt")
F << next_map.type
if(map_datum) // Save which map was this round as the last map
var/F = file("data/last_map.txt")
F << map_datum.type


/datum/controller/subsystem/mapping/Initialize()
environments = list()
Expand Down
42 changes: 40 additions & 2 deletions code/controllers/subsystem/non_firing/SSmaprotate.dm
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ SUBSYSTEM_DEF(maprotate)
rotation_descs[MAPROTATION_MODE_NORMAL_VOTE] = "there is normal map voting."
rotation_descs[MAPROTATION_MODE_NO_DUPLICATES] = "map votes will not include the current map."
rotation_descs[MAPROTATION_MODE_FULL_RANDOM] = "the map for next round is randomised."
rotation_descs[MAPROTATION_MODE_HYBRID_FPTP_NO_DUPLICATES] = "the map for next round is weighted off your preferences and past maps"

// Yes. I am using the DB server to get a numerical weekday
// 0 = Monday
Expand Down Expand Up @@ -60,7 +61,7 @@ SUBSYSTEM_DEF(maprotate)
if(dindex_str in GLOB.configuration.vote.map_vote_day_types)
var/vote_type = GLOB.configuration.vote.map_vote_day_types[dindex_str]
// We have an index, but is it valid
if(vote_type in list(MAPROTATION_MODE_NORMAL_VOTE, MAPROTATION_MODE_NO_DUPLICATES, MAPROTATION_MODE_FULL_RANDOM))
if(vote_type in list(MAPROTATION_MODE_NORMAL_VOTE, MAPROTATION_MODE_NO_DUPLICATES, MAPROTATION_MODE_FULL_RANDOM, MAPROTATION_MODE_HYBRID_FPTP_NO_DUPLICATES))
log_startup_progress("It is [days[day_index]], which means [rotation_descs[vote_type]]")
rotation_mode = vote_type
setup_done = TRUE
Expand All @@ -73,4 +74,41 @@ SUBSYSTEM_DEF(maprotate)
else
log_startup_progress("There is no special rotation defined for this day")


/datum/controller/subsystem/maprotate/proc/decide_next_map()
var/list/potential_maps = list()
for(var/x in subtypesof(/datum/map))
var/datum/map/M = x
if(!initial(M.voteable))
continue
// And of course, if the current map is the same
if(istype(SSmapping.map_datum, M))
continue
// Or the map from last round
if(istype(SSmapping.last_map, M))
continue
potential_maps[M] = 0
// We now have 3 maps. We then pick your highest priority map in the list. Does this mean votes 4 and 5 don't matter? Yeah, with this current system only your top 3 votes will ever be used. 4 and 5 are good info to know however!
for(var/client/user_client in GLOB.clients)
for(var/preferred_text as anything in user_client.prefs.map_vote_pref_json) // stored as a list of text
var/datum/map/preferred_map = text2path(preferred_text)
if(isnull(preferred_map)) // this map datum doesn't exist!!
continue
if(preferred_map in potential_maps)
potential_maps[preferred_map]++
break
var/list/returned_text = list("Map Preference Vote Results:")
var/list/pickable_maps = list()
for(var/possible_next_map in potential_maps)
var/votes = potential_maps[possible_next_map]
var/percentage_text = ""
Qwertytoforty marked this conversation as resolved.
Show resolved Hide resolved
if(votes > 0)
var/actual_percentage = round((votes / length(GLOB.clients)) * 100, 0.1) // Note: Some players will not have this filled out. Too bad.
percentage_text += "[add_lspace(actual_percentage, 5 - length("[actual_percentage]"))]%"
pickable_maps[possible_next_map] = votes
returned_text += "[percentage_text] | <b>[possible_next_map]</b>: [potential_maps[possible_next_map]]"
if(!length(pickable_maps))
pickable_maps = potential_maps // potential_maps should probably be renamed to `available_maps` or `voteable_maps`
var/datum/map/winner = pickweight(pickable_maps) // Even if no one votes, pickweight will pick from them evenly. This means a map with zero votes *can* be chosen
to_chat(world, "[returned_text.Join("\n")]")
SSmapping.next_map = new winner
to_chat(world, "<span class='interface'>Map for next round: [SSmapping.next_map.fluff_name] ([SSmapping.next_map.technical_name])</span>")
3 changes: 2 additions & 1 deletion code/modules/client/login_processing/10-load_preferences.dm
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@
keybindings,
server_region,
muted_adminsounds_ckeys,
viewrange
viewrange,
map_vote_pref_json
FROM player
WHERE ckey=:ckey"}, list(
"ckey" = C.ckey
Expand Down
13 changes: 13 additions & 0 deletions code/modules/client/preference/link_processing.dm
Original file line number Diff line number Diff line change
Expand Up @@ -941,7 +941,20 @@
if(ishuman(usr)) //mid-round preference changes, for aesthetics
var/mob/living/carbon/human/H = usr
H.remake_hud()
if("map_pick")
var/list/potential_maps = list()
for(var/x in subtypesof(/datum/map))
var/datum/map/M = x
if(!initial(M.voteable))
continue
potential_maps += M

var/list/output = tgui_input_ranked_list(usr, "Pick a map, in order of most wanted to least. This will go on until there are no more maps left.", "Maps", potential_maps)
Qwertytoforty marked this conversation as resolved.
Show resolved Hide resolved
if(!length(output))
return
map_vote_pref_json = list() //Clear it out
for(var/index in 1 to length(output)) //This is an associated list to make blackbox tracking easier
map_vote_pref_json[output[index]] = index
if("tgui")
toggles2 ^= PREFTOGGLE_2_FANCYUI

Expand Down
10 changes: 7 additions & 3 deletions code/modules/client/preference/preferences.dm
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,8 @@ GLOBAL_LIST_INIT(special_role_times, list(
var/list/admin_sound_ckey_ignore = list()
/// View range preference for this client
var/viewrange = DEFAULT_CLIENT_VIEWSIZE
/// Map preferences for the first past the post system
var/list/map_vote_pref_json = list()

/datum/preferences/New(client/C, datum/db_query/Q) // Process our query
parent = C
Expand Down Expand Up @@ -170,7 +172,7 @@ GLOBAL_LIST_INIT(special_role_times, list(
dat += "<center>"
dat += "<a href='byond://?_src_=prefs;preference=tab;tab=[TAB_CHAR]' [current_tab == TAB_CHAR ? "class='linkOn'" : ""]>Character Settings</a>"
dat += "<a href='byond://?_src_=prefs;preference=tab;tab=[TAB_GAME]' [current_tab == TAB_GAME ? "class='linkOn'" : ""]>Game Preferences</a>"
dat += "<a href='byond://?_src_=prefs;preference=tab;tab=[TAB_ANTAG]' [current_tab == TAB_ANTAG ? "class='linkOn'" : ""]>Antagonists</a>"
dat += "<a href='byond://?_src_=prefs;preference=tab;tab=[TAB_ANTAG]' [current_tab == TAB_ANTAG ? "class='linkOn'" : ""]>Antagonists and Maps</a>"
dat += "<a href='byond://?_src_=prefs;preference=tab;tab=[TAB_GEAR]' [current_tab == TAB_GEAR ? "class='linkOn'" : ""]>Loadout</a>"
dat += "<a href='byond://?_src_=prefs;preference=tab;tab=[TAB_KEYS]' [current_tab == TAB_KEYS ? "class='linkOn'" : ""]>Key Bindings</a>"
dat += "<a href='byond://?_src_=prefs;preference=tab;tab=[TAB_TOGGLES]' [current_tab == TAB_TOGGLES ? "class='linkOn'" : ""]>General Preferences</a>"
Expand Down Expand Up @@ -482,7 +484,7 @@ GLOBAL_LIST_INIT(special_role_times, list(
dat += "<b> - TGUI Say Theme:</b> <a href='byond://?_src_=prefs;preference=tgui_say_light_mode'>[(toggles2 & PREFTOGGLE_2_ENABLE_TGUI_SAY_LIGHT_MODE) ? "Light" : "Dark"]</a><br>"
dat += "</td></tr></table>"

if(TAB_ANTAG) // Antagonist's Preferences
if(TAB_ANTAG) // Antagonist's Preferences (and maps)
dat += "<table><tr><td width='340px' height='300px' valign='top'>"
dat += "<h2>Special Role Settings</h2>"
if(jobban_isbanned(user, ROLE_SYNDICATE))
Expand All @@ -504,12 +506,14 @@ GLOBAL_LIST_INIT(special_role_times, list(
else
var/is_special = (i in src.be_special)
dat += "<b>Be [capitalize(i)]:</b><a class=[is_special ? "green" : "red"] href='byond://?_src_=prefs;preference=be_special;role=[i]'><b>[(is_special) ? "Yes" : "No"]</b></a><br>"

dat += "<h2>Total Playtime:</h2>"
if(!GLOB.configuration.jobs.enable_exp_tracking)
dat += "<span class='warning'>Playtime tracking is not enabled.</span>"
else
dat += "<b>Your [EXP_TYPE_CREW] playtime is [user.client.get_exp_type(EXP_TYPE_CREW)]</b><br>"
dat += "</td><td width='405px' height='300px' valign='top'>"
dat += "<h2>Map Settings</h2>"
dat += " - <b>Choose your map preferences:</b> <a href='byond://?_src_=prefs;preference=map_pick'><b>Click here!</b></a><br>"
dat += "</td></tr></table>"

if(TAB_GEAR)
Expand Down
14 changes: 11 additions & 3 deletions code/modules/client/preference/preferences_mysql.dm
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

//general preferences
var/raw_muted_admins
var/raw_fptp
while(query.NextRow())
ooccolor = query.item[1]
UI_style = query.item[2]
Expand Down Expand Up @@ -32,6 +33,7 @@
server_region = query.item[25]
raw_muted_admins = query.item[26]
viewrange = query.item[27]
raw_fptp = query.item[28]

lastchangelog_2 = lastchangelog // Clone please

Expand Down Expand Up @@ -60,7 +62,11 @@
admin_sound_ckey_ignore = json_decode(raw_muted_admins)
catch
admin_sound_ckey_ignore = list() // Invalid JSON, handle safely please

if(length(raw_fptp))
try
map_vote_pref_json = json_decode(raw_fptp)
catch
map_vote_pref_json = list()
// Sanitize the region
if(!(server_region in GLOB.configuration.system.region_map))
server_region = null // This region doesnt exist anymore
Expand Down Expand Up @@ -104,7 +110,8 @@
keybindings=:keybindings,
server_region=:server_region,
muted_adminsounds_ckeys=:muted_adminsounds_ckeys,
viewrange=:viewrange
viewrange=:viewrange,
map_vote_pref_json=:map_vote_pref_json
WHERE ckey=:ckey"}, list(
// OH GOD THE PARAMETERS
"ooccolour" = ooccolor,
Expand Down Expand Up @@ -133,7 +140,8 @@
"ckey" = C.ckey,
"server_region" = server_region,
"muted_adminsounds_ckeys" = json_encode(admin_sound_ckey_ignore),
"viewrange" = viewrange
"viewrange" = viewrange,
"map_vote_pref_json" = json_encode(map_vote_pref_json)
))

if(!query.warn_execute())
Expand Down
9 changes: 5 additions & 4 deletions config/example/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ ipc_screens = [
# Enable/disable the database on a whole
sql_enabled = false
# SQL version. If this is a mismatch, round start will be delayed
sql_version = 60
sql_version = 61
# SQL server address. Can be an IP or DNS name
sql_address = "127.0.0.1"
# SQL server port
Expand Down Expand Up @@ -873,15 +873,16 @@ non_repeating_maps = true
# - "Vote" = regular voting
# - "Nodupes" = voting wont include current map
# - "Random" = no vote, map is random
# - "FPTP" = A vote that excludes the map from the current round, and last round, then uses a weighted pick on the remaining maps, from preferences.
# If you dont enter one correctly, it will whine on startup
map_vote_day_types = [
{ day_number = 1, rotation_type = "Vote" }, # Monday
{ day_number = 2, rotation_type = "Random" }, # Tuesday
{ day_number = 3, rotation_type = "Vote" }, # Wednesday
{ day_number = 3, rotation_type = "FPTP" }, # Wednesday
{ day_number = 4, rotation_type = "Random" }, # Thursday
{ day_number = 5, rotation_type = "Vote" }, # Friday
{ day_number = 6, rotation_type = "Nodupes" }, # Saturday
{ day_number = 7, rotation_type = "Nodupes" }, # Sunday
{ day_number = 6, rotation_type = "FPTP" }, # Saturday
{ day_number = 7, rotation_type = "FPTP" }, # Sunday
]


Expand Down