mirror of
https://github.com/Citadel-Station-13/Citadel-Station-13-RP.git
synced 2026-08-28 00:36:32 +01:00
@@ -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)
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user