Async SQL + SSdbcore (#15007)

* Initial Commit - Async SQL

* First batch of queries

* More progress

* Nukes DB Polls

* More work

* oops

* One push

* Notes work now

* Ok these work

* Watchlist done

* Async Bans!

* Async Permissions

* Async client procs

* I officially hate preference datums

* Also these

* Async Custom Items

* Async Karma

* Async Library

* Async TOS

* Cleans out the old SQL code

* CI Sanity

* Apparently MySQL doesnt support this

* What about this

* Maybe this

* Review pass 1

* This too

* Fixes job ban loading

* Fix undeleted queries

* Prevents sensitive queries being logged

* Documentation + tweaks

* Adds a verb to force reconnect the DB

* More review tweaks

* Farie tweaks

* Fixes this
This commit is contained in:
AffectedArc07
2020-12-16 15:46:25 -05:00
committed by GitHub
parent e003d552b0
commit 2bad70717c
55 changed files with 2251 additions and 2445 deletions
+149 -143
View File
@@ -188,73 +188,6 @@
if("prefs") return prefs.process_link(usr,href_list)
if("vars") return view_var_Topic(href,href_list,hsrc)
//Polls and shit
if(href_list["showpoll"])
handle_player_polling()
return
if(href_list["createpollwindow"])
create_poll_window()
return
if(href_list["createpoll"])
create_poll_function(href_list)
return
if(href_list["pollid"])
var/pollid = href_list["pollid"]
if(istext(pollid))
pollid = text2num(pollid)
if(isnum(pollid))
poll_player(pollid)
return
if(href_list["pollresults"])
var/pollid = href_list["pollresults"]
if(istext(pollid))
pollid = text2num(pollid)
if(isnum(pollid))
poll_results(pollid)
if(href_list["votepollid"] && href_list["votetype"])
if(!can_vote())
return // No voting.
var/pollid = text2num(href_list["votepollid"])
var/votetype = href_list["votetype"]
switch(votetype)
if("OPTION")
var/optionid = text2num(href_list["voteoptionid"])
vote_on_poll(pollid, optionid)
if("TEXT")
var/replytext = href_list["replytext"]
log_text_poll_reply(pollid, replytext)
if("NUMVAL")
var/id_min = text2num(href_list["minid"])
var/id_max = text2num(href_list["maxid"])
if( (id_max - id_min) > 100 ) //Basic exploit prevention
to_chat(usr, "The option ID difference is too big. Please contact administration or the database admin.")
return
for(var/optionid = id_min; optionid <= id_max; optionid++)
if(!isnull(href_list["o[optionid]"])) //Test if this optionid was replied to
var/rating
if(href_list["o[optionid]"] == "abstain")
rating = null
else
rating = text2num(href_list["o[optionid]"])
if(!isnum(rating))
return
vote_on_numval_poll(pollid, optionid, rating)
if("MULTICHOICE")
var/id_min = text2num(href_list["minoptionid"])
var/id_max = text2num(href_list["maxoptionid"])
if( (id_max - id_min) > 100 ) //Basic exploit prevention
to_chat(usr, "The option ID difference is too big. Please contact administration or the database admin.")
return
for(var/optionid = id_min; optionid <= id_max; optionid++)
if(!isnull(href_list["option_[optionid]"])) //Test if this optionid was selected
vote_on_poll(pollid, optionid, 1)
src << browse(null, "window=playerpoll")
handle_player_polling()
if(href_list["ssdwarning"])
ssd_warning_acknowledged = TRUE
to_chat(src, "<span class='notice'>SSD warning acknowledged.</span>")
@@ -337,8 +270,6 @@
to_chat(src, "<span class='warning'>If the title screen is black, resources are still downloading. Please be patient until the title screen appears.</span>")
GLOB.clients += src
GLOB.directory[ckey] = src
//Admin Authorisation
// Automatically makes localhost connection an admin
@@ -362,6 +293,12 @@
if(world.byond_version >= 511 && byond_version >= 511 && prefs.clientfps)
fps = prefs.clientfps
// This has to go here to avoid issues
// If you sleep past this point, you will get SSinput errors as well as goonchat errors
// DO NOT STUFF RANDOM SQL QUERIES BELOW THIS POINT WITHOUT USING `INVOKE_ASYNC()` OR SIMILAR
// YOU WILL BREAK STUFF. SERIOUSLY. -aa07
GLOB.clients += src
spawn() // Goonchat does some non-instant checks in start()
chatOutput.start()
@@ -372,7 +309,8 @@
if(holder)
on_holder_add()
add_admin_verbs()
admin_memo_output("Show", 0, 1)
// Must be async because any sleeps (happen in sql queries) will break connectings clients
INVOKE_ASYNC(src, .proc/admin_memo_output, "Show", FALSE, TRUE)
// Forcibly enable hardware-accelerated graphics, as we need them for the lighting overlays.
// (but turn them off first, since sometimes BYOND doesn't turn them on properly otherwise)
@@ -382,7 +320,6 @@
winset(src, null, "command=\".configure graphics-hwmode on\"")
log_client_to_db(tdata)
. = ..() //calls mob.Login()
@@ -406,11 +343,9 @@
if(prefs.toggles & PREFTOGGLE_DISABLE_KARMA) // activates if karma is disabled
if(establish_db_connection())
to_chat(src,"<span class='notice'>You have disabled karma gains.") // reminds those who have it disabled
to_chat(src,"<span class='notice'>You have disabled karma gains.") // reminds those who have it disabled
else
if(establish_db_connection())
to_chat(src,"<span class='notice'>You have enabled karma gains.")
to_chat(src,"<span class='notice'>You have enabled karma gains.")
generate_clickcatcher()
apply_clickcatcher()
@@ -482,11 +417,11 @@
/client/proc/donator_check()
set waitfor = FALSE // This needs to run async because any sleep() inside /client/New() breaks stuff badly
if(IsGuestKey(key))
return
establish_db_connection()
if(!GLOB.dbcon.IsConnected())
if(!SSdbcore.IsConnected())
return
if(check_rights(R_ADMIN, 0, mob)) // Yes, the mob is required, regardless of other examples in this file, it won't work otherwise
@@ -495,33 +430,44 @@
return
//Donator stuff.
var/DBQuery/query_donor_select = GLOB.dbcon.NewQuery("SELECT ckey, tier, active FROM `[format_table_name("donators")]` WHERE ckey = '[ckey]'")
query_donor_select.Execute()
var/datum/db_query/query_donor_select = SSdbcore.NewQuery("SELECT ckey, tier, active FROM `[format_table_name("donators")]` WHERE ckey=:ckey", list(
"ckey" = ckey
))
if(!query_donor_select.warn_execute())
qdel(query_donor_select)
return
while(query_donor_select.NextRow())
if(!text2num(query_donor_select.item[3]))
// Inactive donator.
donator_level = 0
qdel(query_donor_select)
return
donator_level = text2num(query_donor_select.item[2])
donor_loadout_points()
break
qdel(query_donor_select)
/client/proc/donor_loadout_points()
if(donator_level > 0 && prefs)
prefs.max_gear_slots = config.max_loadout_points + 5
/client/proc/log_client_to_db(connectiontopic)
set waitfor = FALSE // This needs to run async because any sleep() inside /client/New() breaks stuff badly
if(IsGuestKey(key))
return
establish_db_connection()
if(!GLOB.dbcon.IsConnected())
if(!SSdbcore.IsConnected())
return
var/datum/db_query/query = SSdbcore.NewQuery("SELECT id, datediff(Now(),firstseen) as age FROM [format_table_name("player")] WHERE ckey=:ckey", list(
"ckey" = ckey
))
if(!query.warn_execute())
qdel(query)
return
var/DBQuery/query = GLOB.dbcon.NewQuery("SELECT id, datediff(Now(),firstseen) as age FROM [format_table_name("player")] WHERE ckey = '[ckey]'")
query.Execute()
var/sql_id = 0
player_age = 0 // New players won't have an entry so knowing we have a connection we set this to zero to be updated if there is a record.
while(query.NextRow())
@@ -529,22 +475,33 @@
player_age = text2num(query.item[2])
break
var/DBQuery/query_ip = GLOB.dbcon.NewQuery("SELECT ckey FROM [format_table_name("player")] WHERE ip = '[address]'")
query_ip.Execute()
qdel(query)
var/datum/db_query/query_ip = SSdbcore.NewQuery("SELECT ckey FROM [format_table_name("player")] WHERE ip=:address", list(
"address" = address
))
if(!query_ip.warn_execute())
qdel(query_ip)
return
related_accounts_ip = list()
while(query_ip.NextRow())
if(ckey != query_ip.item[1])
related_accounts_ip.Add("[query_ip.item[1]]")
qdel(query_ip)
var/datum/db_query/query_cid = SSdbcore.NewQuery("SELECT ckey FROM [format_table_name("player")] WHERE computerid=:cid", list(
"cid" = computer_id
))
if(!query_cid.warn_execute())
qdel(query_cid)
return
var/DBQuery/query_cid = GLOB.dbcon.NewQuery("SELECT ckey FROM [format_table_name("player")] WHERE computerid = '[computer_id]'")
query_cid.Execute()
related_accounts_cid = list()
while(query_cid.NextRow())
if(ckey != query_cid.item[1])
related_accounts_cid.Add("[query_cid.item[1]]")
qdel(query_cid)
var/admin_rank = "Player"
if(holder)
@@ -573,18 +530,19 @@
if(!isnum(sql_id))
return
var/sql_ip = sanitizeSQL(address)
var/sql_computerid = sanitizeSQL(computer_id)
var/sql_admin_rank = sanitizeSQL(admin_rank)
if(sql_id)
//Player already identified previously, we need to just update the 'lastseen', 'ip' and 'computer_id' variables
var/DBQuery/query_update = GLOB.dbcon.NewQuery("UPDATE [format_table_name("player")] SET lastseen = Now(), ip = '[sql_ip]', computerid = '[sql_computerid]', lastadminrank = '[sql_admin_rank]' WHERE id = [sql_id]")
if(!query_update.Execute())
var/err = query_update.ErrorMsg()
log_game("SQL ERROR during log_client_to_db (update). Error : \[[err]\]\n")
message_admins("SQL ERROR during log_client_to_db (update). Error : \[[err]\]\n")
var/datum/db_query/query_update = SSdbcore.NewQuery("UPDATE [format_table_name("player")] SET lastseen = Now(), ip=:sql_ip, computerid=:sql_cid, lastadminrank=:sql_ar WHERE id=:sql_id", list(
"sql_ip" = address,
"sql_cid" = computer_id,
"sql_ar" = admin_rank,
"sql_id" = sql_id
))
if(!query_update.warn_execute())
qdel(query_update)
return
qdel(query_update)
// After the regular update
INVOKE_ASYNC(src, /client/.proc/get_byond_account_date, FALSE) // Async to avoid other procs in the client chain being delayed by a web request
else
@@ -597,18 +555,30 @@
qdel(src)
return // Dont insert or they can just go in again
var/DBQuery/query_insert = GLOB.dbcon.NewQuery("INSERT INTO [format_table_name("player")] (id, ckey, firstseen, lastseen, ip, computerid, lastadminrank) VALUES (null, '[ckey]', Now(), Now(), '[sql_ip]', '[sql_computerid]', '[sql_admin_rank]')")
if(!query_insert.Execute())
var/err = query_insert.ErrorMsg()
log_game("SQL ERROR during log_client_to_db (insert). Error : \[[err]\]\n")
message_admins("SQL ERROR during log_client_to_db (insert). Error : \[[err]\]\n")
var/datum/db_query/query_insert = SSdbcore.NewQuery("INSERT INTO [format_table_name("player")] (id, ckey, firstseen, lastseen, ip, computerid, lastadminrank) VALUES (null, :ckey, Now(), Now(), :ip, :cid, :rank)", list(
"ckey" = ckey,
"ip" = address,
"cid" = computer_id,
"rank" = admin_rank
))
if(!query_insert.warn_execute())
qdel(query_insert)
return
qdel(query_insert)
// This is their first connection instance, so TRUE here to nofiy admins
// This needs to happen here to ensure they actually have a row to update
INVOKE_ASYNC(src, /client/.proc/get_byond_account_date, TRUE) // Async to avoid other procs in the client chain being delayed by a web request
// Log player connections to DB
var/DBQuery/query_accesslog = GLOB.dbcon.NewQuery("INSERT INTO `[format_table_name("connection_log")]`(`datetime`,`ckey`,`ip`,`computerid`) VALUES(Now(),'[ckey]','[sql_ip]','[sql_computerid]');")
query_accesslog.Execute()
var/datum/db_query/query_accesslog = SSdbcore.NewQuery("INSERT INTO `[format_table_name("connection_log")]`(`datetime`,`ckey`,`ip`,`computerid`) VALUES(Now(), :ckey, :ip, :cid)", list(
"ckey" = ckey,
"ip" = address,
"cid" = computer_id
))
// We do nothing with output here, or anything else after, so we dont need to if() wrap it
// If you ever extend this proc below this point, please wrap these with an if() in the same way its done above
query_accesslog.warn_execute()
qdel(query_accesslog)
/client/proc/check_ip_intel()
set waitfor = 0 //we sleep when getting the intel, no need to hold up the client connection while we sleep
@@ -655,16 +625,29 @@
to_chat(src, "<B>You have no verified forum account. <a href='?src=[UID()];link_forum_account=true'>VERIFY FORUM ACCOUNT</a></B>")
/client/proc/create_oauth_token()
var/DBQuery/query_find_token = GLOB.dbcon.NewQuery("SELECT token FROM [format_table_name("oauth_tokens")] WHERE ckey = '[ckey]' limit 1")
if(!query_find_token.Execute())
log_debug("create_oauth_token: failed db read")
var/datum/db_query/query_find_token = SSdbcore.NewQuery("SELECT token FROM [format_table_name("oauth_tokens")] WHERE ckey=:ckey limit 1", list(
"ckey" = ckey
))
// These queries have log_error=FALSE to avoid auth tokens being in plaintext logs
if(!query_find_token.warn_execute(log_error=FALSE))
qdel(query_find_token)
return
if(query_find_token.NextRow())
qdel(query_find_token)
return query_find_token.item[1]
qdel(query_find_token)
var/tokenstr = md5("[rand(0,9999)][world.time][rand(0,9999)][ckey][rand(0,9999)][address][rand(0,9999)][computer_id][rand(0,9999)]")
var/DBQuery/query_insert_token = GLOB.dbcon.NewQuery("INSERT INTO [format_table_name("oauth_tokens")] (ckey, token) VALUES('[ckey]','[tokenstr]')")
if(!query_insert_token.Execute())
var/datum/db_query/query_insert_token = SSdbcore.NewQuery("INSERT INTO [format_table_name("oauth_tokens")] (ckey, token) VALUES(:ckey, :tokenstr)", list(
"ckey" = ckey,
"tokenstr" = tokenstr,
))
// These queries have log_error=FALSE to avoid auth tokens being in plaintext logs
if(!query_insert_token.warn_execute(log_error=FALSE))
qdel(query_insert_token)
return
qdel(query_insert_token)
return tokenstr
/client/proc/link_forum_account(fromban)
@@ -677,15 +660,19 @@
if(!fromban)
to_chat(src, "Your forum account is already set.")
return
var/DBQuery/query_find_link = GLOB.dbcon.NewQuery("SELECT fuid FROM [format_table_name("player")] WHERE ckey = '[ckey]' limit 1")
if(!query_find_link.Execute())
log_debug("link_forum_account: failed db read")
var/datum/db_query/query_find_link = SSdbcore.NewQuery("SELECT fuid FROM [format_table_name("player")] WHERE ckey=:ckey LIMIT 1", list(
"ckey" = ckey
))
if(!query_find_link.warn_execute())
qdel(query_find_link)
return
if(query_find_link.NextRow())
if(query_find_link.item[1])
if(!fromban)
to_chat(src, "Your forum account is already set. (" + query_find_link.item[1] + ")")
qdel(query_find_link)
return
qdel(query_find_link)
var/tokenid = create_oauth_token()
if(!tokenid)
to_chat(src, "link_forum_account: unable to create token")
@@ -705,6 +692,7 @@
// Returns true if a randomizer is being used
/client/proc/check_randomizer(topic)
set waitfor = FALSE // Yes I know this is already called from an async proc but someone may change that without thinking properly
. = FALSE
if(connection != "seeker") //Invalid connection type.
return null
@@ -721,12 +709,17 @@
var/oldcid = cidcheck[ckey]
if(!oldcid)
var/DBQuery/query_cidcheck = GLOB.dbcon.NewQuery("SELECT computerid FROM [format_table_name("player")] WHERE ckey = '[ckey]'")
query_cidcheck.Execute()
var/datum/db_query/query_cidcheck = SSdbcore.NewQuery("SELECT computerid FROM [format_table_name("player")] WHERE ckey=:ckey", list(
"ckey" = ckey
))
if(!query_cidcheck.warn_execute())
qdel(query_cidcheck)
return
var/lastcid = computer_id
if(query_cidcheck.NextRow())
lastcid = query_cidcheck.item[1]
qdel(query_cidcheck)
if(computer_id != lastcid)
// Their current CID does not match what the DB says - OFF WITH THEIR HEAD
@@ -786,23 +779,30 @@
var/const/adminckey = "CID-Error"
// Check for notes in the last day - only 1 note per 24 hours
var/DBQuery/query_get_notes = GLOB.dbcon.NewQuery("SELECT id from [format_table_name("notes")] WHERE ckey = '[ckey]' AND adminckey = '[adminckey]' AND timestamp + INTERVAL 1 DAY < NOW()")
if(!query_get_notes.Execute())
var/err = query_get_notes.ErrorMsg()
log_game("SQL ERROR obtaining id from notes table. Error : \[[err]\]\n")
var/datum/db_query/query_get_notes = SSdbcore.NewQuery("SELECT id from [format_table_name("notes")] WHERE ckey=:ckey AND adminckey=:adminckey AND timestamp + INTERVAL 1 DAY < NOW()", list(
"ckey" = ckey,
"adminckey" = adminckey
))
if(!query_get_notes.warn_execute())
qdel(query_get_notes)
return
if(query_get_notes.NextRow())
qdel(query_get_notes)
return
qdel(query_get_notes)
// Only add a note if their most recent note isn't from the randomizer blocker, either
query_get_notes = GLOB.dbcon.NewQuery("SELECT adminckey FROM [format_table_name("notes")] WHERE ckey = '[ckey]' ORDER BY timestamp DESC LIMIT 1")
if(!query_get_notes.Execute())
var/err = query_get_notes.ErrorMsg()
log_game("SQL ERROR obtaining adminckey from notes table. Error : \[[err]\]\n")
var/datum/db_query/query_get_note = SSdbcore.NewQuery("SELECT adminckey FROM [format_table_name("notes")] WHERE ckey=:ckey ORDER BY timestamp DESC LIMIT 1", list(
"ckey" = ckey
))
if(!query_get_note.warn_execute())
qdel(query_get_note)
return
if(query_get_notes.NextRow())
if(query_get_notes.item[1] == adminckey)
if(query_get_note.NextRow())
if(query_get_note.item[1] == adminckey)
qdel(query_get_note)
return
qdel(query_get_note)
add_note(ckey, "Detected as using a cid randomizer.", null, adminckey, logged = 0)
/client/proc/cid_check_reconnect()
@@ -1043,17 +1043,19 @@
/client/proc/get_byond_account_date(notify = FALSE)
// First we see if the client has a saved date in the DB
var/sql_ckey = sanitizeSQL(ckey)
var/DBQuery/query_date = GLOB.dbcon.NewQuery("SELECT byond_date, DATEDIFF(Now(), byond_date) FROM [format_table_name("player")] WHERE ckey = '[sql_ckey]'")
if(!query_date.Execute())
var/err = query_date.ErrorMsg()
log_game("SQL ERROR during get_byond_account_date (Line 1047). Error: \[[err]\]\n")
message_admins("SQL ERROR during get_byond_account_date (Line 1047). Error: \[[err]\]\n")
var/datum/db_query/query_date = SSdbcore.NewQuery("SELECT byond_date, DATEDIFF(Now(), byond_date) FROM [format_table_name("player")] WHERE ckey=:ckey", list(
"ckey" = ckey
))
if(!query_date.warn_execute())
qdel(query_date)
return
while(query_date.NextRow())
byondacc_date = query_date.item[1]
byondacc_age = max(text2num(query_date.item[2]), 0) // Ensure account isnt negative days old
qdel(query_date)
// They have a date, lets bail
if(byondacc_date)
return
@@ -1067,22 +1069,26 @@
byondacc_date = byond_data["general"]["joined"]
// Now save it
var/sql_date = sanitizeSQL(byondacc_date) // Yes, this is top level paranoia
var/DBQuery/query_update = GLOB.dbcon.NewQuery("UPDATE [format_table_name("player")] SET byond_date = '[sql_date]' WHERE ckey = '[sql_ckey]'")
if(!query_update.Execute())
var/err = query_update.ErrorMsg()
log_game("SQL ERROR during get_byond_account_date (Line 1071). Error: \[[err]\]\n")
message_admins("SQL ERROR during get_byond_account_date (Line 1071). Error: \[[err]\]\n")
var/datum/db_query/query_update = SSdbcore.NewQuery("UPDATE [format_table_name("player")] SET byond_date=:date WHERE ckey=:ckey", list(
"date" = byondacc_date,
"ckey" = ckey
))
if(!query_update.warn_execute())
qdel(query_update)
return
qdel(query_update)
// Now retrieve the age again because BYOND doesnt have native methods for this
var/DBQuery/query_age = GLOB.dbcon.NewQuery("SELECT DATEDIFF(Now(), byond_date) FROM [format_table_name("player")] WHERE ckey = '[sql_ckey]'")
if(!query_age.Execute())
var/err = query_age.ErrorMsg()
log_game("SQL ERROR during get_byond_account_date (Line 1078). Error: \[[err]\]\n")
message_admins("SQL ERROR during get_byond_account_date (Line 1078). Error: \[[err]\]\n")
var/datum/db_query/query_age = SSdbcore.NewQuery("SELECT DATEDIFF(Now(), byond_date) FROM [format_table_name("player")] WHERE ckey=:ckey", list(
"ckey" = ckey
))
if(!query_age.warn_execute())
qdel(query_age)
return
while(query_age.NextRow())
byondacc_age = max(text2num(query_age.item[1]), 0) // Ensure account isnt negative days old
qdel(query_age)
// Notify admins on new clients connecting, if the byond account age is less than a config value
if(notify && (byondacc_age < config.byond_account_age_threshold))
@@ -2306,16 +2306,18 @@ GLOBAL_LIST_INIT(special_role_times, list( //minimum age (in days) for accounts
/datum/preferences/proc/open_load_dialog(mob/user)
var/DBQuery/query = GLOB.dbcon.NewQuery("SELECT slot,real_name FROM [format_table_name("characters")] WHERE ckey='[user.ckey]' ORDER BY slot")
var/datum/db_query/query = SSdbcore.NewQuery("SELECT slot, real_name FROM [format_table_name("characters")] WHERE ckey=:ckey ORDER BY slot", list(
"ckey" = user.ckey
))
var/list/slotnames[max_save_slots]
if(!query.Execute())
var/err = query.ErrorMsg()
log_game("SQL ERROR during character slot loading. Error : \[[err]\]\n")
message_admins("SQL ERROR during character slot loading. Error : \[[err]\]\n")
if(!query.warn_execute())
qdel(query)
return
while(query.NextRow())
slotnames[text2num(query.item[1])] = query.item[2]
qdel(query)
var/dat = "<body>"
dat += "<tt><center>"
@@ -1,6 +1,6 @@
/datum/preferences/proc/load_preferences(client/C)
var/DBQuery/query = GLOB.dbcon.NewQuery({"SELECT
var/datum/db_query/query = SSdbcore.NewQuery({"SELECT
ooccolor,
UI_style,
UI_style_color,
@@ -18,13 +18,12 @@
fuid,
parallax
FROM [format_table_name("player")]
WHERE ckey='[C.ckey]'"}
)
WHERE ckey=:ckey"}, list(
"ckey" = C.ckey
))
if(!query.Execute())
var/err = query.ErrorMsg()
log_game("SQL ERROR during loading player preferences. Error : \[[err]\]\n")
message_admins("SQL ERROR during loading player preferences. Error : \[[err]\]\n")
if(!query.warn_execute())
qdel(query)
return
@@ -47,6 +46,8 @@
fuid = text2num(query.item[15])
parallax = text2num(query.item[16])
qdel(query)
//Sanitize
ooccolor = sanitize_hexcolor(ooccolor, initial(ooccolor))
UI_style = sanitize_inlist(UI_style, list("White", "Midnight"), initial(UI_style))
@@ -73,30 +74,48 @@
log_runtime(EXCEPTION("[C.key] had a malformed role entry: '[role]'. Removing!"), src)
be_special -= role
var/DBQuery/query = GLOB.dbcon.NewQuery({"UPDATE [format_table_name("player")]
var/datum/db_query/query = SSdbcore.NewQuery({"UPDATE [format_table_name("player")]
SET
ooccolor='[ooccolor]',
UI_style='[UI_style]',
UI_style_color='[UI_style_color]',
UI_style_alpha='[UI_style_alpha]',
be_role='[sanitizeSQL(list2params(be_special))]',
default_slot='[default_slot]',
toggles='[num2text(toggles, CEILING(log(10, (TOGGLES_TOTAL)), 1))]',
toggles_2='[num2text(toggles2, CEILING(log(10, (TOGGLES_2_TOTAL)), 1))]',
atklog='[atklog]',
sound='[sound]',
volume='[volume]',
lastchangelog='[lastchangelog]',
clientfps='[clientfps]',
parallax='[parallax]'
WHERE ckey='[C.ckey]'"}
ooccolor=:ooccolour,
UI_style=:ui_style,
UI_style_color=:ui_colour,
UI_style_alpha=:ui_alpha,
be_role=:berole,
default_slot=:defaultslot,
toggles=:toggles,
toggles_2=:toggles2,
atklog=:atklog,
sound=:sound,
volume=:volume,
lastchangelog=:lastchangelog,
clientfps=:clientfps,
parallax=:parallax
WHERE ckey=:ckey"}, list(
// OH GOD THE PARAMETERS
"ooccolour" = ooccolor,
"ui_style" = UI_style,
"ui_colour" = UI_style_color,
"ui_alpha" = UI_style_alpha,
"berole" = list2params(be_special),
"defaultslot" = default_slot,
// Even though its a number in the DB, you have to use num2text here, otherwise byond adds scientific notation to the number
"toggles" = num2text(toggles, CEILING(log(10, (TOGGLES_TOTAL)), 1)),
"toggles2" = num2text(toggles2, CEILING(log(10, (TOGGLES_2_TOTAL)), 1)),
"atklog" = atklog,
"sound" = sound,
"volume" = volume,
"lastchangelog" = lastchangelog,
"clientfps" = clientfps,
"parallax" = parallax,
"ckey" = C.ckey
)
)
if(!query.Execute())
var/err = query.ErrorMsg()
log_game("SQL ERROR during saving player preferences. Error : \[[err]\]\n")
message_admins("SQL ERROR during saving player preferences. Error : \[[err]\]\n")
if(!query.warn_execute())
qdel(query)
return
qdel(query)
return 1
/datum/preferences/proc/load_character(client/C,slot)
@@ -106,11 +125,17 @@
slot = sanitize_integer(slot, 1, max_save_slots, initial(default_slot))
if(slot != default_slot)
default_slot = slot
var/DBQuery/firstquery = GLOB.dbcon.NewQuery("UPDATE [format_table_name("player")] SET default_slot=[slot] WHERE ckey='[C.ckey]'")
firstquery.Execute()
var/datum/db_query/firstquery = SSdbcore.NewQuery("UPDATE [format_table_name("player")] SET default_slot=:slot WHERE ckey=:ckey", list(
"slot" = slot,
"ckey" = C.ckey
))
if(!firstquery.warn_execute())
qdel(firstquery)
return
qdel(firstquery)
// Let's not have this explode if you sneeze on the DB
var/DBQuery/query = GLOB.dbcon.NewQuery({"SELECT
var/datum/db_query/query = SSdbcore.NewQuery({"SELECT
OOC_Notes,
real_name,
name_is_always_random,
@@ -163,11 +188,12 @@
body_accessory,
gear,
autohiss
FROM [format_table_name("characters")] WHERE ckey='[C.ckey]' AND slot='[slot]'"})
if(!query.Execute())
var/err = query.ErrorMsg()
log_game("SQL ERROR during character slot loading. Error : \[[err]\]\n")
message_admins("SQL ERROR during character slot loading. Error : \[[err]\]\n")
FROM [format_table_name("characters")] WHERE ckey=:ckey AND slot=:slot"}, list(
"ckey" = C.ckey,
"slot" = slot
))
if(!query.warn_execute())
qdel(query)
return
while(query.NextRow())
@@ -244,6 +270,7 @@
saved = TRUE
qdel(query)
//Sanitize
var/datum/species/SP = GLOB.all_species[species]
metadata = sanitize_text(metadata, initial(metadata))
@@ -325,74 +352,139 @@
if(!isemptylist(loadout_gear))
gearlist = list2params(loadout_gear)
var/DBQuery/firstquery = GLOB.dbcon.NewQuery("SELECT slot FROM [format_table_name("characters")] WHERE ckey='[C.ckey]' ORDER BY slot")
firstquery.Execute()
var/datum/db_query/firstquery = SSdbcore.NewQuery("SELECT slot FROM [format_table_name("characters")] WHERE ckey=:ckey ORDER BY slot", list(
"ckey" = C.ckey
))
if(!firstquery.warn_execute())
qdel(firstquery)
return
while(firstquery.NextRow())
if(text2num(firstquery.item[1]) == default_slot)
var/DBQuery/query = GLOB.dbcon.NewQuery({"UPDATE [format_table_name("characters")] SET OOC_Notes='[sanitizeSQL(metadata)]',
real_name='[sanitizeSQL(real_name)]',
name_is_always_random='[be_random_name]',
gender='[gender]',
age='[age]',
species='[sanitizeSQL(species)]',
language='[sanitizeSQL(language)]',
hair_colour='[h_colour]',
secondary_hair_colour='[h_sec_colour]',
facial_hair_colour='[f_colour]',
secondary_facial_hair_colour='[f_sec_colour]',
skin_tone='[s_tone]',
skin_colour='[s_colour]',
marking_colours='[sanitizeSQL(markingcolourslist)]',
head_accessory_colour='[hacc_colour]',
hair_style_name='[sanitizeSQL(h_style)]',
facial_style_name='[sanitizeSQL(f_style)]',
marking_styles='[sanitizeSQL(markingstyleslist)]',
head_accessory_style_name='[sanitizeSQL(ha_style)]',
alt_head_name='[sanitizeSQL(alt_head)]',
eye_colour='[e_colour]',
underwear='[underwear]',
undershirt='[undershirt]',
backbag='[backbag]',
b_type='[b_type]',
alternate_option='[alternate_option]',
job_support_high='[job_support_high]',
job_support_med='[job_support_med]',
job_support_low='[job_support_low]',
job_medsci_high='[job_medsci_high]',
job_medsci_med='[job_medsci_med]',
job_medsci_low='[job_medsci_low]',
job_engsec_high='[job_engsec_high]',
job_engsec_med='[job_engsec_med]',
job_engsec_low='[job_engsec_low]',
job_karma_high='[job_karma_high]',
job_karma_med='[job_karma_med]',
job_karma_low='[job_karma_low]',
flavor_text='[sanitizeSQL(flavor_text)]',
med_record='[sanitizeSQL(med_record)]',
sec_record='[sanitizeSQL(sec_record)]',
gen_record='[sanitizeSQL(gen_record)]',
player_alt_titles='[playertitlelist]',
disabilities='[disabilities]',
organ_data='[organlist]',
rlimb_data='[rlimblist]',
nanotrasen_relation='[nanotrasen_relation]',
speciesprefs='[speciesprefs]',
socks='[socks]',
body_accessory='[body_accessory]',
gear='[gearlist]',
autohiss='[autohiss_mode]'
WHERE ckey='[C.ckey]'
AND slot='[default_slot]'"}
var/datum/db_query/query = SSdbcore.NewQuery({"UPDATE [format_table_name("characters")]
SET
OOC_Notes=:metadata,
real_name=:real_name,
name_is_always_random=:be_random_name,
gender=:gender,
age=:age,
species=:species,
language=:language,
hair_colour=:h_colour,
secondary_hair_colour=:h_sec_colour,
facial_hair_colour=:f_colour,
secondary_facial_hair_colour=:f_sec_colour,
skin_tone=:s_tone,
skin_colour=:s_colour,
marking_colours=:markingcolourslist,
head_accessory_colour=:hacc_colour,
hair_style_name=:h_style,
facial_style_name=:f_style,
marking_styles=:markingstyleslist,
head_accessory_style_name=:ha_style,
alt_head_name=:alt_head,
eye_colour=:e_colour,
underwear=:underwear,
undershirt=:undershirt,
backbag=:backbag,
b_type=:b_type,
alternate_option=:alternate_option,
job_support_high=:job_support_high,
job_support_med=:job_support_med,
job_support_low=:job_support_low,
job_medsci_high=:job_medsci_high,
job_medsci_med=:job_medsci_med,
job_medsci_low=:job_medsci_low,
job_engsec_high=:job_engsec_high,
job_engsec_med=:job_engsec_med,
job_engsec_low=:job_engsec_low,
job_karma_high=:job_karma_high,
job_karma_med=:job_karma_med,
job_karma_low=:job_karma_low,
flavor_text=:flavor_text,
med_record=:med_record,
sec_record=:sec_record,
gen_record=:gen_record,
player_alt_titles=:playertitlelist,
disabilities=:disabilities,
organ_data=:organlist,
rlimb_data=:rlimblist,
nanotrasen_relation=:nanotrasen_relation,
speciesprefs=:speciesprefs,
socks=:socks,
body_accessory=:body_accessory,
gear=:gearlist,
autohiss=:autohiss_mode
WHERE ckey=:ckey
AND slot=:slot"}, list(
// OH GOD SO MANY PARAMETERS
"metadata" = metadata,
"real_name" = real_name,
"be_random_name" = be_random_name,
"gender" = gender,
"age" = age,
"species" = species,
"language" = language,
"h_colour" = h_colour,
"h_sec_colour" = h_sec_colour,
"f_colour" = f_colour,
"f_sec_colour" = f_sec_colour,
"s_tone" = s_tone,
"s_colour" = s_colour,
"markingcolourslist" = markingcolourslist,
"hacc_colour" = hacc_colour,
"h_style" = h_style,
"f_style" = f_style,
"markingstyleslist" = markingstyleslist,
"ha_style" = ha_style,
"alt_head" = alt_head,
"e_colour" = e_colour,
"underwear" = underwear,
"undershirt" = undershirt,
"backbag" = backbag,
"b_type" = b_type,
"alternate_option" = alternate_option,
"job_support_high" = job_support_high,
"job_support_med" = job_support_med,
"job_support_low" = job_support_low,
"job_medsci_high" = job_medsci_high,
"job_medsci_med" = job_medsci_med,
"job_medsci_low" = job_medsci_low,
"job_engsec_high" = job_engsec_high,
"job_engsec_med" = job_engsec_med,
"job_engsec_low" = job_engsec_low,
"job_karma_high" = job_karma_high,
"job_karma_med" = job_karma_med,
"job_karma_low" = job_karma_low,
"flavor_text" = flavor_text,
"med_record" = med_record,
"sec_record" = sec_record,
"gen_record" = gen_record,
"playertitlelist" = (playertitlelist ? playertitlelist : ""), // This it intentnional. It wont work without it!
"disabilities" = disabilities,
"organlist" = (organlist ? organlist : ""),
"rlimblist" = (rlimblist ? rlimblist : ""),
"nanotrasen_relation" = nanotrasen_relation,
"speciesprefs" = speciesprefs,
"socks" = socks,
"body_accessory" = (body_accessory ? body_accessory : ""),
"gearlist" = (gearlist ? gearlist : ""),
"autohiss_mode" = autohiss_mode,
"ckey" = C.ckey,
"slot" = default_slot
)
)
if(!query.Execute())
var/err = query.ErrorMsg()
log_game("SQL ERROR during character slot saving. Error : \[[err]\]\n")
message_admins("SQL ERROR during character slot saving. Error : \[[err]\]\n")
if(!query.warn_execute())
qdel(firstquery)
qdel(query)
return
qdel(firstquery)
qdel(query)
return 1
qdel(firstquery)
var/DBQuery/query = GLOB.dbcon.NewQuery({"
var/datum/db_query/query = SSdbcore.NewQuery({"
INSERT INTO [format_table_name("characters")] (ckey, slot, OOC_Notes, real_name, name_is_always_random, gender,
age, species, language,
hair_colour, secondary_hair_colour,
@@ -421,57 +513,112 @@
socks, body_accessory, gear, autohiss)
VALUES
('[C.ckey]', '[default_slot]', '[sanitizeSQL(metadata)]', '[sanitizeSQL(real_name)]', '[be_random_name]','[gender]',
'[age]', '[sanitizeSQL(species)]', '[sanitizeSQL(language)]',
'[h_colour]', '[h_sec_colour]',
'[f_colour]', '[f_sec_colour]',
'[s_tone]', '[s_colour]',
'[sanitizeSQL(markingcolourslist)]',
'[hacc_colour]',
'[sanitizeSQL(h_style)]',
'[sanitizeSQL(f_style)]',
'[sanitizeSQL(markingstyleslist)]',
'[sanitizeSQL(ha_style)]',
'[sanitizeSQL(alt_head)]',
'[e_colour]',
'[underwear]', '[undershirt]',
'[backbag]', '[b_type]', '[alternate_option]',
'[job_support_high]', '[job_support_med]', '[job_support_low]',
'[job_medsci_high]', '[job_medsci_med]', '[job_medsci_low]',
'[job_engsec_high]', '[job_engsec_med]', '[job_engsec_low]',
'[job_karma_high]', '[job_karma_med]', '[job_karma_low]',
'[sanitizeSQL(flavor_text)]',
'[sanitizeSQL(med_record)]',
'[sanitizeSQL(sec_record)]',
'[sanitizeSQL(gen_record)]',
'[playertitlelist]',
'[disabilities]', '[organlist]', '[rlimblist]', '[nanotrasen_relation]', '[speciesprefs]',
'[socks]', '[body_accessory]', '[gearlist]', '[autohiss_mode]')
(:ckey, :slot, :metadata, :name, :be_random_name, :gender,
:age, :species, :language,
:h_colour, :h_sec_colour,
:f_colour, :f_sec_colour,
:s_tone, :s_colour,
:markingcolourslist,
:hacc_colour,
:h_style,
:f_style,
:markingstyleslist,
:ha_style,
:alt_head,
:e_colour,
:underwear, :undershirt,
:backbag, :b_type, :alternate_option,
:job_support_high, :job_support_med, :job_support_low,
:job_medsci_high, :job_medsci_med, :job_medsci_low,
:job_engsec_high, :job_engsec_med, :job_engsec_low,
:job_karma_high, :job_karma_med, :job_karma_low,
:flavor_text,
:med_record,
:sec_record,
:gen_record,
:playertitlelist,
:disabilities, :organlist, :rlimblist, :nanotrasen_relation, :speciesprefs,
:socks, :body_accessory, :gearlist, :autohiss_mode)
"}
)
"}, list(
// This has too many params for anyone to look at this without going insae
"ckey" = C.ckey,
"slot" = default_slot,
"metadata" = metadata,
"name" = real_name,
"be_random_name" = be_random_name,
"gender" = gender,
"age" = age,
"species" = species,
"language" = language,
"h_colour" = h_colour,
"h_sec_colour" = h_sec_colour,
"f_colour" = f_colour,
"f_sec_colour" = f_sec_colour,
"s_tone" = s_tone,
"s_colour" = s_colour,
"markingcolourslist" = markingcolourslist,
"hacc_colour" = hacc_colour,
"h_style" = h_style,
"f_style" = f_style,
"markingstyleslist" = markingstyleslist,
"ha_style" = ha_style,
"alt_head" = alt_head,
"e_colour" = e_colour,
"underwear" = underwear,
"undershirt" = undershirt,
"backbag" = backbag,
"b_type" = b_type,
"alternate_option" = alternate_option,
"job_support_high" = job_support_high,
"job_support_med" = job_support_med,
"job_support_low" = job_support_low,
"job_medsci_high" = job_medsci_high,
"job_medsci_med" = job_medsci_med,
"job_medsci_low" = job_medsci_low,
"job_engsec_high" = job_engsec_high,
"job_engsec_med" = job_engsec_med,
"job_engsec_low" = job_engsec_low,
"job_karma_high" = job_karma_high,
"job_karma_med" = job_karma_med,
"job_karma_low" = job_karma_low,
"flavor_text" = flavor_text,
"med_record" = med_record,
"sec_record" = sec_record,
"gen_record" = gen_record,
"playertitlelist" = (playertitlelist ? playertitlelist : ""), // This it intentnional. It wont work without it!
"disabilities" = disabilities,
"organlist" = (organlist ? organlist : ""),
"rlimblist" = (rlimblist ? rlimblist : ""),
"nanotrasen_relation" = nanotrasen_relation,
"speciesprefs" = speciesprefs,
"socks" = socks,
"body_accessory" = (body_accessory ? body_accessory : ""),
"gearlist" = (gearlist ? gearlist : ""),
"autohiss_mode" = autohiss_mode
))
if(!query.Execute())
var/err = query.ErrorMsg()
log_game("SQL ERROR during character slot saving. Error : \[[err]\]\n")
message_admins("SQL ERROR during character slot saving. Error : \[[err]\]\n")
if(!query.warn_execute())
qdel(query)
return
qdel(query)
saved = TRUE
return 1
/datum/preferences/proc/load_random_character_slot(client/C)
var/DBQuery/query = GLOB.dbcon.NewQuery("SELECT slot FROM [format_table_name("characters")] WHERE ckey='[C.ckey]' ORDER BY slot")
var/datum/db_query/query = SSdbcore.NewQuery("SELECT slot FROM [format_table_name("characters")] WHERE ckey=:ckey ORDER BY slot", list(
"ckey" = C.ckey
))
var/list/saves = list()
if(!query.Execute())
var/err = query.ErrorMsg()
log_game("SQL ERROR during random character slot picking. Error : \[[err]\]\n")
message_admins("SQL ERROR during random character slot picking. Error : \[[err]\]\n")
if(!query.warn_execute())
qdel(query)
return
while(query.NextRow())
saves += text2num(query.item[1])
qdel(query)
if(!saves.len)
load_character(C)
@@ -482,17 +629,31 @@
/datum/preferences/proc/clear_character_slot(client/C)
. = FALSE
// Is there a character in that slot?
var/DBQuery/query = GLOB.dbcon.NewQuery("SELECT slot FROM [format_table_name("characters")] WHERE ckey='[C.ckey]' AND slot='[default_slot]'")
query.Execute()
if(!query.RowCount())
var/datum/db_query/query = SSdbcore.NewQuery("SELECT slot FROM [format_table_name("characters")] WHERE ckey=:ckey AND slot=:slot", list(
"ckey" = C.ckey,
"slot" = default_slot
))
if(!query.warn_execute())
qdel(query)
return
var/DBQuery/query2 = GLOB.dbcon.NewQuery("DELETE FROM [format_table_name("characters")] WHERE ckey='[C.ckey]' AND slot='[default_slot]'")
if(!query2.Execute())
var/err = query2.ErrorMsg()
log_game("SQL ERROR during character slot clearing. Error : \[[err]\]\n")
message_admins("SQL ERROR during character slot clearing. Error : \[[err]\]\n")
if(!query.NextRow())
qdel(query)
return
qdel(query)
var/datum/db_query/delete_query = SSdbcore.NewQuery("DELETE FROM [format_table_name("characters")] WHERE ckey=:ckey AND slot=:slot", list(
"ckey" = C.ckey,
"slot" = default_slot
))
if(!delete_query.warn_execute())
qdel(delete_query)
return
qdel(delete_query)
saved = FALSE
return TRUE