tgui embedding hooks (#6212)

Co-authored-by: silicons <no@you.cat>
This commit is contained in:
silicons
2023-12-26 22:49:06 +01:00
committed by GitHub
co-authored by silicons
parent 70313ff3e2
commit 20f4c5c7ff
221 changed files with 1115 additions and 999 deletions
+149
View File
@@ -0,0 +1,149 @@
GLOBAL_LIST_EMPTY(client_data)
/proc/resolve_client_data(ckey, key)
ckey = ckey(ckey) // just in case
if(!islist(GLOB.client_data))
// we CANNOT RUNTIME
GLOB.client_data = list()
if(!istype(GLOB.client_data[ckey], /datum/client_data))
GLOB.client_data[ckey] = new /datum/client_data(ckey, key)
return GLOB.client_data[ckey]
/**
* client data datums, to hold
* round-based data that we don't want wiped
* by a disconnect.
*/
/datum/client_data
/// owner ckey
var/ckey
/// owner key
var/key
/// absolutely, positively annihilated
var/ligma = FALSE
/// byond account join date
var/account_join
/// byond account age
var/account_age
/// is guest
var/is_guest
//* externally managed data *//
/// playtime - role string to number of minutes.
var/list/playtime
/// playtime was loaded
var/playtime_loaded = FALSE
/// playtime is loading or flushing
var/playtime_mutex = FALSE
/// playtime - queued for addition
var/list/playtime_queued = list()
/// last REALTIMEOFDAY we did queuing
var/playtime_last
/datum/client_data/New(ckey, key)
src.ckey = ckey
src.key = key
src.playtime_last = REALTIMEOFDAY
is_guest = IsGuestKey(key)
load_account_age()
var/list/the_cheese_touch = CONFIG_GET(keyed_list/shadowban)
var/client/C = GLOB.directory[src.ckey]
var/why
if("[C.ckey]" in the_cheese_touch)
why = "ckey"
src.ligma = TRUE
else if("[C.computer_id]" in the_cheese_touch)
why = "computerid"
src.ligma = TRUE
else if("[C.address]" in the_cheese_touch)
why = "IP address"
src.ligma = TRUE
if(src.ligma)
log_shadowban("[ckey] autobanned based on [why].")
message_admins(SPAN_DANGER("Automatically shadowbanning [ckey] based on configuration (matched on [why]). Varedit client.persistent.ligma to change this."))
/datum/client_data/proc/block_on_account_age_loaded(timeout = INFINITY)
var/timed_out = world.time + timeout
UNTIL(!isnull(account_age) || world.time > timed_out)
return account_age
/datum/client_data/proc/load_account_age()
if(is_guest)
account_age = 0
return
var/list/http = world.Export("http://byond.com/members/[ckey]?format=text")
if(!http)
log_world("Failed to connect to byond age check for [ckey]")
return
var/F = file2text(http["CONTENT"])
. = null
if(F)
// year-month-day
var/regex/R = regex("joined = \"(\\d{4}-\\d{2}-\\d{2})\"")
if(R.Find(F))
var/str = R.group[1]
account_join = str
if(!SSdbcore.Connect())
account_age = null
return
var/datum/db_query/query = SSdbcore.RunQuery(
"SELECT DATEDIFF(Now(), :date)",
list(
"date" = str,
)
)
if(query.NextRow())
. = text2num(query.item[1])
else
CRASH("Age check regex failed for [src.ckey]")
account_age = .
//* External - Playtime *//
/datum/client_data/proc/load_playtime()
set waitfor = FALSE
if(playtime_loaded)
return
// no args, injection proof; release proccall guard
var/old_usr = usr
usr = null
load_playtime_impl()
usr = old_usr
/datum/client_data/proc/load_playtime_impl()
PRIVATE_PROC(TRUE)
ASSERT(!playtime_mutex)
if(playtime_mutex)
return
playtime_mutex = TRUE
playtime = list()
var/player_id
var/client/client = GLOB.directory[ckey]
if(isnull(client))
playtime_mutex = FALSE
return
client.player.block_on_available()
// clients can be deleted at any time.
player_id = client?.player?.player_id
if(isnull(player_id))
playtime_mutex = FALSE
return
var/datum/db_query/query = SSdbcore.NewQuery(
"SELECT `roleid`, `minutes` FROM [format_table_name("playtime")] WHERE player = :player",
list(
"player" = player_id,
)
)
query.Execute()
while(query.NextRow())
playtime[query.item[1]] = text2num(query.item[2])
playtime_loaded = TRUE
playtime_mutex = FALSE
/datum/client_data/proc/block_on_playtime_loaded(timeout = INFINITY)
var/timed_out = world.time + timeout
load_playtime()
UNTIL(playtime_loaded || world.time > timed_out)
+237
View File
@@ -0,0 +1,237 @@
// todo: this has to be globally cached for new admin panel
/**
* holds db-related data
* loaded every connect
*/
/datum/player_data
//! intrinsics
/// our ckey
var/ckey
/// our key
var/key
/// available: null if don't know yet, FALSE if no dbcon, TRUE if loaded
var/available
/// is guest
var/is_guest
/// loading?
var/loading = FALSE
/// saving?
var/saving = FALSE
// todo: this lock system sucks ass
// todo: it should realistically be able to queue if something modifies us a lot
//! loaded data
/// player id
var/player_id
/// player flags
var/player_flags = NONE
/// player age
var/player_age
/// join date
var/player_first_seen
/datum/player_data/New(key)
src.ckey = ckey(key)
if(!src.ckey)
return
src.key = key
is_guest = IsGuestKey(key)
load()
/**
* async
*/
/datum/player_data/proc/load()
if(!SSdbcore.Connect())
if(isnull(available))
available = FALSE
return FALSE
INVOKE_ASYNC(src, PROC_REF(load_blocking))
return TRUE
/datum/player_data/proc/load_blocking()
// allow admin proccalls - there's no args here.
var/was_proccall = !!IsAdminAdvancedProcCall()
var/old_usr = usr
usr = null
_load_lock(was_proccall)
usr = old_usr
/datum/player_data/proc/_load_lock(was_proccall)
if(IsAdminAdvancedProcCall())
return
if(loading)
if(!was_proccall)
CRASH("loading is still locked; why are we loading this fast?")
return
loading = TRUE
_load()
loading = FALSE
/datum/player_data/proc/_load()
if(IsAdminAdvancedProcCall())
return
if(IsGuestKey(key))
return
var/datum/db_query/lookup
lookup = SSdbcore.ExecuteQuery(
"SELECT id, playerid, firstseen FROM [format_table_name("player_lookup")] WHERE ckey = :ckey",
list(
"ckey" = ckey
)
)
if(!lookup.NextRow())
CRASH("failed to load lookup data")
var/lookup_id = lookup.item[1]
var/lookup_pid = lookup.item[2]
var/lookup_firstseen = lookup.item[3]
if(lookup_pid)
if(istext(lookup_id))
lookup_id = text2num(lookup_id)
if(istext(lookup_pid))
lookup_pid = text2num(lookup_pid)
qdel(lookup)
lookup = SSdbcore.ExecuteQuery(
"SELECT id, flags, datediff(Now(), firstseen), firstseen FROM [format_table_name("player")] WHERE id = :id",
list(
"id" = lookup_pid
)
)
if(lookup.NextRow())
// found!
var/lookup_flags = lookup.item[2]
var/lookup_age = lookup.item[3]
if(istext(lookup_flags))
lookup_flags = text2num(lookup_flags)
if(istext(lookup_age))
lookup_age = text2num(lookup_age)
player_id = lookup_pid
player_flags = lookup_flags
player_first_seen = lookup.item[4]
player_age = lookup_age
qdel(lookup)
available = TRUE
else
CRASH("failed to lookup player row on id [lookup_pid] even though id was present in player_lookup.")
else
qdel(lookup)
register_new_player(lookup_firstseen, lookup_id)
/datum/player_data/proc/register_new_player(migrate_firstseen, lookup_id)
if(IsAdminAdvancedProcCall())
return
// new person!
player_age = 0
player_flags = NONE
var/datum/db_query/insert
if(migrate_firstseen)
insert = SSdbcore.ExecuteQuery(
"INSERT INTO [format_table_name("player")] (flags, firstseen, lastseen) VALUES (:flags, :fs, Now())",
list(
"flags" = player_flags,
"fs" = migrate_firstseen
)
)
player_first_seen = migrate_firstseen
else
insert = SSdbcore.ExecuteQuery(
"INSERT INTO [format_table_name("player")] (flags, firstseen, lastseen) VALUES (:flags, Now(), Now())",
list(
"flags" = player_flags,
)
)
player_first_seen = time_stamp()
var/insert_id = insert.last_insert_id
if(istext(insert_id))
insert_id = text2num(insert_id)
if(!isnum(insert_id))
CRASH("invalid insert id??")
player_id = insert_id
qdel(insert)
// now update lookup
insert = SSdbcore.ExecuteQuery(
"UPDATE [format_table_name("player_lookup")] SET playerid = :pid WHERE id = :id",
list(
"id" = lookup_id,
"pid" = insert_id
)
)
qdel(insert)
available = TRUE
// todo: lookup DATEDIFF so player age is set for the first connect after migrating a ckey.
/**
* async
*/
/datum/player_data/proc/save()
set waitfor = FALSE
// why are we in here if we're write locked?
if(saving)
CRASH("write locked")
// don't fuck with things if we're read-locked
UNTIL(!loading)
// check again
if(saving)
CRASH("write locked")
// allow admin proccalls - there's no args here.
var/old_usr = usr
usr = null
// don't lock; if something is spamming our flags they probably shouldn't be
saving = TRUE
_save()
saving = FALSE
usr = old_usr
/datum/player_data/proc/_save()
qdel(SSdbcore.ExecuteQuery(
"UPDATE [format_table_name("player")] SET flags = :flags WHERE id = :id",
list(
"flags" = player_flags,
"id" = player_id
)
))
/**
* async
*/
/datum/player_data/proc/log_connect()
set waitfor = FALSE
update_last_seen()
/datum/player_data/proc/update_last_seen()
// don't interrupt
if(!block_on_available())
return FALSE
qdel(SSdbcore.ExecuteQuery(
"UPDATE [format_table_name("player")] SET lastseen = Now() WHERE id = :id",
list(
"id" = player_id
)
))
return TRUE
/**
* sync
*/
/datum/player_data/proc/player_age()
UNTIL(!loading)
return player_age
/**
* block until we know if we're available
* then return if we are
*
* WARNING: without database, or if this is for a guest key, we will never be available.
*/
/datum/player_data/proc/block_on_available(timeout = INFINITY)
var/timed_out = world.time + timeout
UNTIL(!isnull(available) || world.time > timed_out)
return available
/**
* returns if we're available
* if we don't know yet, return false
*/
/datum/player_data/proc/immediately_available()
return !!available