mirror of
https://github.com/Citadel-Station-13/Citadel-Station-13-RP.git
synced 2026-08-18 13:06:56 +01:00
444 lines
13 KiB
Plaintext
444 lines
13 KiB
Plaintext
//* This file is explicitly licensed under the MIT license. *//
|
|
//* Copyright (c) 2023 Citadel Station developers. *//
|
|
|
|
/**
|
|
* objects module
|
|
*
|
|
* handles static and dynamic object persistence on given map levels
|
|
*/
|
|
/datum/controller/subsystem/persistence
|
|
|
|
/datum/controller/subsystem/persistence/proc/level_objects_store_static(list/obj/entities, generation, level_id, map_id)
|
|
if(!SSdbcore.Connect())
|
|
return FALSE
|
|
|
|
var/intentionally_allow_admin_proccall = usr
|
|
usr = null
|
|
|
|
for(var/obj/entity as anything in entities)
|
|
var/datum/db_query/query
|
|
switch(entity.obj_persist_static_mode)
|
|
if(OBJ_PERSIST_STATIC_MODE_LEVEL)
|
|
query = SSdbcore.NewQuery(
|
|
"INSERT INTO [DB_PREFIX_TABLE_NAME("persistence_static_level_objects")] (generation, object_id, level_id, data) \
|
|
VALUES (:generation, :object_id, :level_id, :data) ON DUPLICATE KEY UPDATE \
|
|
data = VALUES(data)",
|
|
list(
|
|
"generation" = generation,
|
|
"object_id" = entity.obj_persist_static_id,
|
|
"level_id" = entity.obj_persist_static_bound_id || level_id,
|
|
"data" = safe_json_encode(entity.serialize()),
|
|
),
|
|
)
|
|
if(OBJ_PERSIST_STATIC_MODE_MAP)
|
|
query = SSdbcore.NewQuery(
|
|
"INSERT INTO [DB_PREFIX_TABLE_NAME("persistence_static_map_objects")] (generation, object_id, map_id, data) \
|
|
VALUES (:generation, :object_id, :map_id, :data) ON DUPLICATE KEY UPDATE \
|
|
data = VALUES(data)",
|
|
list(
|
|
"generation" = generation,
|
|
"object_id" = entity.obj_persist_static_id,
|
|
"map_id" = entity.obj_persist_static_bound_id || map_id,
|
|
"data" = safe_json_encode(entity.serialize()),
|
|
),
|
|
)
|
|
if(OBJ_PERSIST_STATIC_MODE_GLOBAL)
|
|
query = SSdbcore.NewQuery(
|
|
"INSERT INTO [DB_PREFIX_TABLE_NAME("persistence_static_global_objects")] (generation, object_id, data) \
|
|
VALUES (:generation, :object_id, :data) ON DUPLICATE KEY UPDATE \
|
|
data = VALUES(data)",
|
|
list(
|
|
"generation" = generation,
|
|
"object_id" = entity.obj_persist_static_id,
|
|
"data" = safe_json_encode(entity.serialize()),
|
|
),
|
|
)
|
|
else
|
|
stack_trace("unrecognized mode [entity.obj_persist_static_mode]")
|
|
continue
|
|
query.warn_execute()
|
|
QDEL_NULL(query)
|
|
|
|
entity.obj_persist_status |= OBJ_PERSIST_STATUS_SAVED
|
|
|
|
usr = intentionally_allow_admin_proccall
|
|
|
|
return TRUE
|
|
|
|
/**
|
|
* **warning** - does not dedupe. don't directly call unless you know what you're doing!
|
|
*/
|
|
/datum/controller/subsystem/persistence/proc/level_objects_store_dynamic(list/obj/entities, generation, level_id)
|
|
if(!SSdbcore.Connect())
|
|
return FALSE
|
|
|
|
var/intentionally_allow_admin_proccall = usr
|
|
usr = null
|
|
|
|
for(var/obj/entity as anything in entities)
|
|
var/datum/db_query/query
|
|
if(entity.obj_persist_dynamic_id != PERSISTENCE_DYNAMIC_ID_AUTOSET)
|
|
query = SSdbcore.NewQuery(
|
|
"INSERT INTO [DB_PREFIX_TABLE_NAME("persistence_dynamic_objects")] (id, generation, status, data, prototype_id, level_id, x, y) \
|
|
VALUES (:status, :data, :prototype, :level, :x, :y) ON DUPLICATE KEY UPDATE \
|
|
x = VALUES(x), y = VALUES(y), data = VALUES(data), prototype = VALUES(prototype), level = VALUES(level), \
|
|
status = VALUES(status)",
|
|
list(
|
|
"id" = entity.obj_persist_dynamic_id,
|
|
"generation" = generation,
|
|
"status" = entity.obj_persist_dynamic_status,
|
|
"data" = safe_json_encode(entity.serialize()),
|
|
"prototype" = "[entity.type]",
|
|
"level" = level_id,
|
|
"x" = entity.x,
|
|
"y" = entity.y,
|
|
)
|
|
)
|
|
query.warn_execute()
|
|
else
|
|
query = SSdbcore.NewQuery(
|
|
"INSERT INTO [DB_PREFIX_TABLE_NAME("persistence_dynamic_objects")] (status, data, prototype_id, level_id, x, y) \
|
|
VALUES (:status, :data, :prototype, :level, :x, :y)",
|
|
list(
|
|
"status" = entity.obj_persist_dynamic_status,
|
|
"data" = safe_json_encode(entity.serialize()),
|
|
"prototype" = "[entity.type]",
|
|
"level" = level_id,
|
|
"x" = entity.x,
|
|
"y" = entity.y,
|
|
)
|
|
)
|
|
query.warn_execute()
|
|
entity.obj_persist_dynamic_id = query.last_insert_id
|
|
entity.obj_persist_status |= OBJ_PERSIST_STATUS_FIRST_GENERATION
|
|
entity.obj_persist_status |= OBJ_PERSIST_STATUS_SAVED
|
|
QDEL_NULL(query)
|
|
|
|
usr = intentionally_allow_admin_proccall
|
|
|
|
return TRUE
|
|
|
|
/**
|
|
* @return list(count loaded, count dropped, count errored)
|
|
*/
|
|
/datum/controller/subsystem/persistence/proc/level_objects_load_dynamic(datum/map_level_persistence/level_data, z_index)
|
|
if(!SSdbcore.Connect())
|
|
return FALSE
|
|
|
|
var/count_loaded = 0
|
|
var/count_dropped = 0
|
|
var/count_errored = 0
|
|
|
|
var/generation = level_data.generation
|
|
var/level_id = level_data.level_id
|
|
|
|
// todo: anti-dupe system
|
|
|
|
var/intentionally_allow_admin_proccall = usr
|
|
usr = null
|
|
|
|
var/datum/db_query/query = SSdbcore.NewQuery(
|
|
"SELECT object_id, prototype_id, status, data, x, y \
|
|
FROM [DB_PREFIX_TABLE_NAME("persistence_dynamic_objects")] \
|
|
WHERE level_id = :level AND generation = :generation",
|
|
list(
|
|
"generation" = generation,
|
|
"level" = level_id,
|
|
)
|
|
)
|
|
query.warn_execute()
|
|
|
|
while(query.NextRow())
|
|
var/object_id = query.item[1]
|
|
var/prototype_id = query.item[2]
|
|
var/status = query.item[3]
|
|
var/data_encoded = query.item[4]
|
|
var/x = query.item[5]
|
|
var/y = query.item[6]
|
|
|
|
// resolve prototype
|
|
var/object_type = text2path(prototype_id)
|
|
if(isnull(object_type))
|
|
count_dropped++
|
|
continue
|
|
|
|
var/obj/deserializing = new object_type(locate(x, y, z_index))
|
|
deserializing.obj_persist_dynamic_id = object_id
|
|
deserializing.obj_persist_dynamic_status = status
|
|
deserializing.deserialize(json_decode(data_encoded))
|
|
deserializing.decay_persisted(level_data.rounds_since_saved, level_data.hours_since_saved)
|
|
deserializing.obj_persist_status |= OBJ_PERSIST_STATUS_LOADED
|
|
count_loaded++
|
|
|
|
QDEL_NULL(query)
|
|
|
|
usr = intentionally_allow_admin_proccall
|
|
|
|
return list(count_loaded, count_dropped, count_errored)
|
|
|
|
/**
|
|
* @return list(count loaded, count dropped, count errored)
|
|
*/
|
|
/datum/controller/subsystem/persistence/proc/level_objects_load_static(datum/map_level_persistence/level_data, list/obj/entities)
|
|
if(!SSdbcore.Connect())
|
|
return FALSE
|
|
|
|
var/count_loaded = 0
|
|
var/count_dropped = 0
|
|
var/count_errored = 0
|
|
|
|
var/generation = level_data.generation
|
|
var/level_id = level_data.level_id
|
|
var/map_id = level_data.map_id
|
|
|
|
var/intentionally_allow_admin_proccall = usr
|
|
usr = null
|
|
|
|
for(var/obj/entity as anything in entities)
|
|
var/datum/db_query/query
|
|
var/bind_id
|
|
switch(entity.obj_persist_static_mode)
|
|
if(OBJ_PERSIST_STATIC_MODE_GLOBAL)
|
|
query = SSdbcore.NewQuery(
|
|
"SELECT data FROM [DB_PREFIX_TABLE_NAME("persistence_static_global_objects")] \
|
|
WHERE object_id = :object AND generation = :generation",
|
|
list(
|
|
"object" = entity.obj_persist_static_id,
|
|
"generation" = generation,
|
|
),
|
|
)
|
|
if(OBJ_PERSIST_STATIC_MODE_LEVEL)
|
|
query = SSdbcore.NewQuery(
|
|
"SELECT data FROM [DB_PREFIX_TABLE_NAME("persistence_static_level_objects")] \
|
|
WHERE object_id = :object AND level_id = :level AND generation = :generation",
|
|
list(
|
|
"object" = entity.obj_persist_static_id,
|
|
"level" = level_id,
|
|
"generation" = generation,
|
|
),
|
|
)
|
|
bind_id = level_id
|
|
if(OBJ_PERSIST_STATIC_MODE_MAP)
|
|
query = SSdbcore.NewQuery(
|
|
"SELECT data FROM [DB_PREFIX_TABLE_NAME("persistence_static_map_objects")] \
|
|
WHERE object_id = :object AND map_id = :map AND generation = :generation",
|
|
list(
|
|
"object" = entity.obj_persist_static_id,
|
|
"map" = map_id,
|
|
"generation" = generation,
|
|
),
|
|
)
|
|
bind_id = map_id
|
|
else
|
|
stack_trace("unrecognized mode [entity.obj_persist_static_mode]")
|
|
continue
|
|
query.warn_execute(FALSE)
|
|
if(!query.NextRow())
|
|
continue
|
|
var/json_data = query.item[1]
|
|
entity.deserialize(json_decode(json_data))
|
|
entity.decay_persisted(level_data.rounds_since_saved, level_data.hours_since_saved)
|
|
entity.obj_persist_status |= OBJ_PERSIST_STATUS_LOADED
|
|
if(!isnull(bind_id))
|
|
entity.obj_persist_static_bound_id = bind_id
|
|
count_loaded++
|
|
CHECK_TICK
|
|
QDEL_NULL(query)
|
|
|
|
usr = intentionally_allow_admin_proccall
|
|
|
|
return list(count_loaded, count_dropped, count_errored)
|
|
|
|
/datum/controller/subsystem/persistence/proc/level_objects_drop_static_all()
|
|
if(!SSdbcore.Connect())
|
|
return FALSE
|
|
|
|
var/intentionally_allow_admin_proccall = usr
|
|
usr = null
|
|
|
|
SSdbcore.dangerously_block_on_multiple_unsanitized_queries(
|
|
list(
|
|
"TRUNCATE TABLE [DB_PREFIX_TABLE_NAME("persistence_static_map_objects")]",
|
|
"TRUNCATE TABLE [DB_PREFIX_TABLE_NAME("persistence_static_level_objects")]",
|
|
"TRUNCATE TABLE [DB_PREFIX_TABLE_NAME("persistence_static_global_objects")]",
|
|
),
|
|
)
|
|
|
|
usr = intentionally_allow_admin_proccall
|
|
|
|
return TRUE
|
|
|
|
/datum/controller/subsystem/persistence/proc/level_objects_drop_static_level(level_id)
|
|
if(!SSdbcore.Connect())
|
|
return FALSE
|
|
|
|
var/intentionally_allow_admin_proccall = usr
|
|
usr = null
|
|
|
|
SSdbcore.RunQuery(
|
|
"DELETE FROM [DB_PREFIX_TABLE_NAME("persistence_static_level_objects")] WHERE level_id = :level",
|
|
list(
|
|
"level" = level_id,
|
|
),
|
|
)
|
|
|
|
usr = intentionally_allow_admin_proccall
|
|
|
|
return TRUE
|
|
|
|
/datum/controller/subsystem/persistence/proc/level_objects_drop_static_map(map_id)
|
|
if(!SSdbcore.Connect())
|
|
return FALSE
|
|
|
|
var/intentionally_allow_admin_proccall = usr
|
|
usr = null
|
|
|
|
SSdbcore.RunQuery(
|
|
"DELETE FROM [DB_PREFIX_TABLE_NAME("persistence_static_map_objects")] WHERE map_id = :map",
|
|
list(
|
|
"map" = map_id,
|
|
),
|
|
)
|
|
|
|
usr = intentionally_allow_admin_proccall
|
|
|
|
return TRUE
|
|
|
|
/datum/controller/subsystem/persistence/proc/level_objects_drop_static_global()
|
|
if(!SSdbcore.Connect())
|
|
return FALSE
|
|
|
|
var/intentionally_allow_admin_proccall = usr
|
|
usr = null
|
|
|
|
SSdbcore.RunQuery(
|
|
"DELETE FROM [DB_PREFIX_TABLE_NAME("persistence_static_global_objects")]",
|
|
)
|
|
|
|
usr = intentionally_allow_admin_proccall
|
|
|
|
return TRUE
|
|
|
|
/datum/controller/subsystem/persistence/proc/level_objects_drop_dynamic_all()
|
|
if(!SSdbcore.Connect())
|
|
return FALSE
|
|
|
|
var/intentionally_allow_admin_proccall = usr
|
|
usr = null
|
|
|
|
SSdbcore.RunQuery(
|
|
"TRUNCATE TABLE [DB_PREFIX_TABLE_NAME("persistence_dynamic_objects")]",
|
|
)
|
|
|
|
usr = intentionally_allow_admin_proccall
|
|
|
|
return TRUE
|
|
|
|
/datum/controller/subsystem/persistence/proc/level_objects_drop_dynamic_level(level_id)
|
|
if(!SSdbcore.Connect())
|
|
return FALSE
|
|
|
|
var/intentionally_allow_admin_proccall = usr
|
|
usr = null
|
|
|
|
SSdbcore.RunQuery(
|
|
"DELETE FROM [DB_PREFIX_TABLE_NAME("persistence_dynamic_objects")] WHERE level_id = :level",
|
|
list(
|
|
"level" = level_id,
|
|
),
|
|
)
|
|
|
|
usr = intentionally_allow_admin_proccall
|
|
|
|
return TRUE
|
|
|
|
/**
|
|
* @return list(static objects)
|
|
*/
|
|
/datum/controller/subsystem/persistence/proc/level_objects_gather_world_static()
|
|
. = list()
|
|
|
|
for(var/obj/thing in world)
|
|
// don't lock up the server
|
|
CHECK_TICK
|
|
// we only care about things on turfs
|
|
if(!isturf(thing.loc))
|
|
continue
|
|
// check flags
|
|
if(thing.obj_persist_status & OBJ_PERSIST_STATUS_NO_THANK_YOU)
|
|
continue
|
|
// are they static?
|
|
if(thing.obj_persist_static_id)
|
|
. += thing
|
|
|
|
/**
|
|
* @return list(static objects)
|
|
*/
|
|
/datum/controller/subsystem/persistence/proc/level_objects_gather_level_static(z)
|
|
. = list()
|
|
|
|
for(var/obj/thing in world)
|
|
// don't lock up the server
|
|
CHECK_TICK
|
|
// we only care about things on turfs
|
|
// and since if you're not on a turf, z is 0, this works anyways lol
|
|
if(thing.z != z)
|
|
continue
|
|
// check flags
|
|
if(thing.obj_persist_status & OBJ_PERSIST_STATUS_NO_THANK_YOU)
|
|
continue
|
|
// are they static?
|
|
if(thing.obj_persist_static_id)
|
|
. += thing
|
|
|
|
/**
|
|
* @return list(list(static objects), list(dynamic objects))
|
|
*/
|
|
/datum/controller/subsystem/persistence/proc/level_objects_gather_world()
|
|
var/list/static_objects = list()
|
|
var/list/dynamic_objects = list()
|
|
|
|
for(var/obj/thing in world)
|
|
// don't lock up the server
|
|
CHECK_TICK
|
|
// we only care about things on turfs
|
|
if(!isturf(thing.loc))
|
|
continue
|
|
// check flags
|
|
if(thing.obj_persist_status & OBJ_PERSIST_STATUS_NO_THANK_YOU)
|
|
continue
|
|
// are they static?
|
|
if(thing.obj_persist_static_id)
|
|
static_objects += thing
|
|
// are they dynamic?
|
|
else if(thing.obj_persist_dynamic_id)
|
|
dynamic_objects += thing
|
|
|
|
return list(static_objects, dynamic_objects)
|
|
|
|
/**
|
|
* @return list(list(static objects), list(dynamic objects))
|
|
*/
|
|
/datum/controller/subsystem/persistence/proc/level_objects_gather_level(z)
|
|
var/list/static_objects = list()
|
|
var/list/dynamic_objects = list()
|
|
|
|
for(var/obj/thing in world)
|
|
// don't lock up the server
|
|
CHECK_TICK
|
|
// we only care about things on turfs
|
|
// and since if you're not on a turf, z is 0, this works anyways lol
|
|
if(thing.z != z)
|
|
continue
|
|
// check flags
|
|
if(thing.obj_persist_status & OBJ_PERSIST_STATUS_NO_THANK_YOU)
|
|
continue
|
|
// are they static?
|
|
if(thing.obj_persist_static_id)
|
|
static_objects += thing
|
|
// are they dynamic?
|
|
else if(thing.obj_persist_dynamic_id)
|
|
dynamic_objects += thing
|
|
|
|
return list(static_objects, dynamic_objects)
|