SQL-Based Player Ranks and Player Ranks Subsystem (#23099)

* Initial draft, most of the stuff is implemented, not really tested or cleaned up yet

* Refactors the handling into a datum-based approach, for more abstraction

* Added migration, properly hooked up the verb to the new system

* Removed some more dead code

* Fixes some missing stuff from the .dme because VSC is stupid sometimes with merge conflicts

* Fixes the schema

* Wow I was really getting tired huh

* A fool, I say, a fool!

* I hate lists of lists I hate lists of lists I hate lists of lists

* I somehow missed this one twice. TWICE!

* This won't actually work if it's defaulting to true, lmao

* Makes it not log stuff if the adding or removing of players is unsuccessful

* Adds a way to update all of the unlock_contents for preferences datums once the donator list is initialized

* Runs update_prefs_unlock_content on mob Login() (hopefully this helps)

* Fixes the bajillion of runtimes caused by my dumb ass trying to make some client calls on ckeys

* Man I was really tired that day

* I had this ready for three hours and forgot to push it
This commit is contained in:
GoldenAlpharex
2023-08-29 10:12:01 -04:00
committed by GitHub
parent e1adeeb913
commit 1b17b304f2
27 changed files with 1115 additions and 284 deletions

82
SQL/skyrat_schema.sql Normal file
View File

@@ -0,0 +1,82 @@
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8 */;
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE='+00:00' */;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
--
-- Table structure for table `player_rank`.
--
DROP TABLE IF EXISTS `player_rank`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `player_rank` (
`ckey` VARCHAR(32) NOT NULL,
`rank` VARCHAR(12) NOT NULL,
`admin_ckey` VARCHAR(32) NOT NULL,
`date_added` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`last_modified` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`deleted` BOOLEAN NOT NULL DEFAULT FALSE,
PRIMARY KEY (`ckey`, `rank`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `player_rank_log`.
--
DROP TABLE IF EXISTS `player_rank_log`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `player_rank_log` (
`id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`ckey` VARCHAR(32) NOT NULL,
`rank` VARCHAR(12) NOT NULL,
`admin_ckey` VARCHAR(32) NOT NULL,
`action` ENUM('ADDED', 'REMOVED') NOT NULL DEFAULT 'ADDED',
`date` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Trigger structure for trigger `log_player_rank_additions`.
--
DROP TRIGGER IF EXISTS `log_player_rank_additions`;
CREATE TRIGGER `log_player_rank_additions`
AFTER INSERT ON `player_rank`
FOR EACH ROW
INSERT INTO player_rank_log (ckey, rank, admin_ckey, `action`) VALUES (NEW.ckey, NEW.rank, NEW.admin_ckey, 'ADDED');
--
-- Trigger structure for trigger `log_player_rank_changes`.
--
DROP TRIGGER IF EXISTS `log_player_rank_changes`;
DELIMITER //
CREATE TRIGGER `log_player_rank_changes`
AFTER UPDATE ON `player_rank`
FOR EACH ROW
BEGIN
IF NEW.deleted = 1 THEN
INSERT INTO player_rank_log (ckey, rank, admin_ckey, `action`) VALUES (NEW.ckey, NEW.rank, NEW.admin_ckey, 'REMOVED');
ELSE
INSERT INTO player_rank_log (ckey, rank, admin_ckey, `action`) VALUES (NEW.ckey, NEW.rank, NEW.admin_ckey, 'ADDED');
END IF;
END; //
DELIMITER ;
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;

View File

@@ -134,6 +134,7 @@
#define INIT_ORDER_BLACKBOX 94
#define INIT_ORDER_SERVER_MAINT 93
#define INIT_ORDER_INPUT 85
#define INIT_ORDER_PLAYER_RANKS 84 // SKYRAT EDIT - Player Ranks Subsystem
#define INIT_ORDER_SOUNDS 83
#define INIT_ORDER_INSTRUMENTS 82
#define INIT_ORDER_GREYSCALE 81

View File

@@ -940,7 +940,7 @@ SUBSYSTEM_DEF(job)
return JOB_UNAVAILABLE_AGE
//SKYRAT EDIT ADDITION BEGIN - CUSTOMIZATION
if(possible_job.veteran_only && !is_veteran_player(player.client))
if(possible_job.veteran_only && !SSplayer_ranks.is_veteran(player.client))
JobDebug("[debug_prefix] Error: [get_job_unavailable_error_message(JOB_NOT_VETERAN)], Player: [player][add_job_to_log ? ", Job: [possible_job]" : ""]")
return JOB_NOT_VETERAN

View File

@@ -73,7 +73,6 @@ SUBSYSTEM_DEF(ticker)
var/discord_alerted = FALSE //SKYRAT EDIT - DISCORD PING SPAM PREVENTION
/datum/controller/subsystem/ticker/Initialize()
load_mentors() // SKYRAT EDIT ADDITION - MENTORS STOPPED LOADING AUTOMATICALLY DUE TO RECENT TG CHANGES
var/list/byond_sound_formats = list(
"mid" = TRUE,
"midi" = TRUE,

View File

@@ -265,7 +265,8 @@ GLOBAL_PROTECT(admin_verbs_possess)
/// SKYRAT EDIT BEGIN - Player Rank Manager - ORIGINAL: GLOBAL_LIST_INIT(admin_verbs_permissions, list(/client/proc/edit_admin_permissions))
GLOBAL_LIST_INIT(admin_verbs_permissions, list(
/client/proc/edit_admin_permissions,
/client/proc/manage_player_ranks
/client/proc/manage_player_ranks,
/client/proc/migrate_player_ranks,
))
/// SKYRAT EDIT END
GLOBAL_PROTECT(admin_verbs_permissions)

View File

@@ -99,7 +99,7 @@
/datum/preference_middleware/jobs/get_ui_static_data(mob/user)
var/list/data = list()
// SKYRAT EDIT
if(is_veteran_player(user.client))
if(SSplayer_ranks.is_veteran(user.client))
data["is_veteran"] = TRUE
// SKYRAT EDIT END
var/list/required_job_playtime = get_required_job_playtime(user)

View File

@@ -56,7 +56,7 @@
//SKYRAT EDIT ADDITION
var/list/quirks = SSquirks.get_quirks()
var/datum/quirk/quirk = quirks[quirk_name]
if(initial(quirk.veteran_only) && !is_veteran_player(preferences?.parent))
if(initial(quirk.veteran_only) && !SSplayer_ranks.is_veteran(preferences?.parent))
return FALSE
//SKYRAT EDIT END
@@ -96,7 +96,7 @@
//SKYRAT EDIT ADDITION
var/list/quirks = SSquirks.get_quirks()
var/datum/quirk/quirk_datum = quirks[quirk]
if(initial(quirk_datum.veteran_only) && !is_veteran_player(preferences?.parent))
if(initial(quirk_datum.veteran_only) && !SSplayer_ranks.is_veteran(preferences?.parent))
preferences.all_quirks -= quirk
continue
//SKYRAT EDIT END

View File

@@ -165,7 +165,7 @@
return JOB_UNAVAILABLE_LANGUAGE
if(job.has_banned_quirk(client.prefs))
return JOB_UNAVAILABLE_QUIRK
if(job.veteran_only && !is_veteran_player(client))
if(job.veteran_only && !SSplayer_ranks.is_veteran(client))
return JOB_NOT_VETERAN
if(job.has_banned_species(client.prefs))
return JOB_UNAVAILABLE_SPECIES

View File

@@ -25,11 +25,6 @@ PLAYER_SOFT_CAP 0
## The non-IC name we send to OOC and adminsay.
#CROSS_SERVER_NAME
## Comment this out if you want to use the SQL based mentor system, the legacy system uses mentors.txt.
## You need to set up your database to use the SQL based system.
## This flag is automatically enabled if SQL_ENABLED isn't
## But currently the SQL system is not implemented and it'll read from mentors.txt nonetheless
MENTOR_LEGACY_SYSTEM
ALERT_AMBER_UPTO A major security emergency has developed. Security staff may have weapons unholstered at all times. Random searches are allowed and advised.
ALERT_AMBER_DOWNTO A major security emergency is still underway. Non-security personnel are required to obey all relevant instructions from security staff.
@@ -141,3 +136,10 @@ SIZE_COLLAR_MINIMUM 15
## Don't ever set this to 0, just disable MIN_FLAVOR_TEXT
#FLAVOR_TEXT_CHARACTER_REQUIREMENT 150
## Comment these out if you want to use the SQL-based player rank system, the
## legacy system uses the .txt files in the data folder instead.
## You need to set up your database to use the SQL-based system.
## These flags are automatically enabled if SQL_ENABLED isn't.
DONATOR_LEGACY_SYSTEM
MENTOR_LEGACY_SYSTEM
VETERAN_LEGACY_SYSTEM

View File

@@ -49,3 +49,18 @@
// NEVER set this value to 0!!
/datum/config_entry/number/flavor_text_character_requirement
default = 150
/// Defines whether or not mentors can see ckeys alongside mobnames.
/datum/config_entry/flag/mentors_mobname_only
/// Defines whether the server uses the legacy donator system with donators.txt or the SQL system.
/datum/config_entry/flag/donator_legacy_system
protection = CONFIG_ENTRY_LOCKED
/// Defines whether the server uses the legacy mentor system with mentors.txt or the SQL system.
/datum/config_entry/flag/mentor_legacy_system
protection = CONFIG_ENTRY_LOCKED
/// Defines whether the server uses the legacy veteran system with veteran_players.txt or the SQL system.
/datum/config_entry/flag/veteran_legacy_system
protection = CONFIG_ENTRY_LOCKED

View File

@@ -51,6 +51,6 @@
/datum/preference/text/headshot/is_accessible(datum/preferences/preferences)
if(isnull(usr)) // Joining at roundstart
return ..()
if(!is_veteran_player(usr?.client) && !GLOB.donator_list[usr?.ckey] && !is_admin(usr?.client))
if(!SSplayer_ranks.is_veteran(usr?.client) && !GLOB.donator_list[usr?.ckey] && !is_admin(usr?.client))
return FALSE
return ..()

View File

@@ -0,0 +1,10 @@
/mob/Login()
. = ..()
if(!.)
return FALSE
if(SSplayer_ranks.initialized)
SSplayer_ranks.update_prefs_unlock_content(client?.prefs)
return TRUE

View File

@@ -1,136 +0,0 @@
/// The defines for the appropriate config files
#define SKYRAT_DONATOR_CONFIG_FILE "[global.config.directory]/skyrat/donators.txt"
#define SKYRAT_MENTOR_CONFIG_FILE "[global.config.directory]/skyrat/mentors.txt"
#define SKYRAT_VETERAN_CONFIG_FILE "[global.config.directory]/skyrat/veteran_players.txt"
/// The list of the available special player ranks
#define SKYRAT_PLAYER_RANKS list("Donator", "Mentor", "Veteran")
/client/proc/manage_player_ranks()
set category = "Admin"
set name = "Manage Player Ranks"
set desc = "Manage who has the special player ranks while the server is running."
if(!check_rights(R_PERMISSIONS))
return
usr.client?.holder.manage_player_ranks()
/// Proc for admins to change people's "player" ranks (donator, mentor, veteran, etc.)
/datum/admins/proc/manage_player_ranks()
if(!check_rights(R_PERMISSIONS))
return
var/choice = tgui_alert(usr, "Which rank would you like to manage?", "Manage Player Ranks", SKYRAT_PLAYER_RANKS)
if(!choice || !(choice in SKYRAT_PLAYER_RANKS))
return
manage_player_rank_in_group(choice)
/// Proc that helps a bit with repetition, basically an extension of `manage_player_ranks()`
/datum/admins/proc/manage_player_rank_in_group(group)
if(!(group in SKYRAT_PLAYER_RANKS))
CRASH("[key_name(usr)] attempted to add someone to an invalid \"[group]\" group.")
var/group_title = lowertext(group)
var/list/choices = list("Add", "Remove")
switch(tgui_alert(usr, "What would you like to do?", "Manage [group]s", choices))
if("Add")
var/name = input(usr, "Please enter the CKEY (case-insensitive) of the person you would like to make a [group_title]:", "Add a [group_title]") as null|text
if(!name)
return
var/player_to_be = ckey(name)
if(!player_to_be)
to_chat(usr, span_warning("\"[name]\" is not a valid CKEY."))
return
switch(group)
if ("Donator")
for(var/a_donator as anything in GLOB.donator_list)
if(player_to_be == a_donator)
to_chat(usr, span_warning("\"[player_to_be]\" is already a [group_title]!"))
return
// Now that we know that the ckey is valid and they're not already apart of that group, let's add them to it!
GLOB.donator_list[player_to_be] = TRUE
text2file(player_to_be, SKYRAT_DONATOR_CONFIG_FILE)
if("Mentor")
for(var/a_mentor as anything in GLOB.mentor_datums)
if(player_to_be == a_mentor)
to_chat(usr, span_warning("\"[player_to_be]\" is already a [group_title]!"))
return
// Now that we know that the ckey is valid and they're not already apart of that group, let's add them to it!
new /datum/mentors(player_to_be)
text2file(player_to_be, SKYRAT_MENTOR_CONFIG_FILE)
if ("Veteran")
for(var/a_veteran as anything in GLOB.veteran_players)
if(player_to_be == a_veteran)
to_chat(usr, span_warning("\"[player_to_be]\" is already a [group_title]!"))
return
// Now that we know that the ckey is valid and they're not already apart of that group, let's add them to it!
GLOB.veteran_players[player_to_be] = TRUE
text2file(player_to_be, SKYRAT_VETERAN_CONFIG_FILE)
else
return
message_admins("[key_name(usr)] has granted [group_title] status to [player_to_be].")
log_admin_private("[key_name(usr)] has granted [group_title] status to [player_to_be].")
if("Remove")
var/name = input(usr, "Please enter the CKEY (case-insensitive) of the person you would like to no longer be a [group_title]:", "Remove a [group_title]") as null|text
if(!name)
return
var/player_that_was = ckey(name)
if(!player_that_was)
to_chat(usr, span_warning("\"[name]\" is not a valid CKEY."))
return
var/changes = FALSE
switch(group)
if ("Donator")
for(var/a_donator as anything in GLOB.donator_list)
if(player_that_was == a_donator)
GLOB.donator_list -= player_that_was
changes = TRUE
if(!changes)
to_chat(usr, span_warning("\"[player_that_was]\" was already not a [group_title]."))
return
save_donators()
if("Mentor")
for(var/a_mentor as anything in GLOB.mentor_datums)
if(player_that_was == a_mentor)
var/datum/mentors/mentor_datum = GLOB.mentor_datums[a_mentor]
mentor_datum.remove_mentor()
changes = TRUE
if(!changes)
to_chat(usr, span_warning("\"[player_that_was]\" was already not a [group_title]."))
save_mentors()
if("Veteran")
for(var/a_veteran as anything in GLOB.veteran_players)
if(player_that_was == a_veteran)
GLOB.veteran_players -= player_that_was
changes = TRUE
if(!changes)
to_chat(usr, span_warning("\"[player_that_was]\" was already not a [group_title]."))
return
save_veteran_players()
else
return
message_admins("[key_name(usr)] has revoked [group_title] status from [player_that_was].")
log_admin_private("[key_name(usr)] has revoked [group_title] status from [player_that_was].")
else
return
#undef SKYRAT_DONATOR_CONFIG_FILE
#undef SKYRAT_MENTOR_CONFIG_FILE
#undef SKYRAT_VETERAN_CONFIG_FILE
#undef SKYRAT_PLAYER_RANKS

View File

@@ -0,0 +1,121 @@
/// The list of the available special player ranks
#define SKYRAT_PLAYER_RANKS list("Donator", "Mentor", "Veteran")
/client/proc/manage_player_ranks()
set category = "Admin"
set name = "Manage Player Ranks"
set desc = "Manage who has the special player ranks while the server is running."
if(!check_rights(R_PERMISSIONS))
return
usr.client?.holder.manage_player_ranks()
/// Proc for admins to change people's "player" ranks (donator, mentor, veteran, etc.)
/datum/admins/proc/manage_player_ranks()
if(IsAdminAdvancedProcCall())
return
if(!check_rights(R_PERMISSIONS))
return
var/choice = tgui_alert(usr, "Which rank would you like to manage?", "Manage Player Ranks", SKYRAT_PLAYER_RANKS)
if(!choice || !(choice in SKYRAT_PLAYER_RANKS))
return
manage_player_rank_in_group(choice)
/**
* Handles managing player ranks based on the name of the group that was chosen.
*
* Arguments:
* * group - The title of the player rank that was chosen to be managed.
*/
/datum/admins/proc/manage_player_rank_in_group(group)
PROTECTED_PROC(TRUE)
if(IsAdminAdvancedProcCall())
return
if(!(group in SKYRAT_PLAYER_RANKS))
CRASH("[key_name(usr)] attempted to add someone to an invalid \"[group]\" group.")
var/group_title = lowertext(group)
var/list/choices = list("Add", "Remove")
switch(tgui_alert(usr, "What would you like to do?", "Manage [group]s", choices))
if("Add")
var/name = input(usr, "Please enter the CKEY (case-insensitive) of the person you would like to make a [group_title]:", "Add a [group_title]") as null|text
if(!name)
return
var/player_to_be = ckey(name)
if(!player_to_be)
to_chat(usr, span_warning("\"[name]\" is not a valid CKEY."))
return
var/success = SSplayer_ranks.add_player_to_group(usr.client, player_to_be, group_title)
if(!success)
return
message_admins("[key_name(usr)] has granted [group_title] status to [player_to_be].")
log_admin_private("[key_name(usr)] has granted [group_title] status to [player_to_be].")
if("Remove")
var/name = input(usr, "Please enter the CKEY (case-insensitive) of the person you would like to no longer be a [group_title]:", "Remove a [group_title]") as null|text
if(!name)
return
var/player_that_was = ckey(name)
if(!player_that_was)
to_chat(usr, span_warning("\"[name]\" is not a valid CKEY."))
return
var/success = SSplayer_ranks.remove_player_from_group(usr.client, player_that_was, group_title)
if(!success)
return
message_admins("[key_name(usr)] has revoked [group_title] status from [player_that_was].")
log_admin_private("[key_name(usr)] has revoked [group_title] status from [player_that_was].")
else
return
/client/proc/migrate_player_ranks()
set category = "Debug"
set name = "Migrate Player Ranks"
set desc = "Individually migrate the various player ranks from their legacy system to the SQL-based one."
if(!check_rights(R_PERMISSIONS | R_DEBUG | R_SERVER))
return
usr.client?.holder.migrate_player_ranks()
/datum/admins/proc/migrate_player_ranks()
if(IsAdminAdvancedProcCall())
return
if(!check_rights(R_PERMISSIONS | R_DEBUG | R_SERVER))
return
if(!CONFIG_GET(flag/sql_enabled))
return
var/choice = tgui_alert(usr, "Which rank would you like to migrate?", "Migrate Player Ranks", SKYRAT_PLAYER_RANKS)
if(!choice || !(choice in SKYRAT_PLAYER_RANKS))
return
if(tgui_alert(usr, "Are you sure that you would like to migrate [choice]s to the SQL-based system?", "Migrate Player Ranks", list("Yes", "No")) != "Yes")
return
log_admin("[key_name(usr)] is migrating the [choice] player rank from its legacy system to the SQL-based one.")
SSplayer_ranks.migrate_player_rank_to_sql(usr.client, choice)
#undef SKYRAT_PLAYER_RANKS

View File

@@ -6,9 +6,6 @@
populate_total_ui_len_by_block()
populate_total_uf_len_by_block()
make_augment_references()
//We're loading donators here because it's the least intrusive way modularly
load_donators()
load_veteran_players()
/proc/make_sprite_accessory_references()
// Here we build the global list for all accessories

View File

@@ -1,21 +0,0 @@
#define DONATORLISTFILE "[global.config.directory]/skyrat/donators.txt"
GLOBAL_LIST(donator_list)
/proc/load_donators()
GLOB.donator_list = list()
for(var/line in world.file2list(DONATORLISTFILE))
if(!line)
continue
if(findtextEx(line,"#",1,2))
continue
GLOB.donator_list[ckey(line)] = TRUE //Associative so we can check it much faster
/proc/save_donators()
/// Yes, this is incredibly long, deal with it. It's to keep that cute little comment at the top.
var/donators = "###############################################################################################\n# List for people who support us! They get cool loadout items #\n# Case is not important for ckey. #\n###############################################################################################\n"
for(var/donator in GLOB.donator_list)
donators += donator + "\n"
rustg_file_write(donators, DONATORLISTFILE)
#undef DONATORLISTFILE

View File

@@ -5,6 +5,7 @@
/client/Destroy()
if(GLOB.mentors[src])
GLOB.mentors -= src
return ..()
/client/proc/mentor_client_procs(href_list)
@@ -29,17 +30,20 @@
mentor_unfollow()
return TRUE
/client/proc/mentor_datum_set(admin)
/client/proc/mentor_datum_set()
mentor_datum = GLOB.mentor_datums[ckey]
if(!mentor_datum && check_rights_for(src, R_ADMIN,0)) // admin with no mentor datum?let's fix that
if(!mentor_datum && is_admin(src)) // admin with no mentor datum? let's fix that
new /datum/mentors(ckey)
if(mentor_datum)
mentor_datum.owner = src
GLOB.mentors[src] = TRUE
add_mentor_verbs()
if(check_rights_for(src, R_ADMIN,0) && prefs.read_preference(/datum/preference/toggle/admin/auto_dementor))
if(check_rights_for(src, R_ADMIN) && prefs.read_preference(/datum/preference/toggle/admin/auto_dementor))
cmd_mentor_dementor()
/client/proc/is_mentor() // admins are mentors too.
if(mentor_datum || check_rights_for(src, R_ADMIN,0))
if(mentor_datum || check_rights_for(src, R_ADMIN))
return TRUE

View File

@@ -1,8 +0,0 @@
/datum/configuration
var/mentors_mobname_only = FALSE // Only display mob name to mentors in mentorhelps
var/mentor_legacy_system = FALSE // Whether to use the legacy mentor system (flat file) instead of SQL
/datum/config_entry/flag/mentors_mobname_only
/datum/config_entry/flag/mentor_legacy_system //Defines whether the server uses the legacy mentor system with mentors.txt or the SQL system
protection = CONFIG_ENTRY_LOCKED

View File

@@ -1,13 +1,9 @@
#define SKYRAT_MENTOR_CONFIG_FILE "[global.config.directory]/skyrat/mentors.txt"
GLOBAL_LIST_EMPTY(mentor_datums)
GLOBAL_PROTECT(mentor_datums)
GLOBAL_VAR_INIT(mentor_href_token, GenerateToken())
GLOBAL_PROTECT(mentor_href_token)
//#define SKYRAT_MENTOR_CONFIG_FILE "[global.config.directory]/skyrat/mentors.txt"
/datum/mentors
var/name = "someone's mentor datum"
var/client/owner // the actual mentor, client type
@@ -70,64 +66,7 @@ GLOBAL_PROTECT(mentor_href_token)
/proc/MentorHrefToken(forceGlobal = FALSE)
return "mentor_token=[RawMentorHrefToken(forceGlobal)]"
/proc/load_mentors()
usr = null
GLOB.mentor_datums.Cut()
for(var/it as anything in GLOB.mentors)
var/client/C = it
C.remove_mentor_verbs()
C.mentor_datum = null
GLOB.mentors.Cut()
//if(CONFIG_GET(flag/mentor_legacy_system))//legacy
var/list/lines = world.file2list(SKYRAT_MENTOR_CONFIG_FILE)
for(var/line in lines)
if(!length(line))
continue
if(findtextEx(line, "#", 1, 2))
continue
new /datum/mentors(line)
/*else//Database
if(!SSdbcore.Connect())
log_world("Failed to connect to database in load_mentors(). Reverting to legacy system.")
WRITE_FILE(GLOB.world_game_log, "Failed to connect to database in load_mentors(). Reverting to legacy system.")
CONFIG_SET(flag/mentor_legacy_system, TRUE)
load_mentors()
return
var/datum/DBQuery/query_load_mentors = SSdbcore.NewQuery("SELECT ckey FROM [format_table_name("mentor")]")
if(!query_load_mentors.Execute())
return
while(query_load_mentors.NextRow())
var/ckey = ckey(query_load_mentors.item[1])
new /datum/mentors(ckey)*/
/// Proc to save the current mentor list into the config, overwriting it.
/proc/save_mentors()
usr = null
var/mentor_list = ""
// This whole mess is just to create a cache of all the mentors that were in the config already
// so that we don't add every admin to the list, which would be a pain to maintain afterwards.
var/list/existing_mentor_config = world.file2list(SKYRAT_MENTOR_CONFIG_FILE)
var/list/existing_mentors = list()
for(var/line in existing_mentor_config)
if(!length(line))
continue
if(findtextEx(line, "#", 1, 2))
continue
var/existing_mentor = ckey(line)
if(!existing_mentor)
continue
existing_mentors[existing_mentor] = TRUE
for(var/mentor as anything in GLOB.mentor_datums)
// We're doing this check to not add admins to the file, as explained above.
if(existing_mentors[mentor] == TRUE)
mentor_list += mentor + "\n"
rustg_file_write(mentor_list, SKYRAT_MENTOR_CONFIG_FILE)
// new client var: mentor_datum. Acts the same way holder does towards admin: it holds the mentor datum. if set, the guy's a mentor.
/client
/// Acts the same way holder does towards admin: it holds the mentor datum. if set, the guy's a mentor.
var/datum/mentors/mentor_datum
#undef SKYRAT_MENTOR_CONFIG_FILE

View File

@@ -6,7 +6,7 @@ GLOBAL_PROTECT(mentor_verbs)
/client/proc/add_mentor_verbs()
if(mentor_datum)
add_verb(src,GLOB.mentor_verbs)
add_verb(src, GLOB.mentor_verbs)
/client/proc/remove_mentor_verbs()
remove_verb(src,GLOB.mentor_verbs)
remove_verb(src, GLOB.mentor_verbs)

View File

@@ -0,0 +1,227 @@
/// The index of the ckey in the items of a given row in a query for player ranks.
#define INDEX_CKEY 1
/**
* This datum is intended to be used as a method of abstraction for the different
* ways that each player rank handles adding and removing players from their global
* lists, as well as handling the legacy adding, removing, loading and saving of
* said lists.
*/
/datum/player_rank_controller
/// The name of the player rank in the database.
/// This **NEEDS** to be set by subtypes, otherwise you WILL run into severe
/// issues.
var/rank_title = null
/// The path to the legacy file holding all of the players that have this rank.
/// Should be set in `New()`, since it has a non-constant compile-time value.
var/legacy_file_path = null
/// The header for the legacy file, if any.
/// Leave as `""` if you don't have one.
var/legacy_file_header = ""
/datum/player_rank_controller/vv_edit_var(var_name, var_value)
// No touching the controller's vars, treat these as protected config.
return FALSE
/**
* Handles adding this rank to a player by their ckey. This is only intended to
* be used for handling the in-game portion of adding this rank, and not to
* save this change anywhere. That should be handled by the caller.
*
* DO NOT FORGET TO ADD A `IsAdminAdvancedProcCall()` CHECK SO THAT ADMINS
* CAN'T JUST USE THAT TO SKIP PERMISSION CHECKS!!!
*/
/datum/player_rank_controller/proc/add_player(ckey)
SHOULD_CALL_PARENT(FALSE)
CRASH("[src] did not implement add_player()! Fix this ASAP!")
/**
* Handles adding this rank to a player using the legacy system, updating the
* legacy config file in the process.
*
* Don't override this, everything should be handled from `add_player()`,
* this is mostly just a helper for convenience.
*
* Arguments:
* * ckey - The ckey of the player you want to now possess this player rank.
*/
/datum/player_rank_controller/proc/add_player_legacy(ckey)
SHOULD_NOT_OVERRIDE(TRUE)
if(IsAdminAdvancedProcCall())
return
add_player(ckey)
text2file(ckey, legacy_file_path)
/**
* Handles removing this rank from a player by their ckey. This is only
* intended to be used for handling the in-game portion of removing this rank,
* and not to save this change anywhere. That should be handled by the caller.
*
* DO NOT FORGET TO ADD A `IsAdminAdvancedProcCall()` CHECK SO THAT ADMINS
* CAN'T JUST USE THAT TO SKIP PERMISSION CHECKS!!!
*/
/datum/player_rank_controller/proc/remove_player(ckey)
SHOULD_CALL_PARENT(FALSE)
CRASH("[src] did not implement remove_player()! Fix this ASAP!")
/**
* Handles removing this rank from a player using the legacy system, updating
* the legacy config file in the process.
*
* Don't override this, everything should be handled from `remove_player()`
* and `save_legacy()`, this is mostly just a helper for convenience.
*
* Arguments:
* * ckey - The ckey of the player you want to no longer possess this player
* rank.
*/
/datum/player_rank_controller/proc/remove_player_legacy(ckey)
SHOULD_NOT_OVERRIDE(TRUE)
if(IsAdminAdvancedProcCall())
return
remove_player(ckey)
// We have to save like this, because we're taking something out at an
// arbitrary point in the list.
save_legacy()
/**
* Handles loading the players that have this rank from an already-executed
* database query.
*
* Mostly just a helper to simplify the logic of the subsystem.
*/
/datum/player_rank_controller/proc/load_from_query(datum/db_query/query)
if(IsAdminAdvancedProcCall())
return
clear_existing_rank_data()
while(query.NextRow())
var/ckey = ckey(query.item[INDEX_CKEY])
add_player(ckey)
/**
* Handles loading the players that have this rank from its legacy config file.
*
* Don't override this, use `clear_existing_rank_data()` to clear up anything
* that needs to be cleared/initialized before loading the rank, and
* `add_player()` for actually giving the rank to the ckey in-game.
*/
/datum/player_rank_controller/proc/load_legacy()
SHOULD_NOT_OVERRIDE(TRUE)
if(IsAdminAdvancedProcCall())
return
clear_existing_rank_data()
for(var/line in world.file2list(legacy_file_path))
if(!line)
continue
if(findtextEx(line, "#", 1, 2))
continue
add_player(line)
return TRUE
/**
* Handles saving the players that have this rank using its legacy config file.
*
* Don't override this, use `get_ckeys_for_legacy_save()` if you need to filter
* the list of ckeys that will get saved.
*/
/datum/player_rank_controller/proc/save_legacy()
SHOULD_NOT_OVERRIDE(TRUE)
if(IsAdminAdvancedProcCall())
return
var/save_file_contents = legacy_file_header
for(var/player in get_ckeys_for_legacy_save())
save_file_contents += player + "\n"
rustg_file_write(save_file_contents, legacy_file_path)
/**
* Handles returning a list of all the legacy ckeys that should be migrated
* from the legacy system to the database one.
*
* Returns a list of ckeys as strings.
*/
/datum/player_rank_controller/proc/get_ckeys_to_migrate()
SHOULD_NOT_OVERRIDE(TRUE)
RETURN_TYPE(/list)
var/list/ckeys_to_migrate = list()
for(var/line in world.file2list(legacy_file_path))
if(!line)
continue
if(findtextEx(line, "#", 1, 2))
continue
var/to_migrate = ckey(line)
if(!to_migrate)
continue
ckeys_to_migrate += to_migrate
return ckeys_to_migrate
/**
* Simple proc for subtypes to override for their own handling of obtaining
* a list of ckeys to save during a legacy save.
*
* DO NOT FORGET TO ADD A `IsAdminAdvancedProcCall()` CHECK SO THAT ADMINS
* CAN'T JUST ELEVATE PERMISSIONS TO ADD THEMSELVES TO A LEGACY SAVE!!!
*/
/datum/player_rank_controller/proc/get_ckeys_for_legacy_save()
SHOULD_CALL_PARENT(FALSE)
RETURN_TYPE(/list)
CRASH("[src] did not implement get_ckeys_for_legacy_save()! Fix this ASAP!")
/datum/player_rank_controller/proc/should_use_legacy_system()
SHOULD_CALL_PARENT(FALSE)
. = TRUE
stack_trace("[src] did not implement should_use_legacy_system(), defaulting to TRUE! Fix this ASAP!")
/**
* Simple proc for subtypes to override for their own handling of clearing any
* lists that need to be cleared before loading the player rank data.
*
* DO NOT FORGET TO ADD A `IsAdminAdvancedProcCall()` CHECK SO THAT ADMINS
* CAN'T JUST ELEVATE PERMISSIONS TO CLEAR RANK DATA AND SCREW YOU OVER!!!
*/
/datum/player_rank_controller/proc/clear_existing_rank_data()
SHOULD_CALL_PARENT(FALSE)
PROTECTED_PROC(TRUE)
CRASH("[src] did not implement clear_existing_rank_data()! Fix this ASAP!")
#undef INDEX_CKEY

View File

@@ -0,0 +1,49 @@
/// The list of all donators.
GLOBAL_LIST_EMPTY(donator_list)
GLOBAL_PROTECT(donator_list)
/datum/player_rank_controller/donator
rank_title = "donator"
// Yes, this is incredibly long, deal with it. It's to keep that cute little comment at the top.
legacy_file_header = "###############################################################################################\n# List for people who support us! They get cool loadout items #\n# Case is not important for ckey. #\n###############################################################################################\n"
/datum/player_rank_controller/donator/New()
. = ..()
legacy_file_path = "[global.config.directory]/skyrat/donators.txt"
/datum/player_rank_controller/donator/add_player(ckey)
if(IsAdminAdvancedProcCall())
return
ckey = ckey(ckey)
// Associative list for extra SPEED!
GLOB.donator_list[ckey] = TRUE
/datum/player_rank_controller/donator/remove_player(ckey)
if(IsAdminAdvancedProcCall())
return
GLOB.donator_list -= ckey
/datum/player_rank_controller/donator/get_ckeys_for_legacy_save()
if(IsAdminAdvancedProcCall())
return
return GLOB.donator_list
/datum/player_rank_controller/donator/should_use_legacy_system()
return CONFIG_GET(flag/donator_legacy_system)
/datum/player_rank_controller/donator/clear_existing_rank_data()
if(IsAdminAdvancedProcCall())
return
GLOB.donator_list = list()

View File

@@ -0,0 +1,76 @@
// The mentor system is a bit more complex than the other player ranks, so it's
// got its own handling and global lists declarations in the `mentor` module.
/datum/player_rank_controller/mentor
rank_title = "mentor"
/datum/player_rank_controller/mentor/New()
. = ..()
legacy_file_path = "[global.config.directory]/skyrat/mentors.txt"
/datum/player_rank_controller/mentor/add_player(ckey)
if(IsAdminAdvancedProcCall())
return
ckey = ckey(ckey)
new /datum/mentors(ckey)
/datum/player_rank_controller/mentor/remove_player(ckey)
if(IsAdminAdvancedProcCall())
return
var/datum/mentors/mentor_datum = GLOB.mentor_datums[ckey]
mentor_datum?.remove_mentor()
/datum/player_rank_controller/mentor/get_ckeys_for_legacy_save()
if(IsAdminAdvancedProcCall())
return
// This whole mess is just to only save the mentors that were in the config
// already so that we don't add every admin to the config file, which would
// be a pain to maintain afterwards.
// We don't save mentors that are new to the `GLOB.mentor_datums` list,
// because they should have already been saved from `add_player_legacy()`.
var/list/mentors_to_save = list()
var/list/existing_mentor_config = world.file2list(legacy_file_path)
for(var/line in existing_mentor_config)
if(!length(line))
continue
if(findtextEx(line, "#", 1, 2))
continue
var/existing_mentor = ckey(line)
if(!existing_mentor)
continue
// Only save them if they're still in the mentor datums list in-game.
if(!GLOB.mentor_datums[existing_mentor])
continue
// Associative list for extra SPEED!
mentors_to_save[existing_mentor] = TRUE
return mentors_to_save
/datum/player_rank_controller/mentor/should_use_legacy_system()
return CONFIG_GET(flag/mentor_legacy_system)
/datum/player_rank_controller/mentor/clear_existing_rank_data()
if(IsAdminAdvancedProcCall())
return
GLOB.mentor_datums.Cut()
for(var/client/ex_mentor as anything in GLOB.mentors)
ex_mentor.remove_mentor_verbs()
ex_mentor.mentor_datum = null
GLOB.mentors.Cut()

View File

@@ -0,0 +1,47 @@
/// The list of all veteran players.
GLOBAL_LIST_EMPTY(veteran_list)
GLOBAL_PROTECT(veteran_list)
/datum/player_rank_controller/veteran
rank_title = "veteran"
/datum/player_rank_controller/veteran/New()
. = ..()
legacy_file_path = "[global.config.directory]/skyrat/veteran_players.txt"
/datum/player_rank_controller/veteran/add_player(ckey)
if(IsAdminAdvancedProcCall())
return
ckey = ckey(ckey)
// Associative list for extra SPEED!
GLOB.veteran_list[ckey] = TRUE
/datum/player_rank_controller/veteran/remove_player(ckey)
if(IsAdminAdvancedProcCall())
return
GLOB.veteran_list -= ckey
/datum/player_rank_controller/veteran/get_ckeys_for_legacy_save()
if(IsAdminAdvancedProcCall())
return
return GLOB.veteran_list
/datum/player_rank_controller/veteran/should_use_legacy_system()
return CONFIG_GET(flag/veteran_legacy_system)
/datum/player_rank_controller/veteran/clear_existing_rank_data()
if(IsAdminAdvancedProcCall())
return
GLOB.veteran_list = list()

View File

@@ -0,0 +1,454 @@
/// The name of the table on the database containing the player ranks.
/// See `skyrat_schema.sql` for the schema of the table.
#define PLAYER_RANK_TABLE_NAME "player_rank"
/// The index of the ckey in the items of a given row in a query for player ranks.
#define INDEX_CKEY 1
/// The name entered in the database for the admin_ckey for legacy migrations.
#define LEGACY_MIGRATION_ADMIN_CKEY "LEGACY"
SUBSYSTEM_DEF(player_ranks)
name = "Player Ranks"
flags = SS_NO_FIRE
init_order = INIT_ORDER_PLAYER_RANKS
// The following controllers handle most of the legacy system's functions,
// and provide a layer of abstraction for this subsystem to have cleaner
// logic.
/// The donator player rank controller.
var/datum/player_rank_controller/donator/donator_controller
/// The mentor player rank controller.
var/datum/player_rank_controller/mentor/mentor_controller
/// The veteran player rank controller.
var/datum/player_rank_controller/veteran/veteran_controller
/datum/controller/subsystem/player_ranks/Initialize()
if(IsAdminAdvancedProcCall())
return
load_donators()
load_mentors()
load_veterans()
return SS_INIT_SUCCESS
/datum/controller/subsystem/player_ranks/Destroy()
. = ..()
QDEL_NULL(donator_controller)
QDEL_NULL(mentor_controller)
QDEL_NULL(veteran_controller)
/**
* Returns whether or not the user is qualified as a donator.
*
* Arguments:
* * user - The client to verify the donator status of.
*/
/datum/controller/subsystem/player_ranks/proc/is_donator(client/user)
if(!istype(user))
CRASH("Invalid user type provided to is_donator(), expected 'client' and obtained '[user ? user.type : "null"]'.")
if(GLOB.donator_list[user.ckey])
return TRUE
if(is_admin(user))
return TRUE
return FALSE
/**
* Returns whether or not the user is qualified as a mentor.
* Wrapper for the `is_mentor()` proc on the client, with a null check.
*
* Arguments:
* * user - The client to verify the mentor status of.
*/
/datum/controller/subsystem/player_ranks/proc/is_mentor(client/user)
if(!istype(user))
CRASH("Invalid user type provided to is_mentor(), expected 'client' and obtained '[user ? user.type : "null"]'.")
return user.is_mentor()
/**
* Returns whether or not the user is qualified as a veteran.
*
* Arguments:
* * user - The client to verify the veteran status of.
*/
/datum/controller/subsystem/player_ranks/proc/is_veteran(client/user)
if(!istype(user))
CRASH("Invalid user type provided to is_veteran(), expected 'client' and obtained '[user ? user.type : "null"]'.")
if(GLOB.veteran_list[user.ckey])
return TRUE
if(is_admin(user))
return TRUE
return FALSE
/// Handles loading donators either via SQL or using the legacy system,
/// based on configs.
/datum/controller/subsystem/player_ranks/proc/load_donators()
PROTECTED_PROC(TRUE)
if(IsAdminAdvancedProcCall())
return
donator_controller = new
if(CONFIG_GET(flag/donator_legacy_system))
donator_controller.load_legacy()
update_all_prefs_unlock_contents()
return
if(!SSdbcore.Connect())
var/message = "Failed to connect to database in load_donators(). Reverting to legacy system."
log_config(message)
log_game(message)
message_admins(message)
CONFIG_SET(flag/donator_legacy_system, TRUE)
donator_controller.load_legacy()
return
load_player_rank_sql(donator_controller)
update_all_prefs_unlock_contents()
/**
* Handles updating all of the preferences datums to have the appropriate
* `unlock_content` and `max_save_slots` once donators are loaded.
*/
/datum/controller/subsystem/player_ranks/proc/update_all_prefs_unlock_contents()
for(var/ckey as anything in GLOB.preferences_datums)
update_prefs_unlock_content(GLOB.preferences_datums[ckey])
/**
* Updates the `unlock_contents` and the `max_save_slots`
*
* Arguments:
* * prefs - The preferences datum to check the unlock_content eligibility.
*/
/datum/controller/subsystem/player_ranks/proc/update_prefs_unlock_content(datum/preferences/prefs)
if(!prefs)
return
prefs.unlock_content = !!prefs.parent.IsByondMember() || is_donator(prefs.parent)
if(prefs.unlock_content)
prefs.max_save_slots = 50
/// Handles loading mentors either via SQL or using the legacy system,
/// based on configs.
/datum/controller/subsystem/player_ranks/proc/load_mentors()
PROTECTED_PROC(TRUE)
if(IsAdminAdvancedProcCall())
return
mentor_controller = new
if(CONFIG_GET(flag/mentor_legacy_system))
mentor_controller.load_legacy()
return
if(!SSdbcore.Connect())
var/message = "Failed to connect to database in load_mentors(). Reverting to legacy system."
log_config(message)
log_game(message)
message_admins(message)
CONFIG_SET(flag/mentor_legacy_system, TRUE)
mentor_controller.load_legacy()
return
load_player_rank_sql(mentor_controller)
/// Handles loading veteran players either via SQL or using the legacy system,
/// based on configs.
/datum/controller/subsystem/player_ranks/proc/load_veterans()
PROTECTED_PROC(TRUE)
if(IsAdminAdvancedProcCall())
return
veteran_controller = new
if(CONFIG_GET(flag/veteran_legacy_system))
veteran_controller.load_legacy()
return
if(!SSdbcore.Connect())
var/message = "Failed to connect to database in load_veterans(). Reverting to legacy system."
log_config(message)
log_game(message)
message_admins(message)
CONFIG_SET(flag/veteran_legacy_system, TRUE)
veteran_controller.load_legacy()
return
load_player_rank_sql(veteran_controller)
/**
* Handles populating the player rank from the database.
*
* Arguments:
* * rank_controller - The player rank controller of the rank to load.
*/
/datum/controller/subsystem/player_ranks/proc/load_player_rank_sql(datum/player_rank_controller/rank_controller)
PROTECTED_PROC(TRUE)
if(IsAdminAdvancedProcCall())
return
var/datum/db_query/query_load_player_rank = SSdbcore.NewQuery(
"SELECT ckey FROM [format_table_name(PLAYER_RANK_TABLE_NAME)] WHERE deleted = 0 AND rank = :rank",
list("rank" = rank_controller.rank_title),
)
if(!query_load_player_rank.warn_execute())
return
rank_controller.load_from_query(query_load_player_rank)
/// Allows fetching the appropriate player_rank_controller based on its
/// `rank_title`, for convenience.
/datum/controller/subsystem/player_ranks/proc/get_controller_for_group(rank_title)
PROTECTED_PROC(TRUE)
if(IsAdminAdvancedProcCall())
return null
rank_title = lowertext(rank_title)
// Can't make switch() statements with non-constant values.
if(rank_title == donator_controller.rank_title)
return donator_controller
if(rank_title == mentor_controller.rank_title)
return mentor_controller
if(rank_title == veteran_controller.rank_title)
return veteran_controller
CRASH("Invalid player_rank_controller \"[rank_title || "*null*"]\" used in get_controller_for_group()!")
/**
* Handles adding the ckey to the proper player rank group, either on the database
* or in the legacy system.
*
* Arguments:
* * admin - The admin making the rank change.
* * ckey - The ckey of the player you want to now possess that player rank.
* * rank_title - The title of the group you want to add the ckey to.
*/
/datum/controller/subsystem/player_ranks/proc/add_player_to_group(client/admin, ckey, rank_title)
if(IsAdminAdvancedProcCall())
return FALSE
if(!ckey || !admin || !rank_title)
return FALSE
if(!check_rights_for(admin, R_PERMISSIONS))
to_chat(admin, span_warning("You do not possess the permissions to do this."))
return FALSE
rank_title = lowertext(rank_title)
var/datum/player_rank_controller/controller = get_controller_for_group(rank_title)
if(!controller)
stack_trace("Invalid player rank \"[rank_title]\" supplied in add_player_to_group()!")
return FALSE
ckey = ckey(ckey)
var/already_in_config = controller.get_ckeys_for_legacy_save()
if(already_in_config[ckey])
to_chat(admin, span_warning("\"[ckey]\" is already a [rank_title]!"))
return FALSE
if(controller.should_use_legacy_system())
controller.add_player_legacy(ckey)
return TRUE
return add_player_rank_sql(controller, ckey, admin.ckey)
/**
* Handles adding the ckey to the appropriate player rank table on the database,
* as well as in-game.
*
* Arguments:
* * controller - The controller of the player rank you want to add the ckey to.
* * ckey - The ckey of the player you want to now possess that player rank.
* * admin_ckey - The ckey of the admin that made the rank change.
*/
/datum/controller/subsystem/player_ranks/proc/add_player_rank_sql(datum/player_rank_controller/controller, ckey, admin_ckey)
PROTECTED_PROC(TRUE)
if(IsAdminAdvancedProcCall())
return FALSE
var/datum/db_query/query_add_player_rank = SSdbcore.NewQuery(
"INSERT INTO [format_table_name(PLAYER_RANK_TABLE_NAME)] (ckey, rank, admin_ckey) VALUES(:ckey, :rank, :admin_ckey) \
ON DUPLICATE KEY UPDATE deleted = 0, admin_ckey = :admin_ckey",
list("ckey" = ckey, "rank" = controller.rank_title, "admin_ckey" = admin_ckey),
)
if(!query_add_player_rank.warn_execute())
return FALSE
controller.add_player(ckey)
return TRUE
/**
* Handles removing the ckey from the proper player rank group, either on the database
* or in the legacy system.
*
* Arguments:
* * admin - The admin making the rank change.
* * ckey - The ckey of the player you want to no longer possess that player rank.
* * rank_title - The title of the group you want to remove the ckey from.
*/
/datum/controller/subsystem/player_ranks/proc/remove_player_from_group(client/admin, ckey, rank_title)
if(IsAdminAdvancedProcCall())
return FALSE
if(!ckey || !admin || !rank_title)
return FALSE
if(!check_rights_for(admin, R_PERMISSIONS))
to_chat(admin, span_warning("You do not possess the permissions to do this."))
return FALSE
rank_title = lowertext(rank_title)
var/datum/player_rank_controller/controller = get_controller_for_group(rank_title)
if(!controller)
stack_trace("Invalid player rank \"[rank_title]\" supplied in add_player_to_group()!")
return FALSE
ckey = ckey(ckey)
var/already_in_config = controller.get_ckeys_for_legacy_save()
if(!already_in_config[ckey])
to_chat(admin, span_warning("\"[ckey]\" is already not a [rank_title]!"))
return FALSE
if(controller.should_use_legacy_system())
controller.remove_player_legacy(ckey)
return TRUE
return remove_player_rank_sql(controller, ckey, admin.ckey)
/**
* Handles removing the ckey from the appropriate player rank table on the database,
* as well as in-game.
*
* Arguments:
* * controller - The controller of the player rank you want to remove the ckey from.
* * ckey - The ckey of the player you want to no longer possess that player rank.
* * admin_ckey - The ckey of the admin that made the rank change.
*/
/datum/controller/subsystem/player_ranks/proc/remove_player_rank_sql(datum/player_rank_controller/controller, ckey, admin_ckey)
PROTECTED_PROC(TRUE)
if(IsAdminAdvancedProcCall())
return FALSE
var/datum/db_query/query_remove_player_rank = SSdbcore.NewQuery(
"UPDATE [format_table_name(PLAYER_RANK_TABLE_NAME)] SET deleted = 1, admin_ckey = :admin_ckey WHERE ckey = :ckey AND rank = :rank",
list("ckey" = ckey, "rank" = controller.rank_title, "admin_ckey" = admin_ckey),
)
if(!query_remove_player_rank.warn_execute())
return FALSE
controller.remove_player(ckey)
return TRUE
/**
* Handles migrating a player rank system from the legacy system to the
* SQL-based version, from its `rank_title`
*
* Arguments:
* * admin - The admin trying to do the migration.
* * rank_title - String of the name of the player rank to migrate
* (case-sensitive).
*/
/datum/controller/subsystem/player_ranks/proc/migrate_player_rank_to_sql(client/admin, rank_title)
if(IsAdminAdvancedProcCall())
return
if(!check_rights_for(admin, R_PERMISSIONS | R_DEBUG | R_SERVER))
to_chat(admin, span_warning("You do not possess the permissions to do this."))
return
var/datum/player_rank_controller/controller = get_controller_for_group(rank_title)
if(!controller)
return
migrate_player_rank_to_sql_from_controller(controller)
/**
* Handles migrating the ckeys of the players that were stored in a legacy
* player rank system into the SQL-based one instead. It will ensure to only
* add ckeys that were not already present in the database.
*
* Arguments:
* * controller - The player rank controller you want to migrate from the
* legacy system to the SQL one.
*/
/datum/controller/subsystem/player_ranks/proc/migrate_player_rank_to_sql_from_controller(datum/player_rank_controller/controller)
PROTECTED_PROC(TRUE)
if(IsAdminAdvancedProcCall())
return
var/list/ckeys_to_migrate = controller.get_ckeys_to_migrate()
// We explicitly don't check if they were deleted or not, because we
// EXPLICITLY want to avoid any kind of duplicates.
var/datum/db_query/query_get_existing_entries = SSdbcore.NewQuery(
"SELECT ckey FROM [format_table_name(PLAYER_RANK_TABLE_NAME)] WHERE rank = :rank",
list("rank" = controller.rank_title),
)
if(!query_get_existing_entries.warn_execute())
return
while(query_get_existing_entries.NextRow())
var/ckey = ckey(query_get_existing_entries.item[INDEX_CKEY])
ckeys_to_migrate -= ckey
var/list/rows_to_insert = list()
for(var/ckey in ckeys_to_migrate)
rows_to_insert += list(list("ckey" = ckey, "rank" = controller.rank_title, "admin_ckey" = LEGACY_MIGRATION_ADMIN_CKEY))
log_config("Migrating [length(rows_to_insert)] entries from \the [controller.rank_title] legacy system to the SQL-based system.")
SSdbcore.MassInsert(format_table_name(PLAYER_RANK_TABLE_NAME), rows_to_insert, warn = TRUE)
#undef PLAYER_RANK_TABLE_NAME
#undef INDEX_CKEY
#undef LEGACY_MIGRATION_ADMIN_CKEY

View File

@@ -1,31 +0,0 @@
#define VETERANPLAYERS "[global.config.directory]/skyrat/veteran_players.txt"
GLOBAL_LIST(veteran_players)
/proc/load_veteran_players()
GLOB.veteran_players = list()
for(var/line in world.file2list(VETERANPLAYERS))
if(!line)
continue
if(findtextEx(line,"#",1,2))
continue
GLOB.veteran_players[ckey(line)] = TRUE //Associative so we can check it much faster
/proc/save_veteran_players()
var/veteran_list = ""
for(var/veteran in GLOB.veteran_players)
veteran_list += veteran + "\n"
rustg_file_write(veteran_list, VETERANPLAYERS)
/proc/is_veteran_player(client/user)
if(isnull(user))
return FALSE
if(GLOB.veteran_players[user.ckey])
return TRUE
if(check_rights_for(user, R_ADMIN))
return TRUE
if(GLOB.deadmins[user.ckey])
return TRUE
return FALSE
#undef VETERANPLAYERS

View File

@@ -5895,6 +5895,7 @@
#include "modular_skyrat\master_files\code\modules\logging\categories\log_category_uplink.dm"
#include "modular_skyrat\master_files\code\modules\mapfluff\ruins\objects_and_mobs\necropolis_gate.dm"
#include "modular_skyrat\master_files\code\modules\mining\equipment\explorer_gear.dm"
#include "modular_skyrat\master_files\code\modules\mob\login.dm"
#include "modular_skyrat\master_files\code\modules\mob\dead\new_player\latejoin_menu.dm"
#include "modular_skyrat\master_files\code\modules\mob\dead\new_player\preferences_setup.dm"
#include "modular_skyrat\master_files\code\modules\mob\living\blood.dm"
@@ -5972,7 +5973,7 @@
#include "modular_skyrat\modules\admin\code\aooc.dm"
#include "modular_skyrat\modules\admin\code\fix_chat.dm"
#include "modular_skyrat\modules\admin\code\loud_say.dm"
#include "modular_skyrat\modules\admin\code\manage_player_ranks.dm"
#include "modular_skyrat\modules\admin\code\player_ranks.dm"
#include "modular_skyrat\modules\admin\code\sooc.dm"
#include "modular_skyrat\modules\admin\code\smites\pie.dm"
#include "modular_skyrat\modules\advanced_engineering\code\adv_engineering.dm"
@@ -6412,7 +6413,6 @@
#include "modular_skyrat\modules\customization\game\objects\items\storage\belt.dm"
#include "modular_skyrat\modules\customization\game\objects\items\storage\rings.dm"
#include "modular_skyrat\modules\customization\game\objects\items\tanks\n2_tanks.dm"
#include "modular_skyrat\modules\customization\modules\admin\donator_list.dm"
#include "modular_skyrat\modules\customization\modules\client\preferences.dm"
#include "modular_skyrat\modules\customization\modules\client\augment\_augment.dm"
#include "modular_skyrat\modules\customization\modules\client\augment\implants.dm"
@@ -6883,7 +6883,6 @@
#include "modular_skyrat\modules\medievalcrate_skyrat\code\vintageitems.dm"
#include "modular_skyrat\modules\mentor\code\_globalvars.dm"
#include "modular_skyrat\modules\mentor\code\client_procs.dm"
#include "modular_skyrat\modules\mentor\code\config.dm"
#include "modular_skyrat\modules\mentor\code\dementor.dm"
#include "modular_skyrat\modules\mentor\code\follow.dm"
#include "modular_skyrat\modules\mentor\code\logging.dm"
@@ -7216,6 +7215,11 @@
#include "modular_skyrat\modules\pixel_shift\code\pixel_shift_component.dm"
#include "modular_skyrat\modules\pixel_shift\code\pixel_shift_keybind.dm"
#include "modular_skyrat\modules\pixel_shift\code\pixel_shift_mob.dm"
#include "modular_skyrat\modules\player_ranks\code\player_rank_controller\_player_rank_controller.dm"
#include "modular_skyrat\modules\player_ranks\code\player_rank_controller\donator_controller.dm"
#include "modular_skyrat\modules\player_ranks\code\player_rank_controller\mentor_controller.dm"
#include "modular_skyrat\modules\player_ranks\code\player_rank_controller\veteran_controller.dm"
#include "modular_skyrat\modules\player_ranks\code\subsystem\player_ranks.dm"
#include "modular_skyrat\modules\pod_locking\pod_locking.dm"
#include "modular_skyrat\modules\polarized_windows\capacitor.dm"
#include "modular_skyrat\modules\polarized_windows\polarization_controller.dm"
@@ -7414,7 +7418,6 @@
#include "modular_skyrat\modules\verbs\code\subtle.dm"
#include "modular_skyrat\modules\veteran_only\code\job_types.dm"
#include "modular_skyrat\modules\veteran_only\code\species_types.dm"
#include "modular_skyrat\modules\veteran_players\code\veteran_players.dm"
#include "modular_skyrat\modules\vox_sprites\code\color.dm"
#include "modular_skyrat\modules\vox_sprites\code\head.dm"
#include "modular_skyrat\modules\vox_sprites\code\security.dm"