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
| 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. |
@@ -215,7 +197,7 @@ GLOBAL_DATUM_INIT(paiController, /datum/paiController, new) // Global handler fo
| 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. |
@@ -230,7 +212,7 @@ GLOBAL_DATUM_INIT(paiController, /datum/paiController, new) // Global handler fo
|
- 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 += {"
| Name: |
- [c.name] |
+ [c.pai_name] |
| Description: |
@@ -342,11 +323,11 @@ GLOBAL_DATUM_INIT(paiController, /datum/paiController, new) // Global handler fo
| OOC Comments: |
- [c.comments] |
+ [c.ooc_comments] |
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("