diff --git a/code/controllers/subsystem/dbcore.dm b/code/controllers/subsystem/dbcore.dm
index 23ede3c084b..8de6b77cdbb 100644
--- a/code/controllers/subsystem/dbcore.dm
+++ b/code/controllers/subsystem/dbcore.dm
@@ -265,15 +265,17 @@ SUBSYSTEM_DEF(dbcore)
* * warn - Boolean to warn on query failure
* * qdel - Boolean to enable auto qdel of queries
* * assoc - Boolean to enable support for an associative list of queries
+ * * log - Do we want to generate logs for these queries
*/
-/datum/controller/subsystem/dbcore/proc/MassExecute(list/querys, warn = FALSE, qdel = FALSE, assoc = FALSE)
+/datum/controller/subsystem/dbcore/proc/MassExecute(list/querys, warn = FALSE, qdel = FALSE, assoc = FALSE, log = TRUE)
if(!islist(querys))
if(!istype(querys, /datum/db_query))
CRASH("Invalid query passed to MassExecute: [querys]")
querys = list(querys)
var/start_time = start_watch()
- log_debug("Mass executing [length(querys)] queries...")
+ if(log)
+ log_debug("Mass executing [length(querys)] queries...")
for(var/thing in querys)
var/datum/db_query/query
@@ -296,7 +298,8 @@ SUBSYSTEM_DEF(dbcore)
if(qdel)
qdel(query)
- log_debug("Executed [length(querys)] queries in [stop_watch(start_time)]s")
+ if(log)
+ log_debug("Executed [length(querys)] queries in [stop_watch(start_time)]s")
/**
* # db_query
diff --git a/code/controllers/subsystem/jobs.dm b/code/controllers/subsystem/jobs.dm
index 56e877f6f8d..f82964ad4e0 100644
--- a/code/controllers/subsystem/jobs.dm
+++ b/code/controllers/subsystem/jobs.dm
@@ -1,7 +1,7 @@
SUBSYSTEM_DEF(jobs)
name = "Jobs"
init_order = INIT_ORDER_JOBS // 12
- wait = 3000 // 5 minutes (Deciseconds)
+ wait = 5 MINUTES // Dont ever make this a super low value since EXP updates are calculated from this value
runlevels = RUNLEVEL_GAME
offline_implications = "Job playtime hours will no longer be logged. No immediate action is needed."
@@ -25,9 +25,9 @@ SUBSYSTEM_DEF(jobs)
// Only fires every 5 minutes
/datum/controller/subsystem/jobs/fire()
- if(!config.sql_enabled || !config.use_exp_tracking)
+ if(!SSdbcore.IsConnected() || !config.use_exp_tracking)
return
- INVOKE_ASYNC(GLOBAL_PROC, /.proc/update_exp, 5, 0)
+ batch_update_player_exp(announce = FALSE) // Set this to true if you ever want to inform players about their EXP gains
/datum/controller/subsystem/jobs/proc/SetupOccupations(var/list/faction = list("Station"))
occupations = list()
@@ -726,3 +726,139 @@ SUBSYSTEM_DEF(jobs)
new_id_change_records["[id_change_counter]"] = thisrecord
id_change_counter++
id_change_records = new_id_change_records
+
+// This proc will update all players EXP at once. It will calculate amount of time to add dynamically based on the SS fire time.
+/datum/controller/subsystem/jobs/proc/batch_update_player_exp(announce = FALSE)
+ // Right off the bat
+ var/start_time = start_watch()
+ // First calculate minutes
+ var/divider = 10 // By default, 10 deciseconds in 1 second
+ if(flags & SS_TICKER)
+ divider = 20 // If this SS ever gets made into a ticker SS, account for that
+
+ var/minutes = (wait / divider) / 60 // Calculate minutes based on the SS wait time (How often this proc fires)
+
+ // Step 1: Get us a list of clients to process
+ var/list/client/clients_to_process = GLOB.clients.Copy() // This is copied so that clients joining in the middle of this dont break things
+ Debug("Starting EXP update for [length(clients_to_process)] clients. (Adding [minutes] minutes)")
+
+ var/list/datum/db_query/select_queries = list() // List of SELECT queries to mass grab EXP.
+
+ for(var/i in clients_to_process)
+ var/client/C = i
+ if(!C)
+ continue // If a client logs out in the middle of this
+
+ var/datum/db_query/exp_read = SSdbcore.NewQuery(
+ "SELECT exp FROM [format_table_name("player")] WHERE ckey=:ckey",
+ list("ckey" = C.ckey)
+ )
+
+ select_queries[C.ckey] = exp_read
+
+ var/list/read_records = list()
+ // Explanation for parameters:
+ // TRUE: We want warnings if these fail
+ // FALSE: Do NOT qdel() queries here, otherwise they wont be read. At all.
+ // TRUE: This is an assoc list, so it needs to prepare for that
+ // FALSE: We dont want to logspam
+ SSdbcore.MassExecute(select_queries, TRUE, FALSE, TRUE, FALSE) // Batch execute so we can take advantage of async magic
+
+ for(var/i in clients_to_process)
+ var/client/C = i
+ if(!C)
+ continue // If a client logs out in the middle of this
+
+ if(select_queries[C.ckey]) // This check should not be necessary, but I am paranoid
+ while(select_queries[C.ckey].NextRow())
+ read_records[C.ckey] = params2list(select_queries[C.ckey].item[1])
+
+ QDEL_LIST_ASSOC_VAL(select_queries) // Clean stuff up
+
+ var/list/play_records = list()
+
+ var/list/datum/db_query/player_update_queries = list() // List of queries to update player EXP
+ var/list/datum/db_query/playtime_history_update_queries = list() // List of queries to update the playtime history table
+
+ for(var/i in clients_to_process)
+ var/client/C = i
+ if(!C)
+ continue // If a client logs out in the middle of this
+ // Get us a container
+ play_records[C.ckey] = list()
+ for(var/rtype in GLOB.exp_jobsmap)
+ if(text2num(read_records[C.ckey][rtype]))
+ play_records[C.ckey][rtype] = text2num(read_records[C.ckey][rtype])
+ else
+ play_records[C.ckey][rtype] = 0
+
+
+ var/myrole
+ if(C.mob.mind)
+ if(C.mob.mind.playtime_role)
+ myrole = C.mob.mind.playtime_role
+ else if(C.mob.mind.assigned_role)
+ myrole = C.mob.mind.assigned_role
+
+ var/added_living = 0
+ var/added_ghost = 0
+ if(C.mob.stat == CONSCIOUS && myrole)
+ play_records[C.ckey][EXP_TYPE_LIVING] += minutes
+ added_living += minutes
+
+ if(announce)
+ to_chat(C.mob, "You got: [minutes] Living EXP!")
+
+ for(var/category in GLOB.exp_jobsmap)
+ if(GLOB.exp_jobsmap[category]["titles"])
+ if(myrole in GLOB.exp_jobsmap[category]["titles"])
+ play_records[C.ckey][category] += minutes
+ if(announce)
+ to_chat(C.mob, "You got: [minutes] [category] EXP!")
+
+ if(C.mob.mind.special_role)
+ play_records[C.ckey][EXP_TYPE_SPECIAL] += minutes
+ if(announce)
+ to_chat(C.mob, "You got: [minutes] Special EXP!")
+
+ else if(isobserver(C.mob))
+ play_records[C.ckey][EXP_TYPE_GHOST] += minutes
+ added_ghost += minutes
+ if(announce)
+ to_chat(C.mob, "You got: [minutes] Ghost EXP!")
+ else
+ continue
+
+ var/new_exp = list2params(play_records[C.ckey])
+
+ C.prefs.exp = new_exp
+
+ var/datum/db_query/update_query = SSdbcore.NewQuery(
+ "UPDATE [format_table_name("player")] SET exp =:newexp, lastseen=NOW() WHERE ckey=:ckey",
+ list(
+ "newexp" = new_exp,
+ "ckey" = C.ckey
+ )
+ )
+
+ player_update_queries += update_query
+
+ var/datum/db_query/update_query_history = SSdbcore.NewQuery({"
+ INSERT INTO [format_table_name("playtime_history")] (ckey, date, time_living, time_ghost)
+ VALUES (:ckey, CURDATE(), :addedliving, :addedghost)
+ ON DUPLICATE KEY UPDATE time_living=time_living + VALUES(time_living), time_ghost=time_ghost + VALUES(time_ghost)"},
+ list(
+ "ckey" = C.ckey,
+ "addedliving" = added_living,
+ "addedghost" = added_ghost
+ )
+ )
+
+ playtime_history_update_queries += update_query_history
+
+
+ // warn=TRUE, qdel=TRUE, assoc=FALSE, log=FALSE
+ SSdbcore.MassExecute(player_update_queries, TRUE, TRUE, FALSE, FALSE) // Batch execute so we can take advantage of async magic
+ SSdbcore.MassExecute(playtime_history_update_queries, TRUE, TRUE, FALSE, FALSE)
+
+ Debug("Successfully updated all EXP data in [stop_watch(start_time)]s")
diff --git a/code/game/jobs/job_exp.dm b/code/game/jobs/job_exp.dm
index f0483e5c17a..54b5201baa0 100644
--- a/code/game/jobs/job_exp.dm
+++ b/code/game/jobs/job_exp.dm
@@ -224,97 +224,3 @@ GLOBAL_LIST_INIT(role_playtime_requirements, list(
else
return "none"
-/proc/update_exp(mins = 0, ann = 0)
- if(!SSdbcore.IsConnected())
- return
- for(var/client/L in GLOB.clients)
- if(L.inactivity >= (10 MINUTES))
- continue
- L.update_exp_client(mins, ann)
- CHECK_TICK
-
-/client/proc/update_exp_client(minutes = 0, announce_changes = 0)
- if(!src || !ckey || !SSdbcore.IsConnected())
- return
-
- var/datum/db_query/exp_read = SSdbcore.NewQuery(
- "SELECT exp FROM [format_table_name("player")] WHERE ckey=:ckey",
- list("ckey" = ckey)
- )
-
- if(!exp_read.warn_execute())
- qdel(exp_read)
- return FALSE
-
- var/list/read_records = list()
- var/hasread = FALSE
- while(exp_read.NextRow())
- read_records = params2list(exp_read.item[1])
- hasread = TRUE
-
- qdel(exp_read)
-
- if(!hasread)
- return
-
- var/list/play_records = list()
- for(var/rtype in GLOB.exp_jobsmap)
- if(text2num(read_records[rtype]))
- play_records[rtype] = text2num(read_records[rtype])
- else
- play_records[rtype] = 0
- var/myrole
- if(mob.mind)
- if(mob.mind.playtime_role)
- myrole = mob.mind.playtime_role
- else if(mob.mind.assigned_role)
- myrole = mob.mind.assigned_role
- var/added_living = 0
- var/added_ghost = 0
- if(mob.stat == CONSCIOUS && myrole)
- play_records[EXP_TYPE_LIVING] += minutes
- added_living += minutes
- if(announce_changes)
- to_chat(mob,"You got: [minutes] Living EXP!")
- for(var/category in GLOB.exp_jobsmap)
- if(GLOB.exp_jobsmap[category]["titles"])
- if(myrole in GLOB.exp_jobsmap[category]["titles"])
- play_records[category] += minutes
- if(announce_changes)
- to_chat(mob,"You got: [minutes] [category] EXP!")
- if(mob.mind.special_role)
- play_records[EXP_TYPE_SPECIAL] += minutes
- if(announce_changes)
- to_chat(mob,"You got: [minutes] Special EXP!")
- else if(isobserver(mob))
- play_records[EXP_TYPE_GHOST] += minutes
- added_ghost += minutes
- if(announce_changes)
- to_chat(mob,"You got: [minutes] Ghost EXP!")
- else
- return
- var/new_exp = list2params(play_records)
- prefs.exp = new_exp
-
- var/datum/db_query/update_query = SSdbcore.NewQuery(
- "UPDATE [format_table_name("player")] SET exp =:newexp, lastseen=NOW() WHERE ckey=:ckey",
- list(
- "newexp" = new_exp,
- "ckey" = ckey
- )
- )
- update_query.warn_execute()
- qdel(update_query)
-
- var/datum/db_query/update_query_history = SSdbcore.NewQuery({"
- INSERT INTO [format_table_name("playtime_history")] (ckey, date, time_living, time_ghost)
- VALUES (:ckey, CURDATE(), :addedliving, :addedghost)
- ON DUPLICATE KEY UPDATE time_living=time_living + VALUES(time_living), time_ghost=time_ghost + VALUES(time_ghost)"},
- list(
- "ckey" = ckey,
- "addedliving" = added_living,
- "addedghost" = added_ghost
- )
- )
- update_query_history.warn_execute()
- qdel(update_query_history)