diff --git a/SQL/paradise_schema.sql b/SQL/paradise_schema.sql index 523d8eb7f5e..fe3b9b15d85 100644 --- a/SQL/paradise_schema.sql +++ b/SQL/paradise_schema.sql @@ -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`), diff --git a/SQL/updates/60-61.sql b/SQL/updates/60-61.sql new file mode 100644 index 00000000000..17dec8b4623 --- /dev/null +++ b/SQL/updates/60-61.sql @@ -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`; \ No newline at end of file diff --git a/code/__DEFINES/misc_defines.dm b/code/__DEFINES/misc_defines.dm index bd48c7f2eb0..2cd1bc62589 100644 --- a/code/__DEFINES/misc_defines.dm +++ b/code/__DEFINES/misc_defines.dm @@ -429,7 +429,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) @@ -542,11 +542,10 @@ #define LINDA_SPAWN_AIR (1<<8) #define LINDA_SPAWN_COLD (1<<9) -// Throwing these defines here for the TM to minimise conflicts #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" diff --git a/code/controllers/subsystem/SSticker.dm b/code/controllers/subsystem/SSticker.dm index 1a831df06a7..6e9194afb68 100644 --- a/code/controllers/subsystem/SSticker.dm +++ b/code/controllers/subsystem/SSticker.dm @@ -141,6 +141,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 diff --git a/code/controllers/subsystem/non_firing/SSmapping.dm b/code/controllers/subsystem/non_firing/SSmapping.dm index c2cc084f638..dd9d204298e 100644 --- a/code/controllers/subsystem/non_firing/SSmapping.dm +++ b/code/controllers/subsystem/non_firing/SSmapping.dm @@ -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 @@ -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 /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() diff --git a/code/controllers/subsystem/non_firing/SSmaprotate.dm b/code/controllers/subsystem/non_firing/SSmaprotate.dm index 34040c84a92..1086890d75b 100644 --- a/code/controllers/subsystem/non_firing/SSmaprotate.dm +++ b/code/controllers/subsystem/non_firing/SSmaprotate.dm @@ -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 @@ -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 @@ -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 = "" + 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] | [possible_next_map]: [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, "Map for next round: [SSmapping.next_map.fluff_name] ([SSmapping.next_map.technical_name])") diff --git a/code/modules/client/login_processing/10-load_preferences.dm b/code/modules/client/login_processing/10-load_preferences.dm index 0ef03d03c4c..0b2c2dc4afd 100644 --- a/code/modules/client/login_processing/10-load_preferences.dm +++ b/code/modules/client/login_processing/10-load_preferences.dm @@ -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 diff --git a/code/modules/client/preference/link_processing.dm b/code/modules/client/preference/link_processing.dm index 4bf35546b5c..45f88438793 100644 --- a/code/modules/client/preference/link_processing.dm +++ b/code/modules/client/preference/link_processing.dm @@ -924,7 +924,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) + 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 diff --git a/code/modules/client/preference/preferences.dm b/code/modules/client/preference/preferences.dm index ca058c397a9..42fed59b863 100644 --- a/code/modules/client/preference/preferences.dm +++ b/code/modules/client/preference/preferences.dm @@ -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 @@ -170,7 +172,7 @@ GLOBAL_LIST_INIT(special_role_times, list( dat += "
"
dat += "Special Role Settings" if(jobban_isbanned(user, ROLE_SYNDICATE)) @@ -504,12 +506,14 @@ GLOBAL_LIST_INIT(special_role_times, list( else var/is_special = (i in src.be_special) dat += "Be [capitalize(i)]:[(is_special) ? "Yes" : "No"]" - dat += " Total Playtime:" if(!GLOB.configuration.jobs.enable_exp_tracking) dat += "Playtime tracking is not enabled." else dat += "Your [EXP_TYPE_CREW] playtime is [user.client.get_exp_type(EXP_TYPE_CREW)]" + dat += " | "
+ dat += "Map Settings" + dat += " - Choose your map preferences: Click here!" dat += " |