mirror of
https://github.com/Citadel-Station-13/Citadel-Station-13-RP.git
synced 2026-08-21 03:37:08 +01:00
266 lines
9.6 KiB
Plaintext
266 lines
9.6 KiB
Plaintext
//* This file is explicitly licensed under the MIT license. *//
|
|
//* Copyright (c) 2023 Citadel Station developers. *//
|
|
|
|
/**
|
|
* /obj/item's that fit a filter
|
|
*/
|
|
/datum/bulk_entity_persistence/trash
|
|
id = "trash"
|
|
|
|
/datum/bulk_entity_persistence/trash/is_enabled()
|
|
return CONFIG_GET(flag/persistent_trash)
|
|
|
|
/datum/bulk_entity_persistence/trash/gather_all()
|
|
. = list()
|
|
for(var/obj/item/item in world)
|
|
if(!isturf(item.loc))
|
|
continue
|
|
. += item
|
|
return filter_items(.)
|
|
|
|
/datum/bulk_entity_persistence/trash/gather_level(z)
|
|
. = list()
|
|
for(var/obj/item/item in world)
|
|
if(item.z != z)
|
|
continue
|
|
. += item
|
|
return filter_items(.)
|
|
|
|
/datum/bulk_entity_persistence/trash/perform_global_filter(list/atom/movable/entities)
|
|
entities = SSpersistence.entity_filter_out_non_persisting_objs(entities)
|
|
entities = SSpersistence.entity_filter_out_eroding_turfs(entities)
|
|
return entities
|
|
|
|
/datum/bulk_entity_persistence/trash/perform_level_filter(list/atom/movable/entities, datum/map_level/level)
|
|
// yeah the voronoi algorithm doesn't work if there's nothing to compute tbh
|
|
// we need atleast 1 for proper triangulation, but let's be safe and break if not 3+.
|
|
if(length(entities) <= 3)
|
|
return entities
|
|
|
|
// no more trash islands :(
|
|
var/list/bucket = list()
|
|
for(var/i in 1 to length(entities))
|
|
if (prob(50))
|
|
continue
|
|
bucket += entities[i]
|
|
|
|
return bucket
|
|
// perform entity filtering based on level and configuraiton
|
|
// var/mesh_heuristic = level.persistent_trash_mesh_heuristic + round(length(entities) * 0.001 * level.persistent_trash_mesh_heuristic_escalate_per_thousand)
|
|
// var/drop_n_largest_meshes = level.persistent_trash_drop_n_largest
|
|
// var/drop_n_smallest_meshes = level.persistent_trash_drop_n_smallest
|
|
// var/drop_n_most_isolated = level.persistent_trash_drop_n_most_isolated
|
|
// var/drop_n_least_isolated = level.persistent_trash_drop_n_least_isolated
|
|
// // run heuristics
|
|
// var/list/results = heuristics(entities, mesh_heuristic)
|
|
// var/list/meshes = results[1]
|
|
// var/list/singles = results[2]
|
|
// // sort results by descending density
|
|
// tim_sort(meshes, GLOBAL_PROC_REF(cmp_persistent_trash_group_density_dsc))
|
|
// tim_sort(singles, GLOBAL_PROC_REF(cmp_numeric_dsc), associative = TRUE)
|
|
// // drop results as needed
|
|
// meshes.Cut(1, min(length(meshes) + 1, drop_n_largest_meshes + 1))
|
|
// meshes.len -= drop_n_smallest_meshes
|
|
// singles.Cut(1, min(length(singles) + 1, drop_n_least_isolated + 1))
|
|
// singles.len -= drop_n_most_isolated
|
|
// // collate filtered back into entities
|
|
// var/list/collating = singles.Copy()
|
|
// for(var/datum/persistent_trash_group/mesh as anything in meshes)
|
|
// collating += mesh.trash
|
|
// return collating
|
|
|
|
/datum/bulk_entity_persistence/trash/serialize_entities_into_chunks(list/atom/movable/entities, datum/map_level/level, datum/map_level_persistence/persistence)
|
|
var/list/datum/bulk_entity_chunk/chunks = list()
|
|
if(!length(entities))
|
|
return
|
|
// split by area/turf
|
|
var/list/area_turf_tuples = SSpersistence.entity_group_by_area_and_turf(entities)
|
|
for(var/list/area_turf_tuple as anything in area_turf_tuples)
|
|
var/area_type = area_turf_tuple[1]
|
|
var/turf_type = area_turf_tuple[2]
|
|
var/list/area_turf_entities = area_turf_tuples[area_turf_tuple]
|
|
// limit to 500 entities per chunk
|
|
for(var/list/atom/movable/chunk_entities as anything in SSpersistence.entity_split_by_amount(area_turf_entities, 500))
|
|
// create chunk
|
|
var/datum/bulk_entity_chunk/chunk = new
|
|
chunk.level_id = persistence.level_id
|
|
var/list/entities_constructed = list()
|
|
for(var/atom/movable/entity as anything in chunk_entities)
|
|
entities_constructed[++entities_constructed.len] = list(
|
|
"x" = entity.x,
|
|
"y" = entity.y,
|
|
"type" = entity.type,
|
|
"data" = entity.serialize(),
|
|
)
|
|
chunk.data = list(
|
|
"area_lock" = area_type,
|
|
"turf_lock" = turf_type,
|
|
"entities" = entities_constructed,
|
|
)
|
|
chunks += chunk
|
|
return chunks
|
|
|
|
/datum/bulk_entity_persistence/trash/load_chunks(list/datum/bulk_entity_chunk/chunks)
|
|
var/loaded = 0
|
|
var/dropped = 0
|
|
for(var/datum/bulk_entity_chunk/chunk as anything in chunks)
|
|
var/z_index = SSmapping.keyed_levels[chunk.level_id]?.z_index
|
|
var/rounds_since = chunk.rounds_since_saved
|
|
var/hours_since = chunk.hours_since_saved
|
|
// drop if level not loaded
|
|
if(isnull(z_index))
|
|
dropped++
|
|
continue
|
|
var/area_type = text2path(chunk.data["area_lock"])
|
|
var/turf_type = text2path(chunk.data["turf_lock"])
|
|
// drop if area/turf types no longer exist
|
|
if(!ispath(area_type))
|
|
dropped++
|
|
continue
|
|
if(!ispath(turf_type))
|
|
dropped++
|
|
continue
|
|
// entities = list(list("type" = type, "data" = list), ...)
|
|
var/list/entities = chunk.data["entities"]
|
|
for(var/list/struct as anything in entities)
|
|
if(!islist(struct))
|
|
continue
|
|
var/entity_type = text2path(struct["type"])
|
|
var/list/entity_data = struct["data"]
|
|
// check for valid item
|
|
if(!ispath(entity_type, /obj/item))
|
|
dropped++
|
|
continue
|
|
var/x = struct["x"]
|
|
var/y = struct["y"]
|
|
// locate spawn location
|
|
var/turf/where = locate(x, y, z_index)
|
|
if(isnull(where))
|
|
dropped++
|
|
continue
|
|
// check area / turf constraint
|
|
if(!istype(where, turf_type) || !istype(where.loc, area_type))
|
|
dropped++
|
|
continue
|
|
var/obj/item/creating = new entity_type(where)
|
|
creating.deserialize(entity_data)
|
|
creating.decay_persisted(rounds_since, hours_since)
|
|
loaded++
|
|
return list(loaded, dropped, 0)
|
|
|
|
/datum/bulk_entity_persistence/trash/proc/trash_typecache()
|
|
var/list/umbrella_inclusions = list(
|
|
/obj/item/trash,
|
|
)
|
|
var/list/specific_inclusions = list(
|
|
/obj/item/cigbutt,
|
|
/obj/item/paper/crumpled,
|
|
)
|
|
for(var/type in specific_inclusions)
|
|
specific_inclusions[type] = TRUE
|
|
return typecacheof(umbrella_inclusions) | specific_inclusions
|
|
|
|
/datum/bulk_entity_persistence/trash/proc/filter_items(list/obj/item/items)
|
|
// append, not remove; save the vector resizing/etc
|
|
// we could swap with end instead but eh.
|
|
var/list/filtered = list()
|
|
var/list/typecache = trash_typecache()
|
|
for(var/obj/item/item as anything in items)
|
|
// first, check typecache
|
|
if(!typecache[item.type])
|
|
continue
|
|
filtered += item
|
|
return filtered
|
|
|
|
/**
|
|
* all items should be on the same zlevel.
|
|
*
|
|
* returns a list with indices:
|
|
*
|
|
* 1: meshes with densities
|
|
* 2: list of single items associated to density
|
|
*
|
|
* @return list(list(/datum/persistent_trash_group instance, ..), list(item instance = density, ...))
|
|
*/
|
|
// /datum/bulk_entity_persistence/trash/proc/heuristics(list/obj/item/items, mesh_heuristic_threshold)
|
|
// // we need a way to dedupe, so...
|
|
// // this is the 'real' graph vertices list; only one is made for a coordinate.
|
|
// // this is associated to the list of items in it.
|
|
// var/list/datum/vec2/vertices = list()
|
|
// // lockstepped list of vec2's
|
|
// var/list/datum/vec2/lockstepped_points = list()
|
|
// // 16 MB at 1000x1000, dropped right after
|
|
// var/list/buffer = new /list(world.maxx * world.maxy)
|
|
// for(var/i in 1 to length(items))
|
|
// var/obj/item/item = items[i]
|
|
// var/index = (item.y - 1) * world.maxx + (item.x)
|
|
// if(isnull(buffer[index]))
|
|
// var/datum/vec2/created_point = new /datum/vec2(item.x, item.y)
|
|
// buffer[index] = created_point
|
|
// vertices[created_point] = list()
|
|
// vertices[buffer[index]] += item
|
|
// lockstepped_points += buffer[index]
|
|
// // drop buffer now
|
|
// buffer = null
|
|
// // enough?
|
|
// if(length(vertices) < 3)
|
|
// // if less than 3 vertices they're all singles
|
|
// // todo: we should try to mesh anything on the same tile..
|
|
// return list(list(), items)
|
|
// // construct graph
|
|
// var/datum/graph/constructed_graph = vec2_dual_delaunay_voronoi_graph(vertices)
|
|
|
|
// // prepare
|
|
// var/list/datum/persistent_trash_group/meshes = list()
|
|
// var/list/obj/item/singles = list()
|
|
// var/list/datum/vec2/vertices_processing = vertices.Copy()
|
|
// var/list/datum/vec2/processed = list()
|
|
|
|
// // go through vertices and create meshes
|
|
// while(length(vertices_processing))
|
|
// var/datum/vec2/vertex = vertices_processing[length(vertices_processing)]
|
|
// vertices_processing.len--
|
|
// var/list/items_on_vertex = vertices[vertex]
|
|
|
|
// var/list/datum/vec2/expanding = list(vertex)
|
|
// var/list/obj/item/items_within_group = list()
|
|
// items_within_group += items_on_vertex
|
|
// var/maximum_density_over_expansion = 0
|
|
// var/expanding_index = 1
|
|
// while(expanding_index <= length(expanding))
|
|
// var/datum/vec2/source = expanding[expanding_index]
|
|
// expanding_index++
|
|
// if(processed[source])
|
|
// continue
|
|
// processed[source] = TRUE
|
|
// maximum_density_over_expansion = max(maximum_density_over_expansion, 1 / source.voronoi_area)
|
|
// for(var/datum/vec2/dest in constructed_graph.vertices[source])
|
|
// if(source.chebyshev_distance_to(dest) > mesh_heuristic_threshold)
|
|
// continue
|
|
// expanding |= dest
|
|
// items_within_group += vertices[source]
|
|
// vertices_processing -= dest
|
|
|
|
// if(length(items_within_group) > 1)
|
|
// var/datum/persistent_trash_group/constructed_mesh = new
|
|
// constructed_mesh.trash = items_within_group
|
|
// constructed_mesh.highest_marked_density = maximum_density_over_expansion
|
|
// // todo: configurable?
|
|
// var/density_escalation_heuristic = length(items_within_group) / 20
|
|
// constructed_mesh.highest_marked_density *= density_escalation_heuristic
|
|
// meshes += constructed_mesh
|
|
// else
|
|
// ASSERT(items_within_group[1])
|
|
// singles += items_within_group[1]
|
|
|
|
// return list(meshes, singles)
|
|
|
|
/datum/persistent_trash_group
|
|
/// trash in this group
|
|
var/list/obj/item/trash
|
|
/// highest marked density in this group
|
|
var/highest_marked_density = 0
|
|
|
|
/proc/cmp_persistent_trash_group_density_dsc(datum/persistent_trash_group/A, datum/persistent_trash_group/B)
|
|
return B.highest_marked_density - A.highest_marked_density
|