From c358a20498798922b90bb37e0ea0167b461b2ecc Mon Sep 17 00:00:00 2001 From: AffectedArc07 <25063394+AffectedArc07@users.noreply.github.com> Date: Mon, 4 Oct 2021 19:15:53 +0100 Subject: [PATCH] Moves pAI saves to the DB --- SQL/paradise_schema.sql | 17 ++- SQL/updates/25-26.sql | 13 ++ code/__DEFINES/misc.dm | 2 +- code/modules/admin/verbs/debug.dm | 4 +- code/modules/client/client_defines.dm | 3 + code/modules/client/client_procs.dm | 3 + .../client/login_processing/40-pai_save.dm | 8 ++ .../mob/living/silicon/pai/personality.dm | 111 ++++++++------- .../modules/mob/living/silicon/pai/recruit.dm | 91 +++++------- config/example/config.toml | 2 +- paradise.dme | 1 + .../pai_savefile_converter.py | 132 ++++++++++++++++++ 12 files changed, 273 insertions(+), 114 deletions(-) create mode 100644 SQL/updates/25-26.sql create mode 100644 code/modules/client/login_processing/40-pai_save.dm create mode 100644 tools/pai_savefile_converter/pai_savefile_converter.py diff --git a/SQL/paradise_schema.sql b/SQL/paradise_schema.sql index 3ed1cbca688..7ba7ca56dc3 100644 --- a/SQL/paradise_schema.sql +++ b/SQL/paradise_schema.sql @@ -597,5 +597,18 @@ CREATE TABLE `2fa_secrets` ( `date_setup` DATETIME NOT NULL DEFAULT current_timestamp(), `last_time` DATETIME NULL DEFAULT NULL, PRIMARY KEY (`ckey`) USING BTREE -) -COLLATE='utf8mb4_general_ci' ENGINE=InnoDB; +) COLLATE='utf8mb4_general_ci' ENGINE=InnoDB; + +-- +-- Table structure for table `pai_saves` +-- +CREATE TABLE `pai_saves` ( + `id` INT(11) NOT NULL AUTO_INCREMENT, + `ckey` VARCHAR(50) NOT NULL DEFAULT '0' COLLATE 'utf8mb4_general_ci', + `pai_name` LONGTEXT NULL DEFAULT '0' COLLATE 'utf8mb4_general_ci', + `description` LONGTEXT NULL DEFAULT '0' COLLATE 'utf8mb4_general_ci', + `preferred_role` LONGTEXT NULL DEFAULT '0' COLLATE 'utf8mb4_general_ci', + `ooc_comments` LONGTEXT NULL DEFAULT '0' COLLATE 'utf8mb4_general_ci', + PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `ckey` (`ckey`) USING BTREE +) COLLATE='utf8mb4_general_ci' ENGINE=InnoDB; diff --git a/SQL/updates/25-26.sql b/SQL/updates/25-26.sql new file mode 100644 index 00000000000..aca1a1d79b7 --- /dev/null +++ b/SQL/updates/25-26.sql @@ -0,0 +1,13 @@ +# Updates DB from 25-25, -AffectedArc07 +# Adds a pAI saves table to the DB + +CREATE TABLE `pai_saves` ( + `id` INT(11) NOT NULL AUTO_INCREMENT, + `ckey` VARCHAR(50) NOT NULL DEFAULT '0' COLLATE 'utf8mb4_general_ci', + `pai_name` LONGTEXT NULL DEFAULT '0' COLLATE 'utf8mb4_general_ci', + `description` LONGTEXT NULL DEFAULT '0' COLLATE 'utf8mb4_general_ci', + `preferred_role` LONGTEXT NULL DEFAULT '0' COLLATE 'utf8mb4_general_ci', + `ooc_comments` LONGTEXT NULL DEFAULT '0' COLLATE 'utf8mb4_general_ci', + PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `ckey` (`ckey`) USING BTREE +) COLLATE='utf8mb4_general_ci' ENGINE=InnoDB; diff --git a/code/__DEFINES/misc.dm b/code/__DEFINES/misc.dm index 2b95577ef3c..e16635d1c07 100644 --- a/code/__DEFINES/misc.dm +++ b/code/__DEFINES/misc.dm @@ -365,7 +365,7 @@ #define INVESTIGATE_BOMB "bombs" // The SQL version required by this version of the code -#define SQL_VERSION 25 +#define SQL_VERSION 26 // Vending machine stuff #define CAT_NORMAL 1 diff --git a/code/modules/admin/verbs/debug.dm b/code/modules/admin/verbs/debug.dm index fd8a7eaa00e..81ba982b556 100644 --- a/code/modules/admin/verbs/debug.dm +++ b/code/modules/admin/verbs/debug.dm @@ -357,8 +357,8 @@ GLOBAL_PROTECT(AdminProcCallSpamPrevention) pai.real_name = pai.name pai.key = choice.key card.setPersonality(pai) - for(var/datum/paiCandidate/candidate in GLOB.paiController.pai_candidates) - if(candidate.key == choice.key) + for(var/datum/pai_save/candidate in GLOB.paiController.pai_candidates) + if(candidate.owner.ckey == choice.ckey) GLOB.paiController.pai_candidates.Remove(candidate) SSblackbox.record_feedback("tally", "admin_verb", 1, "Make pAI") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! diff --git a/code/modules/client/client_defines.dm b/code/modules/client/client_defines.dm index 583d59476eb..f68e20b1903 100644 --- a/code/modules/client/client_defines.dm +++ b/code/modules/client/client_defines.dm @@ -114,6 +114,9 @@ /// Is the client watchlisted var/watchlisted = FALSE + /// Client's pAI save + var/datum/pai_save/pai_save + /client/vv_edit_var(var_name, var_value) switch(var_name) // I know we will never be in a world where admins are editing client vars to let people bypass TOS diff --git a/code/modules/client/client_procs.dm b/code/modules/client/client_procs.dm index a787ae51d2b..8179aac212f 100644 --- a/code/modules/client/client_procs.dm +++ b/code/modules/client/client_procs.dm @@ -287,6 +287,8 @@ log_client_to_db(tdata) // Make sure our client exists in the DB + pai_save = new(src) + // This is where we load all of the clients stuff from the DB if(SSdbcore.IsConnected()) // Load in all our client data from the DB @@ -449,6 +451,7 @@ GLOB.directory -= ckey GLOB.clients -= src QDEL_NULL(chatOutput) + QDEL_NULL(pai_save) if(movingmob) movingmob.client_mobs_in_contents -= mob UNSETEMPTY(movingmob.client_mobs_in_contents) diff --git a/code/modules/client/login_processing/40-pai_save.dm b/code/modules/client/login_processing/40-pai_save.dm new file mode 100644 index 00000000000..4c21c0201a8 --- /dev/null +++ b/code/modules/client/login_processing/40-pai_save.dm @@ -0,0 +1,8 @@ +/datum/client_login_processor/pai_save + priority = 40 + +/datum/client_login_processor/pai_save/get_query(client/C) + return C.pai_save.get_query() + +/datum/client_login_processor/pai_save/process_result(datum/db_query/Q, client/C) + C.pai_save.load_data(Q) diff --git a/code/modules/mob/living/silicon/pai/personality.dm b/code/modules/mob/living/silicon/pai/personality.dm index 16e9f431b6d..144ede5d5b5 100644 --- a/code/modules/mob/living/silicon/pai/personality.dm +++ b/code/modules/mob/living/silicon/pai/personality.dm @@ -1,60 +1,65 @@ -/* - name - key - description - role - comments - ready = 0 -*/ +/datum/pai_save + /// Client that owns the pAI + var/client/owner + /// pAI's name + var/pai_name + /// pAI's description + var/description + /// pAI's role + var/role + /// pAI's OOC comments + var/ooc_comments -/datum/paiCandidate/proc/savefile_path(mob/user) - return "data/player_saves/[copytext(user.ckey, 1, 2)]/[user.ckey]/pai.sav" +/datum/pai_save/New(client/C) + ..() + owner = C -/datum/paiCandidate/proc/savefile_save(mob/user) - if(IsGuestKey(user.key)) - return 0 +/datum/pai_save/Destroy(force, ...) + owner = null + GLOB.paiController.pai_candidates -= src + return ..() - var/savefile/F = new /savefile(src.savefile_path(user)) +// This proc seems useless but its used by client data loading +/datum/pai_save/proc/get_query() + var/datum/db_query/query = SSdbcore.NewQuery("SELECT pai_name, description, preferred_role, ooc_comments FROM pai_saves WHERE ckey=:ckey", list( + "ckey" = owner.ckey + )) + return query +// Loads our data up +/datum/pai_save/proc/load_data(datum/db_query/Q) + while(Q.NextRow()) + pai_name = Q.item[1] + description = Q.item[2] + role = Q.item[3] + ooc_comments = Q.item[4] - F["name"] << src.name - F["description"] << src.description - F["role"] << src.role - F["comments"] << src.comments +// Reload save from DB if the user edits it +/datum/pai_save/proc/reload_save() + var/datum/db_query/Q = get_query() + if(!Q.warn_execute()) + qdel(Q) + return + load_data(Q) + qdel(Q) - F["version"] << 1 +// Save their save to the DB +/datum/pai_save/proc/save_to_db() + var/datum/db_query/query = SSdbcore.NewQuery({" + INSERT INTO pai_saves (ckey, pai_name, description, preferred_role, ooc_comments) + VALUES (:ckey, :pai_name, :description, :preferred_role, :ooc_comments) + ON DUPLICATE KEY UPDATE pai_name=:pai_name2, description=:description2, preferred_role=:preferred_role2, ooc_comments=:ooc_comments2 + "}, list( + "ckey" = owner.ckey, + "pai_name" = pai_name, + "description" = description, + "preferred_role" = role, + "ooc_comments" = ooc_comments, + "pai_name2" = pai_name, + "description2" = description, + "preferred_role2" = role, + "ooc_comments2" = ooc_comments + )) - return 1 - -// loads the savefile corresponding to the mob's ckey -// if silent=true, report incompatible savefiles -// returns 1 if loaded (or file was incompatible) -// returns 0 if savefile did not exist - -/datum/paiCandidate/proc/savefile_load(mob/user, silent = 1) - if(IsGuestKey(user.key)) - return 0 - - var/path = savefile_path(user) - - if(!fexists(path)) - return 0 - - var/savefile/F = new /savefile(path) - - if(!F) return //Not everyone has a pai savefile. - - var/version = null - F["version"] >> version - - if(isnull(version) || version != 1) - fdel(path) - if(!silent) - alert(user, "Your savefile was incompatible with this version and was deleted.") - return 0 - - F["name"] >> src.name - F["description"] >> src.description - F["role"] >> src.role - F["comments"] >> src.comments - return 1 + query.warn_execute() + qdel(query) diff --git a/code/modules/mob/living/silicon/pai/recruit.dm b/code/modules/mob/living/silicon/pai/recruit.dm index 02de57df689..6606472f65d 100644 --- a/code/modules/mob/living/silicon/pai/recruit.dm +++ b/code/modules/mob/living/silicon/pai/recruit.dm @@ -2,14 +2,6 @@ GLOBAL_DATUM_INIT(paiController, /datum/paiController, new) // Global handler for pAI candidates -/datum/paiCandidate - var/name - var/key - var/description - var/role - var/comments - var/ready = 0 - /datum/paiController var/list/pai_candidates = list() var/list/asked = list() @@ -18,7 +10,7 @@ GLOBAL_DATUM_INIT(paiController, /datum/paiController, new) // Global handler fo /datum/paiController/Topic(href, href_list[]) - var/datum/paiCandidate/candidate = locateUID(href_list["candidate"]) + var/datum/pai_save/candidate = locateUID(href_list["candidate"]) if(candidate) if(!istype(candidate)) @@ -32,14 +24,14 @@ GLOBAL_DATUM_INIT(paiController, /datum/paiController, new) // Global handler fo return if(usr.incapacitated() || isobserver(usr) || !card.Adjacent(usr)) return - if(istype(card, /obj/item/paicard) && istype(candidate, /datum/paiCandidate)) + if(istype(card, /obj/item/paicard) && istype(candidate, /datum/pai_save)) var/mob/living/silicon/pai/pai = new(card) - if(!candidate.name) + if(!candidate.pai_name) pai.name = pick(GLOB.ninja_names) else - pai.name = candidate.name + pai.name = candidate.pai_name pai.real_name = pai.name - pai.key = candidate.key + pai.key = candidate.owner.ckey card.setPersonality(pai) card.looking_for_personality = 0 @@ -64,7 +56,7 @@ GLOBAL_DATUM_INIT(paiController, /datum/paiController, new) // Global handler fo return if(candidate) - if(candidate.key && usr.key && candidate.key != usr.key) + if(candidate.owner.ckey && usr.ckey && candidate.owner.ckey != usr.ckey) message_admins("Warning: possible href exploit by [key_name_admin(usr)] (paiController/Topic, candidate and usr have different keys)") log_debug("Warning: possible href exploit by [key_name(usr)] (paiController/Topic, candidate and usr have different keys)") return @@ -75,9 +67,9 @@ GLOBAL_DATUM_INIT(paiController, /datum/paiController, new) // Global handler fo switch(option) if("name") - t = input("Enter a name for your pAI", "pAI Name", candidate.name) as text + t = input("Enter a name for your pAI", "pAI Name", candidate.pai_name) as text if(t) - candidate.name = sanitize(copytext(t,1,MAX_NAME_LEN)) + candidate.pai_name = sanitize(copytext(t,1,MAX_NAME_LEN)) if("desc") t = input("Enter a description for your pAI", "pAI Description", candidate.description) as message if(t) @@ -87,26 +79,26 @@ GLOBAL_DATUM_INIT(paiController, /datum/paiController, new) // Global handler fo if(t) candidate.role = sanitize(copytext(t,1,MAX_MESSAGE_LEN)) if("ooc") - t = input("Enter any OOC comments", "pAI OOC Comments", candidate.comments) as message + t = input("Enter any OOC comments", "pAI OOC Comments", candidate.ooc_comments) as message if(t) - candidate.comments = sanitize(copytext(t,1,MAX_MESSAGE_LEN)) + candidate.ooc_comments = sanitize(copytext(t,1,MAX_MESSAGE_LEN)) if("save") - candidate.savefile_save(usr) - if("load") - candidate.savefile_load(usr) + candidate.save_to_db(usr) + if("reload") + candidate.reload_save(usr) //In case people have saved unsanitized stuff. - if(candidate.name) - candidate.name = sanitize(copytext(candidate.name,1,MAX_NAME_LEN)) + if(candidate.pai_name) + candidate.pai_name = sanitize(copytext(candidate.pai_name, 1, MAX_NAME_LEN)) if(candidate.description) - candidate.description = sanitize(copytext(candidate.description,1,MAX_MESSAGE_LEN)) + candidate.description = sanitize(copytext(candidate.description, 1, MAX_MESSAGE_LEN)) if(candidate.role) - candidate.role = sanitize(copytext(candidate.role,1,MAX_MESSAGE_LEN)) - if(candidate.comments) - candidate.comments = sanitize(copytext(candidate.comments,1,MAX_MESSAGE_LEN)) + candidate.role = sanitize(copytext(candidate.role, 1, MAX_MESSAGE_LEN)) + if(candidate.ooc_comments) + candidate.ooc_comments = sanitize(copytext(candidate.ooc_comments, 1, MAX_MESSAGE_LEN)) if("submit") if(candidate) - candidate.ready = 1 + GLOB.paiController.pai_candidates |= candidate for(var/obj/item/paicard/p in world) if(p.looking_for_personality == 1) p.alertUpdate() @@ -114,18 +106,8 @@ GLOBAL_DATUM_INIT(paiController, /datum/paiController, new) // Global handler fo return recruitWindow(usr) -/datum/paiController/proc/recruitWindow(mob/M as mob) - var/datum/paiCandidate/candidate - for(var/datum/paiCandidate/c in pai_candidates) - if(!istype(c) || !istype(M)) - break - if(c.key == M.key) - candidate = c - if(!candidate) - candidate = new /datum/paiCandidate() - candidate.key = M.key - pai_candidates.Add(candidate) - +/datum/paiController/proc/recruitWindow(mob/M) + var/datum/pai_save/candidate = M.client.pai_save var/dat = "" dat += {" @@ -194,7 +176,7 @@ GLOBAL_DATUM_INIT(paiController, /datum/paiController, new) // Global handler fo - + @@ -215,7 +197,7 @@ GLOBAL_DATUM_INIT(paiController, /datum/paiController, new) // Global handler fo - + @@ -230,7 +212,7 @@ GLOBAL_DATUM_INIT(paiController, /datum/paiController, new) // Global handler fo
Name:[candidate.name] [candidate.pai_name] 
What you plan to call yourself. Suggestions: Any character name you would choose for a station character OR an AI.
OOC Comments:[candidate.comments] [candidate.ooc_comments] 
Anything you'd like to address specifically to the player reading this in an OOC manner. \"I prefer more serious RP.\", \"I'm still learning the interface!\", etc. Feel free to leave this blank if you want.
- Load Personality + Reload Personality

@@ -246,14 +228,13 @@ GLOBAL_DATUM_INIT(paiController, /datum/paiController, new) // Global handler fo /datum/paiController/proc/findPAI(obj/item/paicard/p, mob/user) requestRecruits(p, user) var/list/available = list() - for(var/datum/paiCandidate/c in GLOB.paiController.pai_candidates) - if(c.ready) - var/found = 0 - for(var/mob/o in GLOB.respawnable_list) - if(o.key == c.key) - found = 1 - if(found) - available.Add(c) + for(var/datum/pai_save/c in GLOB.paiController.pai_candidates) + var/found = 0 + for(var/mob/o in GLOB.respawnable_list) + if(o.ckey == c.owner.ckey) + found = 1 + if(found) + available.Add(c) var/dat = "" dat += {" @@ -325,12 +306,12 @@ GLOBAL_DATUM_INIT(paiController, /datum/paiController, new) // Global handler fo "} dat += "

Displaying available AI personalities from central database... If there are no entries, or if a suitable entry is not listed, check again later as more personalities may be added.

" - for(var/datum/paiCandidate/c in available) + for(var/datum/pai_save/c in available) dat += {" - + @@ -342,11 +323,11 @@ GLOBAL_DATUM_INIT(paiController, /datum/paiController, new) // Global handler fo - +
Name:[c.name][c.pai_name]
Description:
OOC Comments:[c.comments][c.ooc_comments]
-
Download [c.name] + Download [c.pai_name]

diff --git a/config/example/config.toml b/config/example/config.toml index 4a338053796..70ef8d30132 100644 --- a/config/example/config.toml +++ b/config/example/config.toml @@ -141,7 +141,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 = 25 +sql_version = 26 # SQL server address. Can be an IP or DNS name sql_address = "127.0.0.1" # SQL server port diff --git a/paradise.dme b/paradise.dme index 521a4af4399..dfcad75c3b4 100644 --- a/paradise.dme +++ b/paradise.dme @@ -1384,6 +1384,7 @@ #include "code\modules\client\login_processing\37-alts_ip.dm" #include "code\modules\client\login_processing\38-alts_cid.dm" #include "code\modules\client\login_processing\39-cid_count.dm" +#include "code\modules\client\login_processing\40-pai_save.dm" #include "code\modules\client\login_processing\__client_login_processor.dm" #include "code\modules\client\preference\character.dm" #include "code\modules\client\preference\link_processing.dm" diff --git a/tools/pai_savefile_converter/pai_savefile_converter.py b/tools/pai_savefile_converter/pai_savefile_converter.py new file mode 100644 index 00000000000..fe053763a32 --- /dev/null +++ b/tools/pai_savefile_converter/pai_savefile_converter.py @@ -0,0 +1,132 @@ +# AffectedArc07 2021-10-04 +import struct, os, mysql.connector, argparse, html +from datetime import datetime + +TYPE_NULL = 0x0 +TYPE_STRING = 0x1 +TYPE_NUMBER = 0x4 + +KEY_XOR_KEY = 0x53 +DATA_XOR_KEY = 0x3A + +KEY_LEN_OFFSET = 8 +KEY_OFFSET = 9 + +# Might be shitcode but mehhhhhhhhhhh +class Decoder: + def __init__(self, data, key, jump=9): + self.generator = self.decode_data(data, key, jump) + + def read_bytes(self, num): + return [next(self.generator) for _ in range(num)] + + def read_all_bytes(self): + result = [] + for val in self.generator: + result.append(val) + return result + + def read_number(self): + return struct.unpack("