From 8268393f3f4095ead25ab6c519cc9893aea98af0 Mon Sep 17 00:00:00 2001 From: Lohikar Date: Mon, 15 Jun 2026 15:56:19 -0500 Subject: [PATCH 01/12] zm: Multiplanar ZM --- code/__defines/_planes+layers.dm | 4 +- code/__defines/ao.dm | 1 + code/__defines/zmimic.dm | 76 ++- code/controllers/subsystems/ambience.dm | 2 +- code/controllers/subsystems/zcopy.dm | 533 ++++++++++++++------ code/game/atoms_movable.dm | 87 +++- code/game/turfs/space/space.dm | 6 +- code/game/turfs/turf.dm | 3 +- code/game/turfs/turf_ao.dm | 38 +- code/game/turfs/turf_changing.dm | 17 +- code/modules/lighting/lighting_overlay.dm | 8 +- code/modules/mob/login.dm | 2 + code/modules/multiz/zmimic/mimic_docs.dm | 274 ++++++---- code/modules/multiz/zmimic/mimic_masters.dm | 71 +++ code/modules/multiz/zmimic/mimic_movable.dm | 102 +++- code/modules/multiz/zmimic/mimic_turf.dm | 76 ++- nebula.dme | 1 + 17 files changed, 960 insertions(+), 341 deletions(-) create mode 100644 code/modules/multiz/zmimic/mimic_masters.dm diff --git a/code/__defines/_planes+layers.dm b/code/__defines/_planes+layers.dm index a93d92514259..97af48c5ac98 100644 --- a/code/__defines/_planes+layers.dm +++ b/code/__defines/_planes+layers.dm @@ -71,7 +71,9 @@ What is the naming convention for planes or layers? #define DEBRIS_LAYER 1 #define DUST_LAYER 2 -// Openspace uses planes -80 through -70. +// Z-Mimic needs OPENTURF_PLANES_PER_DEPTH * OPENTURF_MAX_DEPTH planes. ZM will emit a compile-time warning if an invalid range is specified here. +#define ZMIMIC_MINIMUM_PLANE -80 +#define ZMIMIC_MAXIMUM_PLANE -50 #define OVER_OPENSPACE_PLANE -3 diff --git a/code/__defines/ao.dm b/code/__defines/ao.dm index 3f5d93283d16..512542c61c06 100644 --- a/code/__defines/ao.dm +++ b/code/__defines/ao.dm @@ -1,5 +1,6 @@ // -- SSao -- #define WALL_AO_ALPHA 80 +#define Z_AO_ALPHA 120 #define AO_UPDATE_NONE 0 #define AO_UPDATE_OVERLAY 1 diff --git a/code/__defines/zmimic.dm b/code/__defines/zmimic.dm index a9af1b126f5c..fd5a46692297 100644 --- a/code/__defines/zmimic.dm +++ b/code/__defines/zmimic.dm @@ -1,27 +1,54 @@ -#define ZM_DESTRUCTION_TIMER(TARGET) addtimer(CALLBACK(TARGET, TYPE_PROC_REF(/datum, qdel_self)), 10 SECONDS, TIMER_STOPPABLE) -#define TURF_IS_MIMICKING(T) (isturf(T) && (T:z_flags & ZM_MIMIC_BELOW)) -#define CHECK_OO_EXISTENCE(OO) if (OO && !MOVABLE_IS_ON_ZTURF(OO) && !OO.destruction_timer) { OO.destruction_timer = ZM_DESTRUCTION_TIMER(OO); } +/// Enable extra ZM debug logs -- these are useful for diagnosing layering issues, but they're potentially expensive. +// #define ZM_ENH_DEBUG + +#ifdef ZM_ENH_DEBUG + +#define ZM_DEBUG_LOG(X...) log_debug(X) + +#else + +#define ZM_DEBUG_LOG(X...) + +#endif + +// end ZM debug + +#define ZM_DESTRUCTION_TIMER(TARGET, REASON) addtimer(CALLBACK(TARGET, TYPE_PROC_REF(/atom/movable/openspace/mimic, timeout), __FILE__, __LINE__, REASON), 10 SECONDS, TIMER_STOPPABLE) + +#define TURF_IS_MIMIC(T) (isturf(T) && (T:z_flags & ZM_MIMIC_BELOW)) //! Is this a full Z-turf? +#define TURF_IS_MIMIC_BOUNDARY(T) (isturf(T) && (T:z_flags & ZM_BOUNDARY)) +#define TURF_IS_MIMICKING(T) (isturf(T) && (T:z_flags & (ZM_MIMIC_BELOW | ZM_BOUNDARY))) //! Is this turf participating in Z-mimic? +#define CHECK_OO_EXISTENCE(OO) if (OO && !MOVABLE_IS_ON_ZTURF(OO) && !OO.destruction_timer) { OO.destruction_timer = ZM_DESTRUCTION_TIMER(OO, "COE"); } #define UPDATE_OO_IF_PRESENT CHECK_OO_EXISTENCE(bound_overlay); if (bound_overlay) { update_above(); } +#define ZM_DIFF_HIDE_STATE(CALC, FLAG, TARGET) (CALC) ? (TARGET.hidden | FLAG) : (TARGET.hidden & ~FLAG) // I do not apologize. // These aren't intended to be used anywhere else, they just can't be undef'd because DM is dum. -#define ZM_INTERNAL_SCAN_LOOKAHEAD(M,VTR,F) ((get_step(M, M:dir)?:VTR & F) || (get_step(M, turn(M:dir, 180))?:VTR & F)) -#define ZM_INTERNAL_SCAN_LOOKBESIDE(M,VTR,F) ((get_step(M, turn(M:dir, 90))?:VTR & F) || (get_step(M, turn(M:dir, -90))?:VTR & F)) +// M: origin movable; TREF: origin loc; VTR: variable to read; F: flag to test +#define ZM_INTERNAL_SCAN_LOOKAHEAD(M,TREF,VTR,F) ((get_step(TREF, M:dir)?:VTR & F) || (get_step(TREF, turn(M:dir, 180))?:VTR & F)) +#define ZM_INTERNAL_SCAN_LOOKBESIDE(M,TREF,VTR,F) ((get_step(TREF, turn(M:dir, 90))?:VTR & F) || (get_step(TREF, turn(M:dir, -90))?:VTR & F)) /// Is this movable visible from a turf that is mimicking below? Note: this does not necessarily mean *directly* below. #define MOVABLE_IS_BELOW_ZTURF(M) (\ isturf(M:loc) && (TURF_IS_MIMICKING(M:loc:above) \ - || ((M:z_flags & ZMM_LOOKAHEAD) && ZM_INTERNAL_SCAN_LOOKAHEAD(M, above?:z_flags, ZM_MIMIC_BELOW)) \ - || ((M:z_flags & ZMM_LOOKBESIDE) && ZM_INTERNAL_SCAN_LOOKBESIDE(M, above?:z_flags, ZM_MIMIC_BELOW))) \ + || ((M:z_flags & ZMM_LOOKAHEAD) && ZM_INTERNAL_SCAN_LOOKAHEAD(M, M, above?:z_flags, (ZM_MIMIC_BELOW | ZM_BOUNDARY))) \ + || ((M:z_flags & ZMM_LOOKBESIDE) && ZM_INTERNAL_SCAN_LOOKBESIDE(M, M, above?:z_flags, (ZM_MIMIC_BELOW | ZM_BOUNDARY)))) \ ) + /// Is this movable located on a turf that is mimicking below? Note: this does not necessarily mean *directly* on. #define MOVABLE_IS_ON_ZTURF(M) (\ (TURF_IS_MIMICKING(M:loc) \ - || ((M:z_flags & ZMM_LOOKAHEAD) && ZM_INTERNAL_SCAN_LOOKAHEAD(M, z_flags, ZM_MIMIC_BELOW)) \ - || ((M:z_flags & ZMM_LOOKBESIDE) && ZM_INTERNAL_SCAN_LOOKBESIDE(M, z_flags, ZM_MIMIC_BELOW))) \ + || ((M:z_flags & ZMM_LOOKAHEAD) && ZM_INTERNAL_SCAN_LOOKAHEAD(M, M, z_flags, (ZM_MIMIC_BELOW | ZM_BOUNDARY))) \ + || ((M:z_flags & ZMM_LOOKBESIDE) && ZM_INTERNAL_SCAN_LOOKBESIDE(M, M, z_flags, (ZM_MIMIC_BELOW | ZM_BOUNDARY)))) \ ) -#define MOVABLE_SHALL_MIMIC(AM) (!(AM.z_flags & ZMM_IGNORE) && MOVABLE_IS_BELOW_ZTURF(AM)) + +/* Don't copy: + - (q)deleted objects + - Explicitly ignored objects + - Always-invisible atoms +*/ +#define MOVABLE_SHALL_MIMIC(AM) (!QDELETED(AM) && !(AM.z_flags & ZMM_IGNORE) && AM.invisibility != INVISIBILITY_ABSTRACT) // Turf MZ flags. #define ZM_MIMIC_BELOW 1 //! If this turf should mimic the turf on the Z below. @@ -30,16 +57,34 @@ #define ZM_ALLOW_ATMOS 8 //! If this turf permits passage of air. #define ZM_MIMIC_NO_AO 16 //! If the turf shouldn't apply regular turf AO and only do Z-mimic AO. #define ZM_NO_OCCLUDE 32 //! Don't occlude below atoms if we're a non-mimic z-turf. -#define ZM_OVERRIDE 64 //! Copy only z_appearance or baseturf and bail, do not attempt to copy movables. This is significantly cheaper and allows you to override the mimic, but results in movables not being visible. -#define ZM_NO_SHADOW 128 //! If this turf is being copied, hide the shadower. -#define ZM_TERMINATOR 256 //! Consider this turf the terminus of a Z-group, like the bottom of a Z-group or a ZM_OVERRIDE turf. +#define ZM_OVERRIDE 64 //! Copy only z_appearance or baseturf and bail, do not attempt to copy movables. This is significantly cheaper and allows you to override the mimic, but results in movables not being visible. This also terminates the Z-stack for purposes of ZM invariants. +#define ZM_NO_SHADOW 128 //! If this turf is being copied, don't darken the shadower. +#define ZM_BOUNDARY 256 //! Internal use. Partially mimic the turf: allow creation of movables, but do not copy the actual turf. Movables are hidden from rightclick. +#define ZM_HIDE_ATOMS 512 //! If this turf is considered opaque to mouse clicks, also hide below mimics from the right-click menu. This makes it harder to examine atoms below, however. // Convenience flags. #define ZM_MIMIC_DEFAULTS (ZM_MIMIC_BELOW|ZM_ALLOW_LIGHTING) //! Common defaults for zturfs. #define ZMM_WIDE_LOAD (ZMM_LOOKAHEAD | ZMM_LOOKBESIDE) //! Atom is big and needs to scan one extra turf in both X and Y. This only extends the range by one turf. Cheap, but not free. +/// Preset: you're creating a hole in the ground. No icon, nothing to cast shadows on. Just a hole. +#define ZM_MIMIC_PRESET_HOLE (ZM_MIMIC_DEFAULTS | ZM_OVERWRITE | ZM_MIMIC_NO_AO | ZM_ALLOW_ATMOS) +/// Preset: you're creating a hole in the ground with a border. This turf has an icon, but you want to be able to click things in the hole like with a regular Z-hole. +#define ZM_MIMIC_PRESET_HOLE_WITH_BORDER (ZM_MIMIC_DEFAULTS | ZM_NO_OCCLUDE | ZM_ALLOW_ATMOS) +/// Preset: you're creating a turf with translucent accents (like glass flooring). This will block clicks even where alpha on the icon is 0, add MIMIC_NO_OCCLUDE if this is unwanted. +#define ZM_MIMIC_PRESET_TRANSLUCENT_TURF (ZM_MIMIC_DEFAULTS) + +/// Flags that require layering resets when a movable moves between turfs with different values for. +#define ZM_STATEFUL_MIMIC_FLAGS (0) +/// Flags that persist across changeturf. +#define ZM_INFECTIOUS_MIMIC_FLAGS (ZM_BOUNDARY|ZM_OVERRIDE) // Hopefully I don't regret making OVERRIDE sticky. + +// ZM hide state flags for simplicity of logic. +#define ZM_HIDE_BOUNDARY 1 //! This mimic is being hidden by its parent being a boundary. +#define ZM_HIDE_NONMIMIC 2 //! This mimic is being hidden by its parent not being a mimic. +#define ZM_HIDE_OPAQUE 4 //! This mimic is being hidden by its parent having mouse_opacity set with MIMIC_HIDE_ATOMS active. + // For debug purposes, should contain the above defines in ascending order. -var/global/list/mimic_defines = list( +var/list/mimic_defines = list( "ZM_MIMIC_BELOW", "ZM_MIMIC_OVERWRITE", "ZM_ALLOW_LIGHTING", @@ -48,7 +93,8 @@ var/global/list/mimic_defines = list( "ZM_NO_OCCLUDE", "ZM_OVERRIDE", "ZM_NO_SHADOW", - "ZM_TERMINATOR" + "ZM_BOUNDARY", + "ZM_HIDE_ATOMS" ) // Movable flags. diff --git a/code/controllers/subsystems/ambience.dm b/code/controllers/subsystems/ambience.dm index 55bd35f9f164..85e22c25eceb 100644 --- a/code/controllers/subsystems/ambience.dm +++ b/code/controllers/subsystems/ambience.dm @@ -89,7 +89,7 @@ SUBSYSTEM_DEF(ambience) var/turf/above = src var/datum/level_data/above_level_data while ((above = GetAbove(above))) - if((above.z_flags & ZM_TERMINATOR) || !HasAbove(above.z)) + if((above.z_flags & ZM_OVERRIDE) || !HasAbove(above.z)) break above_level_data = SSmapping.levels_by_z[above.z] if(above_level_data.daycycle_id) diff --git a/code/controllers/subsystems/zcopy.dm b/code/controllers/subsystems/zcopy.dm index e0dfe3f0b1cb..67eab08e4878 100644 --- a/code/controllers/subsystems/zcopy.dm +++ b/code/controllers/subsystems/zcopy.dm @@ -4,11 +4,50 @@ */ -#define OPENTURF_MAX_PLANE -70 #define OPENTURF_MAX_DEPTH 10 // The maxiumum number of planes deep we'll go before we just dump everything on the same plane. +#define OPENTURF_PLANES_PER_DEPTH 3 + +#if -((ZMIMIC_MINIMUM_PLANE) - (ZMIMIC_MAXIMUM_PLANE)) != (OPENTURF_MAX_DEPTH * OPENTURF_PLANES_PER_DEPTH) +#warn ZMIMIC plane ranges are inconsistent with declared depth and planes per depth. +#endif + +#define ZM_DEPTH_TO_OFFSET_RAW(X, PPD) ((PPD) * (X)) +/// Compute the root offset from ZMIMIC_MAXIMUM_PLANE, sans any slot offset. +#define ZM_DEPTH_TO_OFFSET(X) ZM_DEPTH_TO_OFFSET_RAW(X, OPENTURF_PLANES_PER_DEPTH) +/// Compute the final target plane given a stack depth number and a slot offset. +#define ZM_COMPUTE_PLANE(DEPTH, SLOT) (ZMIMIC_MAXIMUM_PLANE - ZM_DEPTH_TO_OFFSET(DEPTH) + (SLOT)) + #define SHADOWER_DARKENING_FACTOR 0.6 // The multiplication factor for openturf shadower darkness. Lighting will be multiplied by this. #define SHADOWER_DARKENING_COLOR "#999999" // The above, but as an RGB string for lighting-less turfs. +#define ZM_SLICE(Ty, D) "zm_slice_[D]_[Ty]" +#define ZM_SLICE_VIRTUAL(Ty, D) "*zm_slice_[D]_[Ty]" + +// These cannot be reused within a depth since they're used to assign render targets. +#define ZM_SLICE_TY_BASIC "basic" +#define ZM_SLICE_TY_LIGHTING "lighting" +#define ZM_SLICE_TY_CAP "cap" +#define ZM_SLICE_TY_ZSUM "sum" + +#define ZM_SLICE_SLOT_ROOT 0 //! Standard render. Nothing special. aka: basic plane +#define ZM_SLICE_SLOT_LIGHTING 1 //! Shadowers and other *BLEND_MULTIPLY* objects. +#define ZM_SLICE_SLOT_CAP 2 //! ZAO and other non-MULTIPLY effects that must render on top of everything else, including lighting. + +#define ZM_BASEMENT_CAP_PLANE -399 +#define ZM_BASEMENT_MAX_PLANE -400 +#define ZM_BASEMENT_PLANES_PER_DEPTH 1 +#define ZM_VSLICE_SLOT_ZSUM 0 + +/// How many items should we process before we check for yield? Increasing this increases efficiency, but also raises risk of overrun. +#define ZM_PUMP_RATIO 4 +/// Initialize state required for ZM_MC_TRY_YIELD. +#define ZM_PUMP_INIT var/__yield + +/// Check if we need to yield to the MC. This macro will sleep or break. +#define ZM_MC_TRY_YIELD if ((++__yield) >= ZM_PUMP_RATIO) { __yield = 0; if (no_mc_tick) { CHECK_TICK; } else if (MC_TICK_CHECK) { break; } } + +var/list/zm_offset_to_target = list(ZM_SLICE_TY_BASIC, ZM_SLICE_TY_LIGHTING, ZM_SLICE_TY_CAP) + //#define ZM_RECORD_STATS // This doesn't work on O7/Neb right now. #ifdef ZM_RECORD_STATS @@ -33,16 +72,25 @@ SUBSYSTEM_DEF(zcopy) var/list/queued_overlays = list() var/qo_idex = 1 + var/list/pending_boundaries = list() + var/pb_idex = 1 + var/openspace_overlays = 0 var/openspace_turfs = 0 + var/openspace_boundaries = 0 - var/multiqueue_skips_turf = 0 - var/multiqueue_skips_discovery = 0 - var/multiqueue_skips_object = 0 + var/multiqueue_skips_turf = 0 //! How many times did we skip a redundant turf update due to not being the most recent update? + var/multiqueue_skips_object = 0 //! How many times did we skip a redundant object update due to not being the most recent update? var/total_updates_turf = 0 var/total_updates_discovery = 0 var/total_updates_object = 0 + var/total_boundary_reocclusion = 0 //! How many times was a boundary removed by a ZM turf being changed to a non-mimic? + var/deferred_discoveries = 0 //! How many times did we have to recursively discover? This overlaps with the discovery total. + var/lighting_updates = 0 + var/total_boundary_promotions = 0 //! How many turfs were promoted from boundary to mimic? + var/total_boundary_demotions = 0 //! How many turfs were demoted from mimic to boundary? + var/total_boundary_removals = 0 //! How many boundaries were made non-z turfs? #ifdef ZM_RECORD_STATS var/list/turf_stats = list() @@ -50,18 +98,25 @@ SUBSYSTEM_DEF(zcopy) var/list/mimic_stats = list() #endif - // Highest Z level in a given Z-group for absolute layering. - // zstm[zlev] = group_max + /// Highest Z level in a given Z-group for absolute layering. `zlev_maximums[z] = group_max`. var/list/zlev_maximums = list() + // cached debug strings, since rebuilding this every stat() tick is wasteful given how rarely it changes + var/list/last_zlev_max + var/last_zgr_text // Caches for fixup. - var/list/fixup_cache = list() - var/list/fixup_known_good = list() + var/list/fixup_cache = list() //! Cache of already mangled `[old_appearance] = mangled_appearance` mappings. + var/list/fixup_known_good = list() //! Cache of known-valid (no layering violations) appearances. // Fixup stats. - var/fixup_miss = 0 - var/fixup_noop = 0 - var/fixup_hit = 0 + var/fixup_miss = 0 //! How many appearances required active mangling? + var/fixup_noop = 0 //! How many appearances were passed to fixup but did not require mangling? + var/fixup_hit = 0 //! How many appearances were passed to fixup and were found in `fixup_cache` or `fixup_known_good`? + var/fixup_miss_root = 0 + var/fixup_noop_root = 0 + var/fixup_hit_root = 0 + + var/starlight_enabled = FALSE // for admin proc-call /datum/controller/subsystem/zcopy/proc/update_all() @@ -82,7 +137,7 @@ SUBSYSTEM_DEF(zcopy) else if (istype(A, /atom/movable/openspace/mimic)) var/turf/Tloc = A.loc - if (TURF_IS_MIMICKING(Tloc)) + if (TURF_IS_MIMIC(Tloc)) Tloc.update_mimic() num_amupd += 1 else @@ -116,6 +171,7 @@ SUBSYSTEM_DEF(zcopy) if (isturf(A)) T = A if (T.z_flags & ZM_MIMIC_BELOW) + flush_z_state(T) T.update_mimic() num_turfs += 1 @@ -125,29 +181,34 @@ SUBSYSTEM_DEF(zcopy) CHECK_TICK - log_debug("SSzcopy: deleted [num_deleted] overlays, and queued [num_turfs] turfs for update.") + log_ss(name, "deleted [num_deleted] overlays, and queued [num_turfs] turfs for update.") enable() /datum/controller/subsystem/zcopy/stat_entry() var/list/entries = list( "", // newline - "ZSt: [build_zstack_display()]", // This is a human-readable list of the z-stacks known to ZM. - "ZMx: [zlev_maximums.Join(", ")]", // And this is the raw internal state. + "ZGr: [build_zgroup_display()]", // This is a human-readable list of the z-groups known to ZM. + //"ZMx: [zlev_maximums.Join(", ")]", // And this is the raw internal state. // This one gets broken out from the below because it's more important. - "Q: { T: [queued_turfs.len - (qt_idex - 1)] O: [queued_overlays.len - (qo_idex - 1)] }", - // In order: Total, Queued, Skipped - "T(O): { T: [openspace_turfs] O: [openspace_overlays] }", - "T(U): { T: [total_updates_turf] D: [total_updates_discovery] O: [total_updates_object] }", - "Sk: { T: [multiqueue_skips_turf] D: [multiqueue_skips_discovery] O: [multiqueue_skips_object] }", - "F: { H: [fixup_hit] M: [fixup_miss] N: [fixup_noop] FC: [fixup_cache.len] FKG: [fixup_known_good.len] }" + "Q: { Tb: [pending_boundaries.len - (pb_idex - 1)] | T: [queued_turfs.len - (qt_idex - 1)] | O: [queued_overlays.len - (qo_idex - 1)] }", + // In order: Total objects, Updated, State changes, Skipped, + "T(O): { Tb: [openspace_boundaries] | T: [openspace_turfs] | O: [openspace_overlays] }", + "T(U): { Tb: [total_boundary_reocclusion] | T: [total_updates_turf] | L: [lighting_updates] | O: [total_updates_object] }", + "T(St): { BPr: [total_boundary_promotions] | BDe: [total_boundary_demotions] | BRm: [total_boundary_removals] | D: [total_updates_discovery] | DDef: [deferred_discoveries] }", + "Sk: { T: [multiqueue_skips_turf] | O: [multiqueue_skips_object] }", + "F(St): { H: [fixup_hit] / [fixup_hit_root] | M: [fixup_miss] / [fixup_miss_root] | N: [fixup_noop] / [fixup_noop_root] } F(C): { Mangle: [fixup_cache.len] | No-op: [fixup_known_good.len] }" ) ..(entries.Join("\n\t")) // 1, 2, 3..=7, 8 -/datum/controller/subsystem/zcopy/proc/build_zstack_display() +/datum/controller/subsystem/zcopy/proc/build_zgroup_display() if (!zlev_maximums.len) return "" + + if (zlev_maximums ~= last_zlev_max) + return last_zgr_text + var/list/zmx = list() var/idx = 1 var/span_ctr = 0 @@ -161,7 +222,10 @@ SUBSYSTEM_DEF(zcopy) zmx += "[idx]" idx += 1 while (idx <= zlev_maximums.len) - return jointext(zmx, ", ") + last_zgr_text = jointext(zmx, ", ") + last_zlev_max = zlev_maximums.Copy() + + return last_zgr_text /datum/controller/subsystem/zcopy/Initialize(timeofday) calculate_zstack_limits() @@ -179,7 +243,7 @@ SUBSYSTEM_DEF(zcopy) if (z - start_zlev > OPENTURF_MAX_DEPTH) log_ss("zcopy", "WARNING: Z-levels [start_zlev] through [z] exceed maximum depth of [OPENTURF_MAX_DEPTH]; layering may behave strangely in this Z-stack.") else if (z - start_zlev > 1) - log_ss("zcopy", "Found Z-Stack: [start_zlev] -> [z] = [z - start_zlev + 1] zl") + log_ss("zcopy", "Found Z-Group: [start_zlev] -> [z] = [z - start_zlev + 1] zl") start_zlev = z + 1 log_ss("zcopy", "Z-Level maximums: [json_encode(zlev_maximums)]") @@ -191,7 +255,8 @@ SUBSYSTEM_DEF(zcopy) wake() /// Fully reset Z-Mimic, rebuilding state from scratch. Use this if you change Z-stack mappings after Z-Mimic has initialized. Expensive. -/datum/controller/subsystem/zcopy/proc/RebuildZState() +/// WARNING: This is *completely unsupported*. It will probably irreversibly corrupt Z-lighting in changed Z-groups. Use at own risk. +/datum/controller/subsystem/zcopy/proc/unsupported_rebuild_z_state() suspend() UNTIL(state == SS_IDLE) @@ -202,8 +267,8 @@ SUBSYSTEM_DEF(zcopy) for (var/turf/T as anything in block(level.level_inner_min_x, level.level_inner_min_y, zlev, level.level_inner_max_x, level.level_inner_max_y)) if (T.z_flags & ZM_MIMIC_BELOW) flush_z_state(T) - T.below = GetAbove(T) - T.above = GetBelow(T) + T.below = GetBelow(T) + T.above = GetAbove(T) T.update_mimic() CHECK_TICK wake() @@ -212,6 +277,37 @@ SUBSYSTEM_DEF(zcopy) if (!resumed) qt_idex = 1 qo_idex = 1 + pb_idex = 1 + + ZM_PUMP_INIT + + // This is outside of the MC_SPLIT_TICK_INIT because it should _usually_ not run, so we don't want it stealing tick allocation from the two bulk phases. + while (pb_idex <= pending_boundaries.len) + var/turf/boundary = pending_boundaries[pb_idex] + pending_boundaries[pb_idex] = null + pb_idex += 1 + + if (!boundary || !boundary.z_flags || TURF_IS_MIMIC(boundary)) + continue + + var/found = FALSE + for (var/turf/neighbor as anything in RANGE_TURFS(boundary, 1)) + if (TURF_IS_MIMIC(neighbor)) // only consider true mimics + found = TRUE + break + + if (!found) + boundary.z_flags &= ~ZM_BOUNDARY + boundary.cleanup_zmimic_boundary() + total_boundary_removals += 1 + + total_boundary_reocclusion += 1 + + ZM_MC_TRY_YIELD + + if (pb_idex > 1) + pending_boundaries.Cut(1, pb_idex) + pb_idex = 1 MC_SPLIT_TICK_INIT(2) if (!no_mc_tick) @@ -226,6 +322,7 @@ SUBSYSTEM_DEF(zcopy) // - Turf mimic - /datum/controller/subsystem/zcopy/proc/tick_turfs(no_mc_tick) + ZM_PUMP_INIT var/list/curr_turfs = queued_turfs while (qt_idex <= curr_turfs.len) @@ -234,21 +331,12 @@ SUBSYSTEM_DEF(zcopy) qt_idex += 1 if (!isturf(T) || !(T.z_flags & ZM_MIMIC_BELOW) || !T.z_queued) - if (no_mc_tick) - CHECK_TICK - else if (MC_TICK_CHECK) - break continue // If we're not at our most recent queue position, don't bother -- we're updating again later anyways. if (T.z_queued > 1) T.z_queued -= 1 multiqueue_skips_turf += 1 - - if (no_mc_tick) - CHECK_TICK - else if (MC_TICK_CHECK) - break continue // Z-Turf on the bottom-most level, just fake-copy space (or baseturf). @@ -257,7 +345,7 @@ SUBSYSTEM_DEF(zcopy) ZM_RECORD_START flush_z_state(T) if (T.z_flags & ZM_OVERRIDE) - simple_appearance_copy(T, get_base_turf_by_area(T), OPENTURF_MAX_PLANE) + simple_appearance_copy(T, get_base_turf_by_area(T), ZMIMIC_MAXIMUM_PLANE) else simple_appearance_copy(T, SSskybox.dust_cache["[((T.x + T.y) ^ ~(T.x * T.y) + T.z) % 25]"]) @@ -271,10 +359,7 @@ SUBSYSTEM_DEF(zcopy) ZM_RECORD_STOP ZM_RECORD_WRITE(turf_stats, "Fake: [T.type] on [T.z]") - if (no_mc_tick) - CHECK_TICK - else if (MC_TICK_CHECK) - break + ZM_MC_TRY_YIELD continue if (!T.shadower) // If we don't have a shadower yet, something has gone horribly wrong. @@ -285,17 +370,21 @@ SUBSYSTEM_DEF(zcopy) ZM_RECORD_START - // Get the bottom-most turf, the one we want to mimic. - // Baseturf mimics act as false bottoms of the stack. + // We need to find the non-z turf that's visually at the bottom of this group. BOUNDARY turfs are regular turfs, so they need to not be considered here. + // OVERRIDE turfs are considered the end of a Z-group regardless of actual connections. + // This logic is duplicated below in analyze_openturf(). var/turf/Td = T - while (Td.below && !(Td.z_flags & (ZM_OVERRIDE|ZM_TERMINATOR))) + while (Td.below && (Td.z_flags & ZM_MIMIC_BELOW) && !(Td.z_flags & ZM_OVERRIDE)) Td = Td.below + T.z_discovered_root = Td + // Depth must be the depth of the *visible* turf, not self. var/turf_depth + // If you're getting runtimes here, you violated a ZM invariant. Not a bug in ZM, you should be notifying ZM when you resize the world. turf_depth = T.z_depth = zlev_maximums[Td.z] - Td.z - var/t_target = OPENTURF_MAX_PLANE - turf_depth // This is where the turf (but not the copied atoms) gets put. + var/t_target = ZM_COMPUTE_PLANE(turf_depth, ZM_SLICE_SLOT_ROOT) // This is where the turf (but not the copied atoms) gets put. // Turf is set to mimic baseturf, handle that and bail. if (T.z_flags & ZM_OVERRIDE) @@ -311,10 +400,7 @@ SUBSYSTEM_DEF(zcopy) ZM_RECORD_STOP ZM_RECORD_WRITE(turf_stats, "Simple: [T.type] on [T.z]") - if (no_mc_tick) - CHECK_TICK - else if (MC_TICK_CHECK) - break + ZM_MC_TRY_YIELD continue // If we previously were ZM_OVERRIDE, there might be an orphaned proxy. @@ -359,46 +445,27 @@ SUBSYSTEM_DEF(zcopy) var/atom/movable/openspace/turf_mimic/DC = T.below.mimic_above_copy DC.appearance = T.below DC.mouse_opacity = initial(DC.mouse_opacity) - DC.plane = OPENTURF_MAX_PLANE - turf_depth - 1 + DC.target_slot = ZM_SLICE_SLOT_ROOT + DC.plane = ZM_COMPUTE_PLANE(turf_depth - 1, ZM_SLICE_SLOT_ROOT) else if (T.below.mimic_above_copy) QDEL_NULL(T.below.mimic_above_copy) // Handle below atoms. - var/shadower_set = FALSE - // Add everything below us to the discovery queue. for (var/thing in T.below) var/atom/movable/object = thing - if (QDELETED(object) || (object.z_flags & ZMM_IGNORE) || object.loc != T.below || object.invisibility == INVISIBILITY_ABSTRACT) - /* Don't queue: - - (q)deleted objects - - Explicitly ignored objects - - Objects not rooted on this turf (multitiles) - - Always-invisible atoms - */ - continue - - // Special case: these are merged into the shadower to reduce memory usage. - if (object.type == /atom/movable/lighting_overlay) - T.shadower.copy_lighting(object, !(T.below.z_flags & ZM_NO_SHADOW)) - shadower_set = TRUE - continue - // If an atom already has an overlay, we probably don't need to discover it again. // ...but we need to force it if the object was salvaged from another zturf. - if (!object.bound_overlay || object.bound_overlay.destruction_timer) - discover_movable(object, T) + if (MOVABLE_SHALL_MIMIC(object) && (!object.bound_overlay || object.bound_overlay.destruction_timer)) + discover_movable(object) - if (!shadower_set) - if (T.below.z_flags & ZM_NO_SHADOW) - T.shadower.color = null - else - T.shadower.color = SHADOWER_DARKENING_COLOR + if (!T.lighting_overlay?.needs_update) + update_lighting(T) T.z_queued -= 1 - if (T.above) + if (T.above && !T.above.z_queued) T.above.update_mimic() total_updates_turf += 1 @@ -406,17 +473,33 @@ SUBSYSTEM_DEF(zcopy) ZM_RECORD_STOP ZM_RECORD_WRITE(turf_stats, "Complex: [T.type] on [T.z]") - if (no_mc_tick) - CHECK_TICK - else if (MC_TICK_CHECK) - break + ZM_MC_TRY_YIELD if (qt_idex > 1) curr_turfs.Cut(1, qt_idex) qt_idex = 1 +/// Update the passed turf's shadower from the lighting object below it. +/datum/controller/subsystem/zcopy/proc/update_lighting(turf/target_turf) + ASSERT(target_turf.shadower != null) + var/atom/movable/lighting_overlay/object = target_turf.below?.lighting_overlay + if (object) + target_turf.shadower.copy_lighting(object) + else + if (target_turf.below.z_flags & ZM_NO_SHADOW) + target_turf.shadower.color = null + else + target_turf.shadower.color = SHADOWER_DARKENING_COLOR + + target_turf.shadower.lighting_generation_static += 1 // other generation is updated in copy_lighting() + target_turf.shadower.update_above() + + lighting_updates += 1 + target_turf.z_generation_lighting += 1 + // - Phase: Mimic update -- actually update the mimics' appearance, order sensitive - /datum/controller/subsystem/zcopy/proc/tick_mimic(no_mc_tick) + ZM_PUMP_INIT var/list/curr_ov = queued_overlays while (qo_idex <= curr_ov.len) var/atom/movable/openspace/mimic/OO = curr_ov[qo_idex] @@ -424,31 +507,20 @@ SUBSYSTEM_DEF(zcopy) qo_idex += 1 if (QDELETED(OO) || !OO.queued) - if (no_mc_tick) - CHECK_TICK - else if (MC_TICK_CHECK) - break continue if (QDELETED(OO.associated_atom)) // This shouldn't happen. qdel(OO) log_debug("Z-Mimic: Received mimic with QDELETED parent ([OO.associated_atom || ""])") - if (no_mc_tick) - CHECK_TICK - else if (MC_TICK_CHECK) - break + ZM_MC_TRY_YIELD continue // Don't update unless we're at the most recent queue occurrence. if (OO.queued > 1) OO.queued -= 1 multiqueue_skips_object += 1 - - if (no_mc_tick) - CHECK_TICK - else if (MC_TICK_CHECK) - break + // We're decrementing a number, presumably this does not happen enough to risk overrun. continue ZM_RECORD_START @@ -457,18 +529,20 @@ SUBSYSTEM_DEF(zcopy) if (OO.dir != OO.associated_atom.dir) OO.dir = OO.associated_atom.dir // updates are propagated up another way, don't use set_dir OO.appearance = OO.associated_atom - OO.z_flags = OO.associated_atom.z_flags + OO.cached_name = OO.name + if (OO.hidden) + OO.name = "" + OO.z_flags = OO.associated_atom.z_flags | initial(OO.z_flags) if (OO.particles != OO.associated_atom.particles) OO.particles = OO.associated_atom.particles - OO.plane = OPENTURF_MAX_PLANE - OO.depth - + OO.plane = ZM_COMPUTE_PLANE(OO.depth, OO.target_slot) OO.opacity = FALSE OO.queued = 0 - // If an atom has explicit plane sets on its overlays/underlays, we need to replace the appearance so they can be mangled to work with our planing. - if (OO.z_flags & ZMM_MANGLE_PLANES) + // If an atom has explicit plane sets on its overlays/underlays, we need to mangle the appearance's overlays/underlays to align with Z-Mimic's plane usage. + if (OO.z_flags & (ZMM_MANGLE_PLANES | ZMM_AUTOMANGLE)) var/new_appearance = fixup_appearance_planes(OO.appearance) if (new_appearance) OO.appearance = new_appearance @@ -482,43 +556,45 @@ SUBSYSTEM_DEF(zcopy) ZM_RECORD_STOP ZM_RECORD_WRITE(mimic_stats, OO.mimiced_type) - if (no_mc_tick) - CHECK_TICK - else if (MC_TICK_CHECK) - break + ZM_MC_TRY_YIELD if (qo_idex > 1) curr_ov.Cut(1, qo_idex) qo_idex = 1 +// only_reset: do not queue for update, only update layering info // return: is-invalid -/datum/controller/subsystem/zcopy/proc/discover_movable(atom/movable/object) +/datum/controller/subsystem/zcopy/proc/discover_movable(atom/movable/object, only_reset = FALSE) ASSERT(!QDELETED(object)) if(init_state < SS_INITSTATE_STARTED) return FALSE // no-op, discover_movable is only valid during or after zcopy init - var/turf/Tloc = object.loc - if (!isturf(Tloc) || !MOVABLE_SHALL_MIMIC(object)) + if (!isturf(object.loc)) return TRUE + var/turf/Tloc = object.loc + var/turf/T = Tloc.above - var/turf/T = GetAbove(Tloc) + // ??? + ASSERT(T != null) ZM_RECORD_START - var/above_needs_discovery = FALSE + var/atom/movable/defer if (!object.bound_overlay) var/atom/movable/openspace/mimic/M = new(T) object.bound_overlay = M - M.z_flags = object.z_flags // Necessary to ensure MOVABLE_IS_ON_ZTURF works M.associated_atom = object - above_needs_discovery = TRUE + if (T.z_flags & ZM_BOUNDARY) + M.hidden = TRUE + if (MOVABLE_IS_BELOW_ZTURF(M)) + defer = M var/override_depth var/original_type = object.type var/original_z = object.z switch (object.type) - // Layering for recursive mimic needs to be inherited. + // Depth for recursive mimic needs to be inherited. if (/atom/movable/openspace/mimic) var/atom/movable/openspace/mimic/OOO = object original_type = OOO.mimiced_type @@ -529,7 +605,7 @@ SUBSYSTEM_DEF(zcopy) if (/atom/movable/openspace/turf_proxy) if (T.z_eventually_space) // Yes, this is an awful hack; I don't want to add yet another override_* var. - override_depth = OPENTURF_MAX_PLANE - SPACE_PLANE + override_depth = (ZMIMIC_MAXIMUM_PLANE - SPACE_PLANE)/OPENTURF_PLANES_PER_DEPTH // TODO: Please not this -- while it's valid, this is awful. var/atom/movable/openspace/mimic/OO = object.bound_overlay @@ -539,31 +615,39 @@ SUBSYSTEM_DEF(zcopy) OO.destruction_timer = null OO.depth = override_depth || min(zlev_maximums[T.z] - original_z, OPENTURF_MAX_DEPTH) + OO.target_slot = 0 switch (original_type) // These types need to be pushed a layer down for bigturfs to function correctly. if (/atom/movable/openspace/turf_proxy, /atom/movable/openspace/turf_mimic) OO.depth += 1 if (/atom/movable/openspace/multiplier) - OO.depth += 1 + // Ignore override depth for these. + OO.depth = min(zlev_maximums[OO.z] - original_z + 1, OPENTURF_MAX_DEPTH) + OO.target_slot = ZM_SLICE_SLOT_LIGHTING OO.mimiced_type = original_type OO.override_depth = override_depth OO.original_z = original_z - // Multi-queue to maintain ordering of updates to these - // queueing it multiple times will result in only the most recent - // actually processing. - OO.queued += 1 - queued_overlays += OO + if (only_reset) + // We're only trying to rebuild layering information, no need to update the appearance. + OO.plane = ZM_COMPUTE_PLANE(OO.depth, OO.target_slot) + else + // Multi-queue to maintain ordering of updates to these + // queueing it multiple times will result in only the most recent + // actually processing. + OO.queued += 1 + queued_overlays += OO total_updates_discovery += 1 ZM_RECORD_STOP ZM_RECORD_WRITE(discovery_stats, "Depth [OO.depth] on [OO.z]") - if (above_needs_discovery && MOVABLE_IS_ON_ZTURF(OO)) - discover_movable(OO) // recursion! + if (defer) + deferred_discoveries += 1 + .(defer, only_reset) return FALSE @@ -584,7 +668,7 @@ SUBSYSTEM_DEF(zcopy) T.name = initial(T.name) T.desc = initial(T.desc) T.gender = initial(T.gender) - if (T.plane == 0 && target_plane) + if (T.plane == DEFAULT_PLANE && target_plane) T.plane = target_plane else // Some openturfs have icons, so we can't overwrite their appearance. @@ -595,7 +679,7 @@ SUBSYSTEM_DEF(zcopy) TO.name = T.name TO.gender = T.gender // Need to grab this too so PLURAL works properly in examine. TO.mouse_opacity = initial(TO.mouse_opacity) - if (TO.plane == 0 && target_plane) + if (TO.plane == DEFAULT_PLANE && target_plane) TO.plane = target_plane // Recurse: for self, check if planes are invalid, if yes; return fixed appearance @@ -610,11 +694,17 @@ SUBSYSTEM_DEF(zcopy) if (fixup_known_good[appearance]) fixup_hit += 1 + if (depth == 0) + fixup_hit_root += 1 return null if (fixup_cache[appearance]) fixup_hit += 1 + if (depth == 0) + fixup_hit_root += 1 return fixup_cache[appearance] + ZM_DEBUG_LOG("Plane fixup: considering appearance with name [appearance:name || ""] and icon [appearance:icon || ""] | [appearance:icon_state || ""]") + // If you have more than 4 layers of overlays within overlays, I dunno what to say. if (depth > 4) var/icon_name = "[appearance:icon]" @@ -669,10 +759,14 @@ SUBSYSTEM_DEF(zcopy) // If we did nothing (no violations), don't bother creating a new appearance if (!plane_needs_fix && !fixed_overlays && !fixed_underlays) fixup_noop += 1 + if (depth == 0) + fixup_noop_root += 1 fixup_known_good[appearance] = TRUE return null fixup_miss += 1 + if (depth == 0) + fixup_miss_root += 1 var/mutable_appearance/MA = new(appearance) if (plane_needs_fix) @@ -689,6 +783,29 @@ SUBSYSTEM_DEF(zcopy) return MA +/client + var/list/zm_objs = list() + +/datum/controller/subsystem/zcopy/proc/CreateSlice(client/C, path, depth) + ASSERT(ispath(path)) + var/obj/mimic_master/slice/slice = new path(null, depth) + C.screen += slice + LAZYADD(C.zm_objs[slice.slice_prefix], slice) + +/datum/controller/subsystem/zcopy/proc/CreatePrimordialSlice(client/C, path) + var/obj/mimic_master/master = new path + C.screen += master + LAZYADD(C.zm_objs["primordial"], master) + +/datum/controller/subsystem/zcopy/proc/CreatePlanesFor(client/C) + for (var/i in 0 to OPENTURF_MAX_DEPTH) + CreateSlice(C, /obj/mimic_master/slice/basic, i) + CreateSlice(C, /obj/mimic_master/slice/shadower_master, i) + CreateSlice(C, /obj/mimic_master/slice/cap, i) + CreateSlice(C, /obj/mimic_master/slice/virtual/zsum, i) + + CreatePrimordialSlice(C, /obj/mimic_master/plane_zero) + #define FMT_DEPTH(X) (X == null ? "(null)" : X) #define FMT_OK(X) (X) ? "OK" : "MISMATCH" @@ -699,6 +816,9 @@ SUBSYSTEM_DEF(zcopy) var/turf/parent var/computed_depth +/atom/movable/openspace/debug/shadower + var/associated_depth + /client/proc/analyze_openturf(turf/T) set name = "Analyze Openturf" set desc = "Show the layering of an openturf and everything it's mimicking." @@ -718,12 +838,36 @@ SUBSYSTEM_DEF(zcopy) var/list/temp_objects = list() + // Manually compute stack information to check connections + var/turf/Tscan = T + var/list/computed_stack = list() + if (TURF_IS_MIMIC(T)) // non-mimic turfs can enter this proc due to boundaries + while (Tscan && (Tscan.z_flags & ZM_MIMIC_BELOW) && !(Tscan.z_flags & ZM_OVERRIDE)) + computed_stack += Tscan + Tscan = GetBelow(Tscan) + + // terminating/root turf + computed_stack += Tscan + else + Tscan = null + + var/root = "(null)" + if (T.z_discovered_root) + root = "[T.z_discovered_root] (z [T.z_discovered_root.z], ty [T.z_discovered_root.type], \ref[T.z_discovered_root])" + + var/computed_root = "(null)" + if (Tscan) + computed_root = "[Tscan] (z [Tscan.z], ty [Tscan.type], \ref[Tscan])" + var/is_above_space = T.is_above_space() var/list/out = list( "", "

Analysis of [T] at [T.x],[T.y],[T.z]

", "Queue occurrences: [T.z_queued]", "Above space: Apparent [T.z_eventually_space ? "Yes" : "No"], Actual [is_above_space ? "Yes" : "No"] - [FMT_OK(T.z_eventually_space == is_above_space)]", + "Root: [FMT_OK(T.z_discovered_root == Tscan)]", + "- Apparent [root]", + "- Actual [computed_root]", "Z Flags: [english_list(bitfield2list(T.z_flags, global.mimic_defines), "(none)")]", "Has Shadower: [T.shadower ? "Yes" : "No"]", "Has turf proxy: [T.mimic_proxy ? "Yes" : "No"]", @@ -731,40 +875,63 @@ SUBSYSTEM_DEF(zcopy) "Has mimic underlay: [T.mimic_underlay ? "Yes" : "No"]", "Below: [!T.below ? "(nothing)" : "[T.below] at [T.below.x],[T.below.y],[T.below.z]"]", "Depth: [FMT_DEPTH(T.z_depth)] [T.z_depth == OPENTURF_MAX_DEPTH ? "(max)" : ""]", - "Generation: [T.z_generation]", - "Update count: Claimed [claimed_update_count], Actual [real_update_count] - [FMT_OK(claimed_update_count == real_update_count)]", - "
    " + "Generation: [T.z_generation] general, [T.z_generation_lighting] lighting", + "Update count: Claimed [claimed_update_count], Actual [real_update_count] - [FMT_OK(claimed_update_count == real_update_count)]" ) - if (!T.below) - out += "

    Using synthetic rendering (Not Z).

    " - else if (T.z_flags & ZM_OVERRIDE) - out += "

    Using synthetic rendering (OVERRIDE).

    " - var/list/found_oo = list(T) - var/turf/Tbelow = T - while ((Tbelow = Tbelow.below)) + var/list/shadow_stack = list(T.shadower) + var/list/apparent_stack = list() + + for (var/turf/Tbelow = T; TURF_IS_MIMIC(Tbelow); Tbelow = Tbelow.below) var/atom/movable/openspace/debug/turf/VTO = new VTO.computed_depth = SSzcopy.zlev_maximums[Tbelow.z] - Tbelow.z VTO.appearance = Tbelow VTO.parent = Tbelow - VTO.plane = OPENTURF_MAX_PLANE - VTO.computed_depth + VTO.plane = ZM_COMPUTE_PLANE(VTO.computed_depth, ZM_SLICE_SLOT_ROOT) found_oo += VTO temp_objects += VTO + shadow_stack += Tbelow.shadower + apparent_stack += Tbelow + + // manually add root, since above loop (intentionally) omits it + if (apparent_stack.len) + apparent_stack += apparent_stack[apparent_stack.len]?:below + + if (computed_stack ~= apparent_stack) + out += "Z-stack: OK" + out += SSzcopy.debug_fmt_turf_list(computed_stack) + else + out += "Z-stack: MISMATCH" + out += "Expected:" + out += SSzcopy.debug_fmt_turf_list(computed_stack) + out += "Actual:" + out += SSzcopy.debug_fmt_turf_list(apparent_stack) + + out += "
    " + + if (!TURF_IS_MIMIC(T)) + out += "

    Not a mimic.

    " + else if (!T.below) + out += "

    Using synthetic rendering (Not Z).

    " + else if (T.z_flags & ZM_OVERRIDE) + out += "

    Using synthetic rendering (OVERRIDE) — override is [T.z_appearance ? "MANUAL" : "BASETURF (resolved to [get_base_turf_by_area(T)])"].

    " for (var/atom/movable/openspace/O in T) found_oo += O - if (T.shadower.overlays.len) - for (var/overlay in T.shadower.overlays) - var/atom/movable/openspace/debug/D = new - D.appearance = overlay - if (D.plane < -10000) // FLOAT_PLANE - D.plane = T.shadower.plane - found_oo += D - temp_objects += D + for (var/atom/movable/shadower in shadow_stack) + if (shadower.overlays.len) + for (var/overlay in shadower.overlays) + var/atom/movable/openspace/debug/shadower/D = new + D.associated_depth = shadower.loc:z_depth + D.appearance = overlay + if (D.plane < -10000) // FLOAT_PLANE + D.plane = T.shadower.plane + found_oo += D + temp_objects += D - sortTim(found_oo, /proc/cmp_planelayer) + sortTim(found_oo, GLOBAL_PROC_REF(cmp_planelayer)) var/list/atoms_list_list = list() for (var/thing in found_oo) @@ -773,23 +940,39 @@ SUBSYSTEM_DEF(zcopy) LAZYINITLIST(atoms_list_list[pl]) atoms_list_list[pl] += A - if (atoms_list_list["0"]) + if (atoms_list_list["[DEFAULT_PLANE]"]) out += "Non-Z" - SSzcopy.debug_fmt_planelist(atoms_list_list["0"], out, T) + SSzcopy.debug_fmt_planelist(atoms_list_list["[DEFAULT_PLANE]"], out, T) - atoms_list_list -= "0" + atoms_list_list -= "[DEFAULT_PLANE]" - for (var/d in 0 to OPENTURF_MAX_DEPTH) - var/pl = OPENTURF_MAX_PLANE - d - if (!atoms_list_list["[pl]"]) - out += "Depth [d], planes [pl] — empty" - continue + if (atoms_list_list["[LIGHTING_PLANE]"]) + out += "Upper lighting plane" + SSzcopy.debug_fmt_planelist(atoms_list_list["[LIGHTING_PLANE]"], out, T) - out += "Depth [d], plane [pl]" - SSzcopy.debug_fmt_planelist(atoms_list_list["[pl]"], out, T) + atoms_list_list -= "[LIGHTING_PLANE]" - // Flush the list so we can find orphans. - atoms_list_list -= "[pl]" + for (var/d in 0 to OPENTURF_MAX_DEPTH) + var/list/local_temp = list() + var/list/offsets = list() + var/local_acc = 0 + for (var/offset in (OPENTURF_PLANES_PER_DEPTH - 1) to 0 step -1) + var/ident = zm_offset_to_target[offset + 1] + var/plane = ZM_COMPUTE_PLANE(d, offset) + var/plane_str = "[plane]" + + offsets += plane + + local_temp += "Depth [d] ([ident]), plane [plane], computed target [ZM_SLICE(zm_offset_to_target[offset + 1], d)]" + SSzcopy.debug_fmt_planelist(atoms_list_list[plane_str], local_temp, T) + local_acc += length(atoms_list_list[plane_str]) + atoms_list_list -= plane_str // remove the found plane so we can find orphans + + // potentially wasted work (strings above are built eagerly), but it's a debug verb + if (local_acc) + out += local_temp + else + out += "Depth [d], planes [jointext(offsets, "/")] — empty" if (atoms_list_list["[SPACE_PLANE]"]) // Space parallax plane out += "Space parallax plane ([SPACE_PLANE])" @@ -805,7 +988,7 @@ SUBSYSTEM_DEF(zcopy) out += "" - show_browser(usr, out.Join("
    "), "size=980x580;window=openturfanalysis-\ref[T]") + show_browser(usr, out.Join("
    "), "size=1200x950;window=openturfanalysis-\ref[T]") for (var/item in temp_objects) qdel(item) @@ -814,12 +997,14 @@ SUBSYSTEM_DEF(zcopy) /datum/controller/subsystem/zcopy/proc/debug_fmt_thing(atom/A, list/out, turf/original) if (istype(A, /atom/movable/openspace/mimic)) var/atom/movable/openspace/mimic/OO = A - var/base = "
  • [fmt_label("Mimic", A)] plane [A.plane], layer [A.layer], depth [FMT_DEPTH(OO.depth)]" + var/base = "
  • [fmt_label("Mimic", A)] plane [A.plane], layer [A.layer], depth [FMT_DEPTH(OO.depth)], override depth [FMT_DEPTH(OO.override_depth)], [OO.reset_generation] resets" if (QDELETED(OO.associated_atom)) // This shouldn't happen, but can if the deletion hook is not working. return "[base] - [OO.type] copying ([OO.mimiced_type]) - ORPHANED
  • " var/atom/movable/AA = OO.associated_atom var/copied_type = AA.type == OO.mimiced_type ? "[AA.type] \[direct\]" : "[AA.type], eventually [OO.mimiced_type]" + if (OO.mimiced_type == /atom/movable/openspace/mimic) // This is invalid, these should always be a 'real' type. + copied_type += " CORRUPT" return "[base], associated Z-level [AA.z] - [OO.type] copying [AA] ([copied_type])" else if (istype(A, /atom/movable/openspace/turf_mimic)) @@ -835,10 +1020,10 @@ SUBSYSTEM_DEF(zcopy) else if (A.type == /atom/movable/openspace/multiplier) - return "
  • [fmt_label("Shadower", A)] plane [A.plane], layer [A.layer], Z-level [A.z] - [A] ([A.type])
  • " + return "
  • [fmt_label("Shadower", A)] plane [A.plane], layer [A.layer], Z-level [A.z] - [A] ([A.type]), generation dyn [A:lighting_generation], static [A:lighting_generation_static]
  • " - else if (A.type == /atom/movable/openspace/debug) // These are fake objects that exist just to show the shadower's overlays in this list. - return "
  • [fmt_label("Shadower True Overlay", A, vv = FALSE)] plane [A.plane], layer [A.layer] - VIRTUAL
  • " + else if (A.type == /atom/movable/openspace/debug/shadower) // These are fake objects that exist just to show the shadower's overlays in this list. + return "
  • [fmt_label("Shadower True Overlay", A, vv = FALSE)] depth [A:associated_depth], plane [A.plane], layer [A.layer] - VIRTUAL
  • " else if (A.type == /atom/movable/openspace/debug/turf) var/atom/movable/openspace/debug/turf/VTO = A @@ -853,7 +1038,7 @@ SUBSYSTEM_DEF(zcopy) /datum/controller/subsystem/zcopy/proc/fmt_label(label, atom/target, vv = TRUE) . = "\icon[target] \[[label]\] " if (vv) - . += "(VV) " + . += "(VV) " /datum/controller/subsystem/zcopy/proc/debug_fmt_planelist(list/things, list/out, turf/original) if (things) @@ -864,6 +1049,25 @@ SUBSYSTEM_DEF(zcopy) else out += "No atoms." +/datum/controller/subsystem/zcopy/proc/debug_fmt_turf_list(list/turf/turfs) + var/list/working = list("
      ") + for (var/item in turfs) + var/turf/T = astype(item, /turf) + if (!T) + working += "
    • (Non-turf: [item?:type || ""])
    • " + continue + + var/list/flags = bitfield2list(T.z_flags, global.mimic_defines) + var/flags_text = "(none)" + if (flags.len) + flags_text = "[flags.Join(" | ")]" + + working += "
    • [T.z]: [T], [T.type], flags [flags_text]
    • " + + working += "
    " + + return working.Join("\n") + #undef FMT_DEPTH #undef FMT_OK #undef ZM_RECORD_START @@ -878,6 +1082,10 @@ SUBSYSTEM_DEF(zcopy) if(!check_rights(R_DEBUG)) return + if (!length(SSzcopy.turf_stats)) + alert("No stats.") + return + render_stats(SSzcopy.turf_stats, src) /client/proc/zms_display_discovery() @@ -887,6 +1095,10 @@ SUBSYSTEM_DEF(zcopy) if(!check_rights(R_DEBUG)) return + if (!length(SSzcopy.discovery_stats)) + alert("No stats.") + return + render_stats(SSzcopy.discovery_stats, src) /client/proc/zms_display_mimic() @@ -896,7 +1108,10 @@ SUBSYSTEM_DEF(zcopy) if(!check_rights(R_DEBUG)) return - render_stats(SSzcopy.mimic_stats, src) + if (!length(SSzcopy.mimic_stats)) + alert("No stats.") + return + render_stats(SSzcopy.mimic_stats, src) #endif diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm index 628b5880042e..71f795cbf8bf 100644 --- a/code/game/atoms_movable.dm +++ b/code/game/atoms_movable.dm @@ -168,7 +168,6 @@ // Both the origin and destination are turfs with different areas. // When either origin or destination is a turf and the other is not. var/is_new_area = (is_origin_turf ^ is_destination_turf) || (is_origin_turf && is_destination_turf && loc.loc != destination.loc) - var/was_below_z_turf = MOVABLE_IS_BELOW_ZTURF(src) var/atom/origin = loc loc = destination @@ -210,17 +209,48 @@ L = thing L.source_atom.update_light() - // Z-Mimic. - if (bound_overlay) - // The overlay will handle cleaning itself up on non-openspace turfs. - if (isturf(destination)) + // Z-Mimic. + if (bound_overlay) + // Some types (like humantypes) change this per Move. + if (bound_overlay.glide_size != glide_size) + bound_overlay.glide_size = glide_size + // The overlay will handle cleaning itself up on non-openspace turfs. bound_overlay.forceMove(get_step(src, UP)) - if (dir != bound_overlay.dir) + if (bound_overlay.dir != dir) bound_overlay.set_dir(dir) - else // Not a turf, so we need to destroy immediately instead of waiting for the destruction timer to proc. - qdel(bound_overlay) - else if (isturf(loc) && (!origin || !was_below_z_turf) && MOVABLE_SHALL_MIMIC(src)) - SSzcopy.discover_movable(src) + +#if ZM_STATEFUL_MIMIC_FLAGS != 0 + var/turf/Told = astype(origin, /turf)?.above +#endif + var/turf/Tnew = astype(loc, /turf)?.above + + if (Tnew) +#if ZM_STATEFUL_MIMIC_FLAGS != 0 + if (Told) + var/old_flags = Told.z_flags & ZM_STATEFUL_MIMIC_FLAGS + var/new_flags = Tnew.z_flags & ZM_STATEFUL_MIMIC_FLAGS + + if (old_flags != new_flags) + ZM_DEBUG_LOG("Told = [old_flags], Tnew = [new_flags]") + bound_overlay.reset_internal_layering() +#endif + var/target_state = bound_overlay.hidden + if (Tnew.mouse_opacity == 2 && (Tnew.z_flags & ZM_HIDE_ATOMS)) + target_state |= ZM_HIDE_OPAQUE + else + target_state &= ~ZM_HIDE_OPAQUE + + if (Tnew.z_flags & ZM_BOUNDARY) + target_state |= ZM_HIDE_BOUNDARY + else + target_state &= ~ZM_HIDE_BOUNDARY + + if (bound_overlay.hidden != target_state) + bound_overlay.name = target_state ? "" : bound_overlay.cached_name + bound_overlay.hidden = target_state + + else if (isturf(loc) && (!origin || !TURF_IS_MIMICKING(origin)) && MOVABLE_SHALL_MIMIC(src) && MOVABLE_IS_BELOW_ZTURF(src)) + SSzcopy.discover_movable(src) if(buckled_mob) if(isturf(loc)) @@ -244,7 +274,6 @@ /atom/movable/Move(...) var/old_loc = loc - var/was_below_z_turf = MOVABLE_IS_BELOW_ZTURF(src) . = ..() if(.) @@ -276,11 +305,45 @@ // Z-Mimic. if (bound_overlay) + // Some types (like humantypes) change this per Move. + if (bound_overlay.glide_size != glide_size) + bound_overlay.glide_size = glide_size // The overlay will handle cleaning itself up on non-openspace turfs. bound_overlay.forceMove(get_step(src, UP)) if (bound_overlay.dir != dir) bound_overlay.set_dir(dir) - else if (isturf(loc) && (!old_loc || !was_below_z_turf) && MOVABLE_SHALL_MIMIC(src)) + +#if ZM_STATEFUL_MIMIC_FLAGS != 0 + var/turf/Told = astype(old_loc, /turf)?.above +#endif + var/turf/Tnew = astype(loc, /turf)?.above + + if (Tnew) +#if ZM_STATEFUL_MIMIC_FLAGS != 0 + if (Told) + var/old_flags = Told.z_flags & ZM_STATEFUL_MIMIC_FLAGS + var/new_flags = Tnew.z_flags & ZM_STATEFUL_MIMIC_FLAGS + + if (old_flags != new_flags) + ZM_DEBUG_LOG("Told = [old_flags], Tnew = [new_flags]") + bound_overlay.reset_internal_layering() +#endif + var/target_state = bound_overlay.hidden + if (Tnew.mouse_opacity == 2 && (Tnew.z_flags & ZM_HIDE_ATOMS)) + target_state |= ZM_HIDE_OPAQUE + else + target_state &= ~ZM_HIDE_OPAQUE + + if (Tnew.z_flags & ZM_BOUNDARY) + target_state |= ZM_HIDE_BOUNDARY + else + target_state &= ~ZM_HIDE_BOUNDARY + + if (bound_overlay.hidden != target_state) + bound_overlay.name = target_state ? "" : bound_overlay.cached_name + bound_overlay.hidden = target_state + + else if (isturf(loc) && (!old_loc || !TURF_IS_MIMICKING(old_loc)) && MOVABLE_SHALL_MIMIC(src) && MOVABLE_IS_BELOW_ZTURF(src)) SSzcopy.discover_movable(src) if(isturf(loc)) diff --git a/code/game/turfs/space/space.dm b/code/game/turfs/space/space.dm index 21fce02894d7..24d207004f5c 100644 --- a/code/game/turfs/space/space.dm +++ b/code/game/turfs/space/space.dm @@ -19,7 +19,7 @@ /// Force this one to pretend it's an overedge turf. var/forced_dirs = 0 -/turf/space/Initialize() +/turf/space/Initialize(mapload) SHOULD_CALL_PARENT(FALSE) atom_flags |= ATOM_FLAG_INITIALIZED @@ -44,6 +44,10 @@ else //Dust appearance = SSskybox.dust_cache["[((x + y) ^ ~(x * y) + z) % 25]"] + if (mapload && (z_flags & ZM_MIMIC_BELOW)) + // If CT fired, ZM is done in CT for BOUNDARY ordering reasons. + setup_zmimic(mapload) + if(!HasBelow(z)) return INITIALIZE_HINT_NORMAL diff --git a/code/game/turfs/turf.dm b/code/game/turfs/turf.dm index 28171b7c2394..99b015282ec8 100644 --- a/code/game/turfs/turf.dm +++ b/code/game/turfs/turf.dm @@ -140,7 +140,8 @@ if(simulated) updateVisibility(src, FALSE) - if (z_flags & ZM_MIMIC_BELOW) + if (mapload && (z_flags & ZM_MIMIC_BELOW)) + // If CT fired, ZM is done in CT for BOUNDARY ordering reasons. setup_zmimic(mapload) if(flooded) diff --git a/code/game/turfs/turf_ao.dm b/code/game/turfs/turf_ao.dm index c07ebeceb522..69f5b48fc34f 100644 --- a/code/game/turfs/turf_ao.dm +++ b/code/game/turfs/turf_ao.dm @@ -1,5 +1,10 @@ +#ifdef AO_USE_LIGHTING_OPACITY +#define AO_TURF_CHECK(T) (!T.has_opaque_atom || !T.permit_ao) +#define AO_SELF_CHECK(T) (!T.has_opaque_atom) +#else #define AO_TURF_CHECK(T) (!T.density || !T.opacity || !T.permit_ao) #define AO_SELF_CHECK(T) (!T.density && !T.opacity) +#endif /turf var/permit_ao = TRUE @@ -28,16 +33,19 @@ if (AO_SELF_CHECK(src) && !(z_flags & ZM_MIMIC_NO_AO)) CALCULATE_NEIGHBORS(src, ao_neighbors, T, AO_TURF_CHECK(T)) -/proc/make_ao_image(corner, i, px = 0, py = 0, pz = 0, pw = 0) +/proc/make_ao_image(corner, i, px = 0, py = 0, pz = 0, pw = 0, alpha, plane) var/list/cache = SSao.cache var/cstr = "[corner]" - var/key = "[cstr]-[i]-[px]/[py]/[pz]/[pw]" + // PROCESS_AO_CORNER below also uses this cache, check it before changing this key. + var/key = "[cstr]|[i]|[px]/[py]/[pz]/[pw]|[alpha]|[plane]" var/image/I = image('icons/turf/flooring/shadows.dmi', cstr, dir = BITFLAG(i-1)) - I.alpha = WALL_AO_ALPHA + I.alpha = alpha I.blend_mode = BLEND_OVERLAY I.appearance_flags = RESET_ALPHA|RESET_COLOR|TILE_BOUND I.layer = AO_LAYER + if (plane) + I.plane = plane // If there's an offset, counteract it. if (px || py || pz || pw) I.pixel_x = -px @@ -55,7 +63,7 @@ if (ao_queued < new_level) ao_queued = new_level -#define PROCESS_AO_CORNER(AO_LIST, NEIGHBORS, CORNER_INDEX, CDIR) \ +#define PROCESS_AO_CORNER(AO_LIST, NEIGHBORS, CORNER_INDEX, CDIR, ALPHA, TARGET, PLANE) \ corner = 0; \ if (NEIGHBORS & (BITFLAG(CDIR))) { \ corner |= 2; \ @@ -67,9 +75,9 @@ corner |= 4; \ } \ if (corner != 7) { /* 7 is the 'no shadows' state, no reason to add overlays for it. */ \ - var/image/I = cache["[corner]-[CORNER_INDEX]-[pixel_x]/[pixel_y]/[pixel_z]/[pixel_w]"]; \ + var/image/I = cache["[corner]|[CORNER_INDEX]|[pixel_x]/[pixel_y]/[pixel_z]/[pixel_w]|[ALPHA]|[PLANE]"]; \ if (!I) { \ - I = make_ao_image(corner, CORNER_INDEX, pixel_x, pixel_y, pixel_z, pixel_w) /* this will also add the image to the cache. */ \ + I = make_ao_image(corner, CORNER_INDEX, TARGET.pixel_x, TARGET.pixel_y, TARGET.pixel_z, TARGET.pixel_w, ALPHA, PLANE) /* this will also add the image to the cache. */ \ } \ LAZYADD(AO_LIST, I); \ } @@ -80,13 +88,13 @@ AO_LIST.Cut(); \ } -#define REGEN_AO(TARGET, AO_LIST, NEIGHBORS) \ +#define REGEN_AO(TARGET, AO_LIST, NEIGHBORS, ALPHA, PLANE) \ if (permit_ao && NEIGHBORS != AO_ALL_NEIGHBORS) { \ var/corner;\ - PROCESS_AO_CORNER(AO_LIST, NEIGHBORS, 1, NORTHWEST); \ - PROCESS_AO_CORNER(AO_LIST, NEIGHBORS, 2, SOUTHEAST); \ - PROCESS_AO_CORNER(AO_LIST, NEIGHBORS, 3, NORTHEAST); \ - PROCESS_AO_CORNER(AO_LIST, NEIGHBORS, 4, SOUTHWEST); \ + PROCESS_AO_CORNER(AO_LIST, NEIGHBORS, 1, NORTHWEST, ALPHA, TARGET, PLANE); \ + PROCESS_AO_CORNER(AO_LIST, NEIGHBORS, 2, SOUTHEAST, ALPHA, TARGET, PLANE); \ + PROCESS_AO_CORNER(AO_LIST, NEIGHBORS, 3, NORTHEAST, ALPHA, TARGET, PLANE); \ + PROCESS_AO_CORNER(AO_LIST, NEIGHBORS, 4, SOUTHWEST, ALPHA, TARGET, PLANE); \ } \ UNSETEMPTY(AO_LIST); \ if (AO_LIST && TARGET) { \ @@ -98,11 +106,11 @@ CUT_AO(shadower, ao_overlays_mimic) CUT_AO(src, ao_overlays) if (z_flags & ZM_MIMIC_BELOW) - REGEN_AO(shadower, ao_overlays_mimic, ao_neighbors_mimic) + var/computed_depth = SSzcopy.zlev_maximums[z] - z + 1 + var/target_plane = ZM_COMPUTE_PLANE(computed_depth, ZM_SLICE_SLOT_CAP) + REGEN_AO(shadower, ao_overlays_mimic, ao_neighbors_mimic, Z_AO_ALPHA, target_plane) if (AO_SELF_CHECK(src) && !(z_flags & ZM_MIMIC_NO_AO)) - REGEN_AO(src, ao_overlays, ao_neighbors) - - update_above() + REGEN_AO(src, ao_overlays, ao_neighbors, WALL_AO_ALPHA, FLOAT_PLANE) #undef REGEN_AO #undef PROCESS_AO_CORNER diff --git a/code/game/turfs/turf_changing.dm b/code/game/turfs/turf_changing.dm index 0dfa901cf988..3ca4cbca59b5 100644 --- a/code/game/turfs/turf_changing.dm +++ b/code/game/turfs/turf_changing.dm @@ -66,6 +66,7 @@ var/old_lighting_overlay = lighting_overlay var/old_dynamic_lighting = TURF_IS_DYNAMICALLY_LIT_UNSAFE(src) var/old_z_opacity = z_flags & ZM_ALLOW_LIGHTING + var/old_z_flags = z_flags & ZM_INFECTIOUS_MIMIC_FLAGS var/old_flooded = flooded var/old_outside = is_outside var/old_is_open = is_open() @@ -110,8 +111,11 @@ changed_turf.affecting_heat_sources = old_affecting_heat_sources +#ifndef AO_USE_LIGHTING_OPACITY + // If we're using opacity-based AO, this is done in recalc_atom_opacity(). if (permit_ao) regenerate_ao() +#endif // Update ZAS, atmos and fire. if(keep_air && changed_turf.can_inherit_air) @@ -133,7 +137,18 @@ if(changed_turf.density != old_density && changed_turf.event_listeners?[/decl/observ/density_set]) changed_turf.raise_event_non_global(/decl/observ/density_set, old_density, changed_turf.density) - // lighting stuff + // lighting and z-mimic stuff + + // This only copies a subset, see ZM_INFECTIOUS_MIMIC_FLAGS. This must be done before ambient lights are rebuilt. + if (old_z_flags) + z_flags |= old_z_flags + if (z_flags & ZM_MIMIC_BELOW) + setup_zmimic(FALSE) + // If we're a boundary *but not also a mimic*, initialize the boundary info. + else if (z_flags & ZM_BOUNDARY) + setup_zmimic_boundary() + else if (z_flags & ZM_MIMIC_BELOW) + setup_zmimic(FALSE) affecting_lights = old_affecting_lights corners = old_corners diff --git a/code/modules/lighting/lighting_overlay.dm b/code/modules/lighting/lighting_overlay.dm index c4900999b891..bb176509bbde 100644 --- a/code/modules/lighting/lighting_overlay.dm +++ b/code/modules/lighting/lighting_overlay.dm @@ -10,6 +10,7 @@ invisibility = INVISIBILITY_LIGHTING simulated = FALSE blend_mode = BLEND_OVERLAY + z_flags = ZMM_IGNORE var/needs_update = FALSE @@ -133,10 +134,9 @@ // If there's a Z-turf above us, update its shadower. if (T.above) - if (T.above.shadower) - T.above.shadower.copy_lighting(src) - else - T.above.update_mimic() + // This used to directly call the relevant update procs (currently `SSzcopy.update_lighting(turf)`), but doing that tends to explode elevators. + // This is somewhat less efficient, but works reliably. + T.above.update_mimic() #undef ALL_EQUAL diff --git a/code/modules/mob/login.dm b/code/modules/mob/login.dm index 23e4895e065a..9ec3f7da12d2 100644 --- a/code/modules/mob/login.dm +++ b/code/modules/mob/login.dm @@ -69,6 +69,7 @@ client.images = null //remove the images such as AIs being unable to see runes client.screen = list() //remove hud items just in case client.set_right_click_menu_mode(shift_to_open_context_menu) + client.zm_objs.Cut() next_move = 1 set_sight(sight|SEE_SELF) @@ -90,6 +91,7 @@ RAISE_EVENT(/decl/observ/logged_in, src) hud_reset(TRUE) + SSzcopy.CreatePlanesFor(client) if(istype(machine)) machine.on_user_login(src) diff --git a/code/modules/multiz/zmimic/mimic_docs.dm b/code/modules/multiz/zmimic/mimic_docs.dm index e3d794acafb0..88ac1346d44b 100644 --- a/code/modules/multiz/zmimic/mimic_docs.dm +++ b/code/modules/multiz/zmimic/mimic_docs.dm @@ -1,101 +1,177 @@ /* -Types (also see terminology section): - openspace/multiplier -> shadows the below level, also copies lighting - openspace/mimic -> copies below movables - openspace/turf_proxy -> holds the appearance of the below turf for non-OVERWRITE Z-turfs - openspace/turf_mimic -> copies openspace/turf_proxy objects - -Public API: - Notifying Z-Mimic of icon updates: - - UPDATE_OO_IF_PRESENT - - valid on movables only - - if this movable is being copied, update the copies - - cheap (if this movable is not being mimiced, this is a null check) - - - atom/update_above() - - similar to UPDATE_OO_IF_PRESENT, but for both turfs and movables - - less cheap (pretty much just proc-call overhead) - - Checking state: - - TURF_IS_MIMICKING(turf or any) - - value: bool - if the passed turf is z-mimic enabled - - - movable/get_above_oo() - - return: list of movables - - get a list of every openspace mimic that's copying this atom for things like animate() - - Changing state: - - turf/enable_zmimic(extra_flags = 0) - - return: bool - FALSE if this turf was already mimicking, TRUE otherwise - - Enables z-mimic for this turf, potentially adding extra z_flags. - - This will automatically queue the turf for update. - - - turf/disable_zmimic() - - return: bool - FALSE if this turf was not mimicking, TRUE otherwise - - Disables z-mimic for this turf. - - This will clean up associated mimic objects, but they may hang around for a few additional seconds. - - Vars: - - turf/z_flags - - bitfield - - ZM_MIMIC_BELOW: copy below atoms - - ZM_MIMIC_OVERWRITE: z-mimic can overwrite this turf's appearance - - ZM_ALLOW_LIGHTING: lighting should pass through this turf - - ZM_ALLOW_ATMOS: air should pass through this turf - - ZM_MIMIC_NO_AO: normal turf AO should be skipped, only do openspace AO (if your turf is not solid, you probably want this) - - ZM_NO_OCCLUDE: don't block clicking on below atoms if not OVERWRITE - - - atom/movable/z_flags - - bitfield - - ZMM_IGNORE: Do not copy this atom. Atoms with INVISIBILITY_ABSTRACT are automatically not copied. - - ZMM_MANGLE_PLANES: Scan this atom's overlays and monkeypatch explicit plane sets. Fixes emissive overlays shining through floors, but expensive -- use only if necessary. - -Implementation details: - Z-Mimic makes some assumptions. While it may continue to work if these are violated, don't be surprised if it behaves strangely, renders things in the incorrect order, or outright breaks. - - Assumptions: - - Z-Stacks will not be taller than OPENTURF_MAX_DEPTH. - - If violated: Warning emitted on boot, layering may break for items near the bottom of the z-stack. - - Atoms will render correctly if copied to another plane. - - Atoms will layer correctly if copied to the same plane as other arbitrary in-world atoms. - - Atoms without ZMM_MANGLE_PLANES do not have any overlays that have explicit plane sets. - - If violated: Atoms on the below floor may be partially visible on the current floor. - - Z-Stacks are 1:1 across the entire x/y plane. - - If violated: Z-turfs may form nonsensical connections. - - Z-Stacks are contiguous and linear -- get_step(UP) corresponds to moving up a z-level (within a z-stack) in all cases. - - If violated: layering becomes nonsensical. - - Z-Stacks will not be changed (note: adding new Z-stacks is OK) after an openturf has been initialized on that z-stack. - - If violated: Z-Turfs may act as if they are still connected even though they are not. - - /turf/space is never above another turf type in the Z-Stack. - - Turfs that are setting ZM_MIMIC_OVERWRITE do not care about their appearance. - - If violated: Appearance of turf is lost. - - Multiturf movable atoms are symmetric, and centered on their visual center. - - If violated: Multitile atoms may not render in cases where they should. - - SHADOWER_DARKENING_FACTOR and SHADOWER_DARKENING_COLOR represent the same shade of grey. - - If violated: unlit and lit z-turfs may look inconsistent with each other. - - Lighting will mimic correctly without being associated with a plane. - - If violated: depending on implementation, lighting may be inverted, or not render at all. - - This can usually be addressed by changing /atom/movable/openspace/multiplier/proc/copy_lighting(). - - Known Limitations: - - Multiturf movable atoms are not rendered if they are not centered on a z-turf, but overlap one. - - vis_contents is ignored -- mimics will not copy it. - - Terminology (of varying obscurity): - - Z-Stack - - A set of z-connected turfs with the same x/y coordinates. - - Z-Depth - - How many Z-levels this atom is *from the top of a Z-Stack* (absolute layering), regardless of z-turf presence - - Shadower / Multiplier - - An abstract object used to darken lower levels, copy lighting, and host Z-AO overlays. - - Mimic / Openspace Object - - An abstract object that holds appearances of atoms and proxies clicks. - - Turf Proxy / Turf Object - - An abstract object that holds Z-Copy turf appearances for non-OVERWRITE turfs. - - Turf Mimic - - An abstract object that holds appearances of non-OVERWRITE z-turfs below this z-turf. - - Foreign Turf - - A turf below this z-turf that is contributing to our appearance. - - Mimic Underlay - - A turf appearance holder specifically for fake space below a z-turf at the bottom of a z-stack. + + - Z-Mimic - + + This is a system for rendering the Z-level(s) below this one under turfs in a way that's as indistinguishable to players as possible, while also remaining scalable + enough to run on maps with extreme numbers of Z-enabled turfs (100k+). While it may be complex internally, interacting with Z-Mimic should be straightforward. + + + - Usage (Turf) - + + Most of the time just setting the turf level `z_flags` is all you need to do. To enable Z-Mimic for a turf: + + z_flags = MIMIC_DEFAULTS + + There are also presets for some common situations: + - MIMIC_PRESET_HOLE: Just a z-hole. No icon, nothing to cast a shadow on, allows atmos. + - MIMIC_PRESET_HOLE_WITH_BORDER: A z-hole with an icon, like an icon smoothed border. Allows atmos. + - MIMIC_PRESET_TRANSLUCENT_TURF: A turf that has translucent/transparent accents. Does not allow atmos, but allows players to see the things below. Blocks clicks, even where alpha is 0. + + You may want to enable some other flags based on what the turf is doing, but this is all that is necesssary to enable baseline Z copy for a turf. Other flags that are + likely to be useful include: + + - ZM_MIMIC_BELOW: Enable Z-mimic. Part of MIMIC_DEFAULTS. + - ZM_MIMIC_OVERWRITE: Replace the turf's appearance instead of preserving it. This is more efficient, but means the turf can't have its own icon or overlays. This is intended for simple Z-holes. + - ZM_ALLOW_LIGHTING: Allow lights to shine through this Z-turf. Part of MIMIC_DEFAULTS. + - ZM_ALLOW_ATMOS: Allow ZAS to form connections through this Z-turf. + - ZM_NO_SHADOW: Do not darken lighting on this Z-turf. Z-AO will still render. This does not remove the shadower object, nor improve performance. + + Niche use flags that you probably won't need: + - ZM_MIMIC_NO_AO: Skip regular turf AO. This is intended for simple Z-holes. + - ZM_NO_OCCLUDE: By default, Z-Mimic assumes that turfs that are *not* MIMIC_OVERWRITE are blocking the entire turf (like glass flooring) and will intercept clicks. + This will force the turf to allow clickthrough, which can be useful if creating a Z-hole turf with smoothed edges. This does not prevent clicking on the Z-turf itself. + - ZM_OVERRIDE: Unrelated to OVERWRITE. If this is set, Z-Mimic will ignore the normal copy pipeline and will just copy either the turf's baseturf, or use the contents + of the `z_appearance` var if it is set. Z-turfs with this set are considered the bottom of a Z-group, so can be used to prevent Z-Mimic from interacting with Z-connections. + - ZM_BOUNDARY: Internal use, do not set manually. This indicates that this turf is beside an active Z-turf, so is creating atom mimics despite not being a mimic turf. + - ZM_HIDE_ATOMS: If this turf is considered opaque (see MIMIC_NO_OCCLUDE), also hide atoms from the right click menu. This prevents examining below atoms, however. + + Z-Mimic can be toggled after a turf has been created using the `enable_zmimic()` and `disable_zmimic()` procs. + If a turf's appearance has been updated in a way that doesn't involve SSoverlays, call `update_above()` on the turf to instruct Z-Mimic to recopy its appearance. This is cheap. + + - Usage (Movable) - + + Generally movables should not need to care about Z-Mimic. There are some edge cases that may require `z_flags` to be set: + - If your atom is long (greater than one turf along the facing axis), you should set ZMM_LOOKAHEAD. + - If your atom is wide (greater than one turf perpendicular to the facing axis), you should set ZMM_LOOKBESIDE. + - If your atom is wide on both axes, set both flags. + - If your atom always contains overlays with `plane` explicitly set, consider setting ZMM_MANGLE_PLANES to improve efficiency. If this is only sometimes true, omit this flag and let ZM detect. + - If your atom should not render under ZM at all (for instance, it uses rendering features that ZM does not support, or it is an abstract object), set ZMM_IGNORE. + + If you update the appearance of your movable without interacting with SSoverlays, consider adding an `UPDATE_OO_IF_PRESENT` call at the end to notify ZM of appearance updates. This call is very cheap. + + - Public API - + These are all calls in ZM that are considered public API, anything other these calls is considered unstable (in the API sense) and may change without regards to compatibility. + + Movables: + - `UPDATE_OO_IF_PRESENT` (macro) + - Only valid in contexts where `src` is a movable. + - If this movable has an associated Z-Mimic mimic, update its appearance. + - Cheap, but SSoverlays will automatically run this for you if you make overlay calls. + + - `MOVABLE_IS_BELOW_ZTURF(M)` (macro) + - Check if a specified movable is below an active Z-mimic turf and should be mimicked. + - This respects the LOOKAHEAD/LOOKBESIDE flags. + + - `MOVABLE_IS_ON_ZTURF(M)` (macro) + - Check if a specified movable is on an active Z-mimic turf. + - This respects the LOOKAHEAD/LOOKBESIDE flags. + + - `z_flags` (var) + - Flags `ZMM_IGNORE`, `ZMM_LOOKAHEAD`, `ZMM_LOOKBESIDE`, and `ZMM_MANGLE_PLANES` are considered stable. + + - `get_above_oo()` (proc) + - Return a list of mimics copying this movable, directly or indirectly. + - This can be useful if trying to `animate()` a movable and you want this animation to also apply to mimics. + - Keep in mind that the mimic cannot update its appearance while `animate()` runs. + + Turfs: + - `TURF_IS_MIMICKING(T)` (macro) + - Check if a specified turf is copying movables. This includes mimic boundary turfs. + - Cheap. + + - `TURF_IS_MIMIC(T)` (macro) + - Check if a specified turf is a Z-mimic turf. This is implied by the above, but this excludes boundaries. + - Cheap. + + - `enable_zmimic(additional_flags = 0)` (proc) + - Enable Z-mimic on a turf after it has been initialized. + - Returns: TRUE if this turf transitioned from non-mimic to mimic, FALSE otherwise. + + - `disable_zmimic()` (proc) + - Disable Z-mimic on a turf after it has been initialized. + - Returns: TRUE if this turf transitioned from mimic to non-mimic, FALSE otherwise. + - Mimic objects will hang around for a few seconds after this is called, though they should be hidden from rightclick. + + Atoms: + - `update_above()` (proc) + - Update above mimics' appearance. Valid on turfs and movables. + + Z-Copy (SSzcopy): + - `calculate_zstack_limits()` (proc) + - Regenerate Z-group information. Call this if you create new Z-levels, even if they do not contain Z-turfs. + + - `update_all()` (proc) + - Intended for admin/developer proc-call. Do not use in code. + - Force all mimic turfs and mimic objects on the map to update. + + - `hard_reset()` (proc) + - Intended for admin/developer proc-call. Do not use in code. + - Flush all Z-Mimic state and rebuild from scratch. + + - `unsupported_rebuild_z_state()` (proc) + - Harder than hard_reset(). Will forcibly reconstruct ZM connection information, as well as performing a hard reset. + - Please don't use this without knowing its implications. + - Actually using this proc is fully unsupported, and may corrupt Z-lighting for the rest of the round. + - This does not rebuild Z-lighting. + - This does not rebuild boundary information. + - This proc may be necessary if you are doing (unsupported) things with dynamic Z-groups and need to kick Z-Mimic. + +*/ + +/* + + - Z-Mimic Internals - + + ZM fundamentally works by creating mimic objects to hold appearances for movables *directly below*, including other mimic objects. This recursive copy allows ZM to + copy appearance of many levels without having to ever consider more than one level at a time, but requires Z-groups to update bottom to top. This is enforced by allowing + multiple queue entries for the same object, though ZM will only actually evaluate the last entry. ZM stores relations between mimic objects as a doubly-linked list on the + movables, as well as storing turf up/down connections as a doubly-linked list. + + ZM's code makes some assumptions, though this list is not necessarily exhaustive: + - Z-groups will not be taller than OPENSPACE_MAX_DEPTH. + - If violated: Warning emitted on boot, layering behavior undefined for atoms with a depth below OPENSPACE_MAX_DEPTH. + - There is little drawback to increasing OPENSPACE_MAX_DEPTH beyond greater plane use and slightly higher client graphics load. + - Atoms should render correctly if relocated to another plane. + - This generally does not apply to overlays on atoms due to overlay mangling. + - Z coordinates are linear and do not skip levels. + - A turf above another turf (within the same Z-group) is always at `z = below.z + 1`. + - More specifically: `get_step(ref, UP) == GET_ABOVE(ref)`, where turfs are members of the same Z-group. + - ZM does math on the `z` coordinate to calculate depth. + - Z-groups are contiguous across the entire Z-level. + - If two turfs have the same z coordinate, they must have the same z connections / be part of the same z-group. + - ZM assumes that z-groups are global, and stores the maximum Z value for a z-group with z-level granularity. + - Virtual z-levels are only supported if every virtual z in the real z-level has identical Z-connections, or does not interact with ZM (e.g., only one level tall). + - MIMIC_OVERRIDE turfs will block ZM scans and connections below them do not need to make sense, but use with care. + - Z-groups are immutable after they have been used. + - Changing Z-connections after ZM has been initialized on a level is undefined behavior, though primarily due to lighting. + - MIMIC_OVERRIDE turfs will block ZM scans and connections below them can change, but use with care. + - Creating new Z-levels and registering them with Z-Mimic (via `calculate_zstack_limits()`) is allowed and expected. + - Unlike older versions of Z-Mimic, making space turfs mimic is supported. + - Actually doing this will increase Z-Mimic's memory usage on maps with a lot of space, though it will probably not be a problematic amount -- test. + - This may look strange due to interactions with lighting -- depth cues will still render, but lighting generally does not on space. + - `vis_contents` is not copied, though particles are. + - ZMM_LOOKAHEAD and ZMM_LOOKBESIDE only increase the scan radius by one turf (in both directions). + - Atoms larger than this may not render in some cases where they should, but this will probably not be very obvious. Support for these can be added if it becomes necessary. + + Miscellaneous notes: + - The destruction timer exists to preserve gliding in/out below Z-turfs, as well as reduce mimic churn when movables are moving between z and non-z turfs frequently. + - Some of this behavior is now also provided by MIMIC_BOUNDARY turfs, which are more reliable at preserving gliding. + - Mimics that move between Z-levels or move between Z-turfs with different rendering behavior require `reset_internal_layering()` to be run. + - This is handled in Move()/forceMove(), though Z-turfs are only considered different if their Z-flags contain a different set of values in the subset `ZM_STATEFUL_MIMIC_FLAGS`. + - A turf can be a BOUNDARY and a MIMIC at the same time. Converting a BOUNDARY into a MIMIC is considered a boundary promotion, and converting a MIMIC into a BOUNDARY is + considered a boundary demotion. + - ZM proxies examine: when a mimic is examined, it will call its parent atom's examine proc with a z-depth description suffix. + - This behavior does not apply to turfs, though `desc` is copied by ZM too and will display fine. + - ZAO copy is implemented by simply manually assigning ZAO to the correct absolute ZM plane, then blindly copying the overlays. This requires shadowers (which hold the ZAO) to + not be mangled by overlay mangling. + - 'Z-stack' and 'Z-group' are related, but not the same: a z-stack is the discovered stack of active ZM turfs, whereas a Z-group is the set of Z-connections discovered by ZM. + - A z-stack is always within a single z-group, though may span the entire height of the z-group. + + Internal types: + - openspace/multiplier -> shadows below level, holds Z-AO overlays, handles lighting copy. + - These render on ZM_SLICE_SLOT_LIGHTING, with Z-AO being on ZM_SLICE_SLOT_CAP. + - openspace/mimic -> holds appearance of copied atoms, proxies examine + - These are allowed to copy other mimic objects, as well as multipliers. Their behavior may change based on the root object they're copying. + - openspace/turf_proxy -> holds appearance of non-OVERWRITE turfs + - openspace/turf_mimic -> mimic for the above */ diff --git a/code/modules/multiz/zmimic/mimic_masters.dm b/code/modules/multiz/zmimic/mimic_masters.dm new file mode 100644 index 000000000000..307f885792f5 --- /dev/null +++ b/code/modules/multiz/zmimic/mimic_masters.dm @@ -0,0 +1,71 @@ +/obj/mimic_master + appearance_flags = PLANE_MASTER | PASS_MOUSE + screen_loc = "1,1" + +/obj/mimic_master/slice + var/slot + var/root_plane = ZMIMIC_MAXIMUM_PLANE + var/stack_size = OPENTURF_PLANES_PER_DEPTH + var/slice_kind + var/slice_prefix = "slice" + var/assign_target = FALSE + +/obj/mimic_master/slice/New(loc, depth) + ..() + plane = root_plane - ZM_DEPTH_TO_OFFSET_RAW(depth, stack_size) + slot + ASSERT(slice_kind != null) + ASSERT(slot != null) + name = "[slice_prefix] [slice_kind] on [depth] slot [slot] ([plane])" + if (assign_target) // This is used for the basic and blur slices. + render_target = ZM_SLICE(slice_kind, depth) + +/obj/mimic_master/slice/virtual + root_plane = ZM_BASEMENT_MAX_PLANE + stack_size = ZM_BASEMENT_PLANES_PER_DEPTH + slice_prefix = "virtual slice" + +// -- Slices -- + +/// Holding buffer for standard ZM render. +/obj/mimic_master/slice/basic + appearance_flags = PLANE_MASTER | PASS_MOUSE + slot = ZM_SLICE_SLOT_ROOT + slice_kind = ZM_SLICE_TY_BASIC + assign_target = TRUE + +/// Contains shadower objects. +/obj/mimic_master/slice/shadower_master + blend_mode = BLEND_MULTIPLY + slot = ZM_SLICE_SLOT_LIGHTING + slice_kind = ZM_SLICE_TY_LIGHTING + +/obj/mimic_master/slice/shadower_master/New(loc, depth) + ..() + filters += filter(type = "alpha", render_source = ZM_SLICE_VIRTUAL(ZM_SLICE_TY_ZSUM, depth)) + +/obj/mimic_master/slice/cap + slot = ZM_SLICE_SLOT_CAP + slice_kind = ZM_SLICE_TY_CAP + +// -- Virtual slices -- + +/// Sums ZM appearances, excluding shadowers. +/obj/mimic_master/slice/virtual/zsum + slot = ZM_VSLICE_SLOT_ZSUM + slice_kind = ZM_SLICE_TY_ZSUM + +/obj/mimic_master/slice/virtual/zsum/New(loc, depth) + ..() + if (depth != OPENTURF_MAX_DEPTH) + render_source = ZM_SLICE_VIRTUAL(ZM_SLICE_TY_ZSUM, depth + 1) + filters = list( + filter(type = "layer", render_source = ZM_SLICE(ZM_SLICE_TY_BASIC, depth)) + ) + render_target = ZM_SLICE_VIRTUAL(ZM_SLICE_TY_ZSUM, depth) + +// -- Non-slice Z masters - + +/// Contains game world. +/obj/mimic_master/plane_zero + plane = DEFAULT_PLANE + render_target = "plane_zero" diff --git a/code/modules/multiz/zmimic/mimic_movable.dm b/code/modules/multiz/zmimic/mimic_movable.dm index 2af50724b884..86c1659324ed 100644 --- a/code/modules/multiz/zmimic/mimic_movable.dm +++ b/code/modules/multiz/zmimic/mimic_movable.dm @@ -10,13 +10,17 @@ bound_overlay.set_dir(ndir) /atom/movable/update_above() - if (!bound_overlay || !isturf(loc)) + if (!isturf(loc)) return if (MOVABLE_IS_BELOW_ZTURF(src)) + if (!bound_overlay) + SSzcopy.discover_movable(src) + return + SSzcopy.queued_overlays += bound_overlay bound_overlay.queued += 1 - else + else if (bound_overlay) // qdel is null-safe, but might as well save a proc call qdel(bound_overlay) // Grabs a list of every openspace object that's directly or indirectly mimicking this object. Returns an empty list if none found. @@ -35,8 +39,9 @@ anchored = TRUE mouse_opacity = FALSE abstract_type = /atom/movable/openspace // unsure if this is valid, check with Lohi -- Yes, it's valid. + var/target_slot = 0 -/atom/movable/openspace/can_fall(anchor_bypass = FALSE, turf/location_override = loc) +/atom/movable/openspace/can_fall() return FALSE // No blowing up abstract objects. @@ -52,10 +57,11 @@ desc = "You shouldn't see this." icon = 'icons/effects/lighting_overlay.dmi' icon_state = "blank" - plane = OPENTURF_MAX_PLANE + plane = ZM_COMPUTE_PLANE(0, ZM_SLICE_SLOT_LIGHTING) layer = MIMICED_LIGHTING_LAYER - blend_mode = BLEND_MULTIPLY color = SHADOWER_DARKENING_COLOR + var/lighting_generation = 0 + var/lighting_generation_static = 0 /atom/movable/openspace/multiplier/Destroy(force) if(!force) @@ -67,13 +73,17 @@ return ..() -/atom/movable/openspace/multiplier/proc/copy_lighting(atom/movable/lighting_overlay/LO, use_shadower_mult = TRUE) +/atom/movable/openspace/multiplier/proc/copy_lighting(atom/movable/lighting_overlay/LO) + if (LO.needs_update) + // If this LO is pending an update, avoid this update and just let it update us. + return + ASSERT(LO.z == z - 1) var/mutable_appearance/MA = new /mutable_appearance(LO) MA.layer = MIMICED_LIGHTING_LAYER - MA.plane = OPENTURF_MAX_PLANE - MA.blend_mode = BLEND_MULTIPLY + MA.plane = ZM_COMPUTE_PLANE(0, ZM_SLICE_SLOT_LIGHTING) - if (use_shadower_mult) + var/turf/T = loc + if (!(T.below.z_flags & ZM_NO_SHADOW)) if (MA.icon_state == LIGHTING_BASE_ICON_STATE) // We're using a color matrix, so just darken the colors across the board. var/list/c_list = MA.color @@ -93,7 +103,10 @@ else // Not a color matrix, so we can just use the color var ourselves. MA.color = SHADOWER_DARKENING_COLOR + appearance = MA + + lighting_generation += 1 set_invisibility(INVISIBILITY_NONE) if (our_overlays || priority_overlays) @@ -106,7 +119,7 @@ // Object used to hold a mimiced atom's appearance. /atom/movable/openspace/mimic - plane = OPENTURF_MAX_PLANE + plane = ZMIMIC_MAXIMUM_PLANE var/atom/movable/associated_atom var/depth var/queued = 0 @@ -114,11 +127,15 @@ var/mimiced_type var/original_z var/override_depth + var/reset_generation = 0 + var/hidden = FALSE + var/cached_name var/have_performed_fixup = FALSE /atom/movable/openspace/mimic/New() - atom_flags |= ATOM_FLAG_INITIALIZED + atom_flags = ATOM_FLAG_INITIALIZED SSzcopy.openspace_overlays += 1 + loc?.Entered(src, null) /atom/movable/openspace/mimic/Destroy() SSzcopy.openspace_overlays -= 1 @@ -133,6 +150,10 @@ return ..() +/atom/movable/openspace/mimic/proc/timeout(filename, line, reason) + ZM_DEBUG_LOG("Mimic timeout from origin [filename]:[line], caused by [reason]; was mimicking [associated_atom || "NULL"] ([associated_atom?.type || "NULL"]) at [x],[y],[z]") + qdel(src) + /atom/movable/openspace/mimic/attackby(obj/item/used_item, mob/user) to_chat(user, SPAN_NOTICE("\The [src] is too far away.")) return TRUE @@ -146,6 +167,11 @@ SHOULD_CALL_PARENT(FALSE) return associated_atom.examined_by(user, distance, infix, suffix) +/atom/movable/openspace/mimic/examined_by(mob/user, distance, infix, suffix) + SHOULD_CALL_PARENT(FALSE) + var/decl/pronouns/P = get_pronouns_by_gender(gender) + return associated_atom.examined_by(user, null, infix, suffix || "[P.He] [P.is] [z - original_z] level\s below you.") + // Trying to grab a mimic tries to grab the copied atom instead. /atom/movable/openspace/mimic/try_make_grab(mob/living/user, defer_hand) return associated_atom.try_make_grab(user, defer_hand) @@ -153,36 +179,64 @@ /atom/movable/openspace/mimic/forceMove(turf/dest) var/atom/old_loc = loc . = ..() + if (QDELING(src)) // Everything in this block is nonsense if we're being destroyed. + return + // The mimic might be reclaimed from the destruction timer, so do this regardless of if this mimic is likely to continue existing. + // It might be more efficient to do this on reclaim instead. + if (old_loc?.z != loc?.z) + reset_internal_layering() + + var/new_hide_state = FALSE if (MOVABLE_IS_ON_ZTURF(src)) if (destruction_timer) deltimer(destruction_timer) destruction_timer = null - if (old_loc?.z != loc?.z) // Null checking in case of qdel(), observed with dirt effect falling through multiz. - reset_internal_layering() else if (!destruction_timer) - destruction_timer = ZM_DESTRUCTION_TIMER(src) + destruction_timer = ZM_DESTRUCTION_TIMER(src, "forceMove") + new_hide_state = TRUE + + var/target_state = ZM_DIFF_HIDE_STATE(new_hide_state, ZM_HIDE_NONMIMIC, src) + if (hidden != target_state) + name = target_state ? "" : cached_name + hidden = target_state // Called when the turf we're on is deleted/changed. /atom/movable/openspace/mimic/proc/owning_turf_changed() if (!destruction_timer) - destruction_timer = ZM_DESTRUCTION_TIMER(src) + destruction_timer = ZM_DESTRUCTION_TIMER(src, "OTC") -/atom/movable/openspace/mimic/proc/reset_internal_layering() +/atom/movable/openspace/mimic/proc/reset_internal_layering(depth_hint, no_discover = FALSE) + reset_generation += 1 + var/root_z if (bound_overlay?.override_depth) depth = bound_overlay.override_depth - else if (isturf(associated_atom.loc)) - depth = min(SSzcopy.zlev_maximums[associated_atom.z] - associated_atom.z, OPENTURF_MAX_DEPTH) - override_depth = depth - - plane = OPENTURF_MAX_PLANE - depth - bound_overlay?.reset_internal_layering() + else if (isturf(associated_atom.loc)) + // Find the new root. + root_z = depth_hint // If we were reset by the mimic below us, they will have already calculated the root and we can just use that. + if (!root_z) + var/atom/movable/openspace/mimic/M + for (M = src; istype(M); M = M.associated_atom) + // body intentionally left empty + + root_z = M.z + +#ifdef ZM_ENH_DEBUG + var/old_depth = depth +#endif + depth = min(SSzcopy.zlev_maximums[associated_atom.z] - root_z, OPENTURF_MAX_DEPTH) + original_z = root_z + ZM_DEBUG_LOG("Resetting mimic ([src], copying [associated_atom.type], ([x], [y], [z])) layering: [old_depth] -> [depth]") + + bound_overlay?.reset_internal_layering(root_z, TRUE) + if (!no_discover) + SSzcopy.discover_movable(associated_atom, only_reset = TRUE) // -- TURF PROXY -- // This thing holds the mimic appearance for non-OVERWRITE turfs. /atom/movable/openspace/turf_proxy - plane = OPENTURF_MAX_PLANE + plane = ZMIMIC_MAXIMUM_PLANE mouse_opacity = MOUSE_OPACITY_UNCLICKABLE z_flags = ZMM_IGNORE // Only one of these should ever be visible at a time, the mimic logic will handle that. @@ -205,7 +259,7 @@ // A type for copying non-overwrite turfs' self-appearance. /atom/movable/openspace/turf_mimic - plane = OPENTURF_MAX_PLANE // These *should* only ever be at the top? + plane = ZMIMIC_MAXIMUM_PLANE // These *should* only ever be at the top? mouse_opacity = MOUSE_OPACITY_UNCLICKABLE var/turf/delegate diff --git a/code/modules/multiz/zmimic/mimic_turf.dm b/code/modules/multiz/zmimic/mimic_turf.dm index d47230e98e1c..e4b9d54056fb 100644 --- a/code/modules/multiz/zmimic/mimic_turf.dm +++ b/code/modules/multiz/zmimic/mimic_turf.dm @@ -1,6 +1,7 @@ /turf - // Reference to any open turf that might be above us to speed up atom Entered() updates. + /// The z-turf above us, if present. var/tmp/turf/above + /// If we're a z-turf, the turf below us. var/tmp/turf/below /// If we're a non-overwrite z-turf, this holds the appearance of the bottom-most Z-turf in the z-stack. var/tmp/atom/movable/openspace/turf_proxy/mimic_proxy @@ -14,16 +15,18 @@ var/tmp/z_queued = 0 /// If this Z-turf leads to space, uninterrupted. var/tmp/z_eventually_space = FALSE - var/z_flags = 0 /// Use this appearance for our appearance instead of `appearance`. If ZM_OVERRIDE is set, *only* this will be visible, no movables will be copied. var/z_appearance + var/z_flags = 0 // debug var/tmp/z_depth var/tmp/z_generation = 0 + var/tmp/z_generation_lighting = 0 + var/tmp/turf/z_discovered_root /turf/update_above() - if (TURF_IS_MIMICKING(above)) + if (TURF_IS_MIMIC(above)) above.update_mimic() /turf/proc/update_mimic() @@ -52,22 +55,56 @@ /// Sets up Z-mimic for this turf. You shouldn't call this directly 99% of the time. /turf/proc/setup_zmimic(mapload) + var/boundary_promotion = z_flags & ZM_BOUNDARY if (shadower) CRASH("Attempt to enable Z-mimic on already-enabled turf!") shadower = new(src) SSzcopy.openspace_turfs += 1 - var/turf/under = GetBelow(src) + if (boundary_promotion) + ZM_DEBUG_LOG("Promoting boundary [DEBUG_REF(src)] at ([x],[y],[z])") + SSzcopy.total_boundary_promotions++ + + var/turf/under = GET_BELOW(src) if (under) below = under below.above = src if (!(z_flags & (ZM_MIMIC_OVERWRITE|ZM_NO_OCCLUDE)) && mouse_opacity) - mouse_opacity = MOUSE_OPACITY_PRIORITY + mouse_opacity = 2 + + // TODO: There should be a more efficient way to do this, but it doesn't seem to have a meaningful impact on init time as-is. + for (var/turf/T as anything in RANGE_TURFS(src, 1)) + if (TURF_IS_MIMICKING(T)) // this also checks MIMIC_BOUNDARY + continue + T.z_flags |= ZM_BOUNDARY + T.setup_zmimic_boundary(mapload) + + // Reclaim mimics -- this is necessary to fix render for MIMIC_BLUR state changes. + if (!mapload) + for (var/atom/movable/object in below) + if (MOVABLE_SHALL_MIMIC(object)) + SSzcopy.discover_movable(object) + + update_mimic() + +/turf/proc/setup_zmimic_boundary(mapload) + ASSERT(!(z_flags & ZM_MIMIC_BELOW)) + + SSzcopy.openspace_boundaries += 1 + + var/turf/under = GET_BELOW(src) + if (under) + below = under + below.above = src - update_mimic(!mapload) // Only recursively update if the map isn't loading. + if (!mapload) + for (var/atom/movable/object in below) + if (MOVABLE_SHALL_MIMIC(object)) + SSzcopy.discover_movable(object) /// Cleans up Z-mimic objects for this turf. You shouldn't call this directly 99% of the time. /turf/proc/cleanup_zmimic() + var/demotion = z_flags & ZM_BOUNDARY SSzcopy.openspace_turfs -= 1 // Don't remove ourselves from the queue, the subsystem will explode. We'll naturally fall out of the queue. z_queued = 0 @@ -79,12 +116,35 @@ QDEL_NULL(mimic_above_copy) QDEL_NULL(mimic_underlay) - for (var/atom/movable/openspace/mimic/OO in src) - OO.owning_turf_changed() + if (demotion) + SSzcopy.total_boundary_demotions++ + ZM_DEBUG_LOG("Demoting boundary [DEBUG_REF(src)] at ([x],[y],[z])") + else + for (var/atom/movable/openspace/mimic/OO in src) + OO.owning_turf_changed() + + // If we were above a space turf, regenerate space tracking info. + if (z_eventually_space) + for (var/turf/T = above; above; above = above.above) + if (!isspaceturf(T)) + T.z_eventually_space = FALSE if (above) above.update_mimic() + if (!demotion && below) + below.above = null + below = null + + for (var/turf/T as anything in RANGE_TURFS(src, 1)) + if (TURF_IS_MIMIC_BOUNDARY(T)) + SSzcopy.pending_boundaries += T + +/turf/proc/cleanup_zmimic_boundary() + SSzcopy.openspace_boundaries -= 1 + for (var/atom/movable/openspace/mimic/OO in src) + OO.owning_turf_changed() + if (below) below.above = null below = null diff --git a/nebula.dme b/nebula.dme index 68161da9a707..dc50696f6dd3 100644 --- a/nebula.dme +++ b/nebula.dme @@ -3303,6 +3303,7 @@ #include "code\modules\multiz\turf.dm" #include "code\modules\multiz\turf_mimic_edge.dm" #include "code\modules\multiz\zmimic\mimic_common.dm" +#include "code\modules\multiz\zmimic\mimic_masters.dm" #include "code\modules\multiz\zmimic\mimic_movable.dm" #include "code\modules\multiz\zmimic\mimic_turf.dm" #include "code\modules\nano\nanoexternal.dm" From f1f35bf49601ca8c5ae9c1490882d5c91ff8a886 Mon Sep 17 00:00:00 2001 From: Lohikar Date: Mon, 15 Jun 2026 16:15:20 -0500 Subject: [PATCH 02/12] zm: Remove AUTOMANGLE remnant --- code/controllers/subsystems/zcopy.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/controllers/subsystems/zcopy.dm b/code/controllers/subsystems/zcopy.dm index 67eab08e4878..66e0666b31f4 100644 --- a/code/controllers/subsystems/zcopy.dm +++ b/code/controllers/subsystems/zcopy.dm @@ -542,7 +542,7 @@ SUBSYSTEM_DEF(zcopy) OO.queued = 0 // If an atom has explicit plane sets on its overlays/underlays, we need to mangle the appearance's overlays/underlays to align with Z-Mimic's plane usage. - if (OO.z_flags & (ZMM_MANGLE_PLANES | ZMM_AUTOMANGLE)) + if (OO.z_flags & ZMM_MANGLE_PLANES) var/new_appearance = fixup_appearance_planes(OO.appearance) if (new_appearance) OO.appearance = new_appearance From afe75783cb24fbea88605decf2b774f7eeb2c243 Mon Sep 17 00:00:00 2001 From: Lohikar Date: Mon, 15 Jun 2026 16:21:06 -0500 Subject: [PATCH 03/12] zm: mouse_opacity fixes for CI --- code/game/atoms_movable.dm | 2 +- code/modules/multiz/zmimic/mimic_turf.dm | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm index 71f795cbf8bf..73a26e876e9d 100644 --- a/code/game/atoms_movable.dm +++ b/code/game/atoms_movable.dm @@ -235,7 +235,7 @@ bound_overlay.reset_internal_layering() #endif var/target_state = bound_overlay.hidden - if (Tnew.mouse_opacity == 2 && (Tnew.z_flags & ZM_HIDE_ATOMS)) + if (Tnew.mouse_opacity == MOUSE_OPACITY_PRIORITY && (Tnew.z_flags & ZM_HIDE_ATOMS)) target_state |= ZM_HIDE_OPAQUE else target_state &= ~ZM_HIDE_OPAQUE diff --git a/code/modules/multiz/zmimic/mimic_turf.dm b/code/modules/multiz/zmimic/mimic_turf.dm index e4b9d54056fb..a5d64fe940a9 100644 --- a/code/modules/multiz/zmimic/mimic_turf.dm +++ b/code/modules/multiz/zmimic/mimic_turf.dm @@ -70,7 +70,7 @@ below.above = src if (!(z_flags & (ZM_MIMIC_OVERWRITE|ZM_NO_OCCLUDE)) && mouse_opacity) - mouse_opacity = 2 + mouse_opacity = MOUSE_OPACITY_PRIORITY // TODO: There should be a more efficient way to do this, but it doesn't seem to have a meaningful impact on init time as-is. for (var/turf/T as anything in RANGE_TURFS(src, 1)) From 302b72e9539e0b27403a3194705d63fdbbc8b1ae Mon Sep 17 00:00:00 2001 From: Lohikar Date: Mon, 15 Jun 2026 16:31:31 -0500 Subject: [PATCH 04/12] dtw: Blacklist ZM internal objects --- code/unit_tests/del_the_world.dm | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/code/unit_tests/del_the_world.dm b/code/unit_tests/del_the_world.dm index 58a54cc64f0c..51f822f4340e 100644 --- a/code/unit_tests/del_the_world.dm +++ b/code/unit_tests/del_the_world.dm @@ -13,7 +13,9 @@ // throw assert failures around non-null alarm area on spawn /obj/machinery/alarm, // Needs a level above. - /obj/structure/stairs + /obj/structure/stairs, + // These being created outside of ZM is undefined behavior. + /atom/movable/openspace ) // Suspend to avoid fluid flows shoving stuff off the testing turf. From 63f22d9c99ab3957b413a48c9737aa91b2994f87 Mon Sep 17 00:00:00 2001 From: Lohikar Date: Mon, 15 Jun 2026 16:36:36 -0500 Subject: [PATCH 05/12] zm: Mark globals --- code/__defines/zmimic.dm | 2 +- code/controllers/subsystems/zcopy.dm | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/code/__defines/zmimic.dm b/code/__defines/zmimic.dm index fd5a46692297..1203cdd5a6d8 100644 --- a/code/__defines/zmimic.dm +++ b/code/__defines/zmimic.dm @@ -84,7 +84,7 @@ #define ZM_HIDE_OPAQUE 4 //! This mimic is being hidden by its parent having mouse_opacity set with MIMIC_HIDE_ATOMS active. // For debug purposes, should contain the above defines in ascending order. -var/list/mimic_defines = list( +var/global/list/mimic_defines = list( "ZM_MIMIC_BELOW", "ZM_MIMIC_OVERWRITE", "ZM_ALLOW_LIGHTING", diff --git a/code/controllers/subsystems/zcopy.dm b/code/controllers/subsystems/zcopy.dm index 66e0666b31f4..eb1b28fae4e2 100644 --- a/code/controllers/subsystems/zcopy.dm +++ b/code/controllers/subsystems/zcopy.dm @@ -46,7 +46,7 @@ /// Check if we need to yield to the MC. This macro will sleep or break. #define ZM_MC_TRY_YIELD if ((++__yield) >= ZM_PUMP_RATIO) { __yield = 0; if (no_mc_tick) { CHECK_TICK; } else if (MC_TICK_CHECK) { break; } } -var/list/zm_offset_to_target = list(ZM_SLICE_TY_BASIC, ZM_SLICE_TY_LIGHTING, ZM_SLICE_TY_CAP) +var/global/list/zm_offset_to_target = list(ZM_SLICE_TY_BASIC, ZM_SLICE_TY_LIGHTING, ZM_SLICE_TY_CAP) //#define ZM_RECORD_STATS // This doesn't work on O7/Neb right now. @@ -957,13 +957,13 @@ SUBSYSTEM_DEF(zcopy) var/list/offsets = list() var/local_acc = 0 for (var/offset in (OPENTURF_PLANES_PER_DEPTH - 1) to 0 step -1) - var/ident = zm_offset_to_target[offset + 1] + var/ident = global.zm_offset_to_target[offset + 1] var/plane = ZM_COMPUTE_PLANE(d, offset) var/plane_str = "[plane]" offsets += plane - local_temp += "Depth [d] ([ident]), plane [plane], computed target [ZM_SLICE(zm_offset_to_target[offset + 1], d)]" + local_temp += "Depth [d] ([ident]), plane [plane], computed target [ZM_SLICE(global.zm_offset_to_target[offset + 1], d)]" SSzcopy.debug_fmt_planelist(atoms_list_list[plane_str], local_temp, T) local_acc += length(atoms_list_list[plane_str]) atoms_list_list -= plane_str // remove the found plane so we can find orphans From d4c8fdbaf8e8e33a35fafad805d26e135d6959ec Mon Sep 17 00:00:00 2001 From: Lohikar Date: Mon, 15 Jun 2026 16:36:52 -0500 Subject: [PATCH 06/12] zm: use Initialize for mimic_master --- code/modules/multiz/zmimic/mimic_masters.dm | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/code/modules/multiz/zmimic/mimic_masters.dm b/code/modules/multiz/zmimic/mimic_masters.dm index 307f885792f5..d2c121a636b5 100644 --- a/code/modules/multiz/zmimic/mimic_masters.dm +++ b/code/modules/multiz/zmimic/mimic_masters.dm @@ -10,8 +10,8 @@ var/slice_prefix = "slice" var/assign_target = FALSE -/obj/mimic_master/slice/New(loc, depth) - ..() +/obj/mimic_master/slice/Initialize(mapload, depth) + ..(mapload) plane = root_plane - ZM_DEPTH_TO_OFFSET_RAW(depth, stack_size) + slot ASSERT(slice_kind != null) ASSERT(slot != null) @@ -39,7 +39,7 @@ slot = ZM_SLICE_SLOT_LIGHTING slice_kind = ZM_SLICE_TY_LIGHTING -/obj/mimic_master/slice/shadower_master/New(loc, depth) +/obj/mimic_master/slice/shadower_master/Initialize(mapload, depth) ..() filters += filter(type = "alpha", render_source = ZM_SLICE_VIRTUAL(ZM_SLICE_TY_ZSUM, depth)) @@ -54,7 +54,7 @@ slot = ZM_VSLICE_SLOT_ZSUM slice_kind = ZM_SLICE_TY_ZSUM -/obj/mimic_master/slice/virtual/zsum/New(loc, depth) +/obj/mimic_master/slice/virtual/zsum/Initialize(mapload, depth) ..() if (depth != OPENTURF_MAX_DEPTH) render_source = ZM_SLICE_VIRTUAL(ZM_SLICE_TY_ZSUM, depth + 1) From 9f3c763a5d51d347ac86dde76dfdbdce16fd7ab7 Mon Sep 17 00:00:00 2001 From: Lohikar Date: Mon, 15 Jun 2026 16:51:54 -0500 Subject: [PATCH 07/12] dtw: Fix reading comprehension --- code/unit_tests/del_the_world.dm | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/code/unit_tests/del_the_world.dm b/code/unit_tests/del_the_world.dm index 51f822f4340e..58a54cc64f0c 100644 --- a/code/unit_tests/del_the_world.dm +++ b/code/unit_tests/del_the_world.dm @@ -13,9 +13,7 @@ // throw assert failures around non-null alarm area on spawn /obj/machinery/alarm, // Needs a level above. - /obj/structure/stairs, - // These being created outside of ZM is undefined behavior. - /atom/movable/openspace + /obj/structure/stairs ) // Suspend to avoid fluid flows shoving stuff off the testing turf. From 0f3d503dc15c227907f957f0639231474e8f2264 Mon Sep 17 00:00:00 2001 From: Lohikar Date: Sun, 5 Jul 2026 18:28:16 -0500 Subject: [PATCH 08/12] zm: Rewrite movement hook, fix some issues with BOUNDARY --- code/__defines/zmimic.dm | 7 +- code/controllers/subsystems/zcopy.dm | 219 ++++++++++++++------ code/game/atoms_movable.dm | 87 ++------ code/modules/admin/view_variables/topic.dm | 2 + code/modules/multiz/zmimic/mimic_movable.dm | 73 +++---- 5 files changed, 205 insertions(+), 183 deletions(-) diff --git a/code/__defines/zmimic.dm b/code/__defines/zmimic.dm index 1203cdd5a6d8..f0f318e63e2c 100644 --- a/code/__defines/zmimic.dm +++ b/code/__defines/zmimic.dm @@ -73,10 +73,11 @@ /// Preset: you're creating a turf with translucent accents (like glass flooring). This will block clicks even where alpha on the icon is 0, add MIMIC_NO_OCCLUDE if this is unwanted. #define ZM_MIMIC_PRESET_TRANSLUCENT_TURF (ZM_MIMIC_DEFAULTS) -/// Flags that require layering resets when a movable moves between turfs with different values for. -#define ZM_STATEFUL_MIMIC_FLAGS (0) +#define ZM_FLAGS_AFFECTS_VIS (ZM_BOUNDARY | ZM_HIDE_ATOMS) //! Flags that affect mimic right-click visibility (maps to ZM_HIDE_*). +#define ZM_FLAGS_AFFECTS_LAYERING (0) //! Flags that affect mimic layering (different render slice placement). + /// Flags that persist across changeturf. -#define ZM_INFECTIOUS_MIMIC_FLAGS (ZM_BOUNDARY|ZM_OVERRIDE) // Hopefully I don't regret making OVERRIDE sticky. +#define ZM_INFECTIOUS_MIMIC_FLAGS (ZM_OVERRIDE) // Hopefully I don't regret making OVERRIDE sticky. // ZM hide state flags for simplicity of logic. #define ZM_HIDE_BOUNDARY 1 //! This mimic is being hidden by its parent being a boundary. diff --git a/code/controllers/subsystems/zcopy.dm b/code/controllers/subsystems/zcopy.dm index eb1b28fae4e2..1ba9247e4cd8 100644 --- a/code/controllers/subsystems/zcopy.dm +++ b/code/controllers/subsystems/zcopy.dm @@ -16,6 +16,7 @@ #define ZM_DEPTH_TO_OFFSET(X) ZM_DEPTH_TO_OFFSET_RAW(X, OPENTURF_PLANES_PER_DEPTH) /// Compute the final target plane given a stack depth number and a slot offset. #define ZM_COMPUTE_PLANE(DEPTH, SLOT) (ZMIMIC_MAXIMUM_PLANE - ZM_DEPTH_TO_OFFSET(DEPTH) + (SLOT)) +#define ZM_COMPUTE_DEPTH(Z) max((SSzcopy.zlev_maximums[Z] - (Z)), OPENTURF_MAX_DEPTH) #define SHADOWER_DARKENING_FACTOR 0.6 // The multiplication factor for openturf shadower darkness. Lighting will be multiplied by this. #define SHADOWER_DARKENING_COLOR "#999999" // The above, but as an RGB string for lighting-less turfs. @@ -229,6 +230,19 @@ SUBSYSTEM_DEF(zcopy) /datum/controller/subsystem/zcopy/Initialize(timeofday) calculate_zstack_limits() + var/boundaries = 0 + var/t = REALTIMEOFDAY + for (var/turf/T in world) + if (TURF_IS_MIMIC(T)) + for (var/turf/boundary in RANGE_TURFS(T, 1)) + if (!TURF_IS_MIMICKING(boundary)) + boundary.z_flags |= ZM_BOUNDARY + boundary.setup_zmimic_boundary(TRUE) + boundaries++ + CHECK_TICK + + log_ss(name, "discovered [boundaries] boundaries in [(t - timeofday)/10] seconds!") + // Flush the queue. fire(FALSE, TRUE) @@ -564,15 +578,13 @@ SUBSYSTEM_DEF(zcopy) // only_reset: do not queue for update, only update layering info // return: is-invalid -/datum/controller/subsystem/zcopy/proc/discover_movable(atom/movable/object, only_reset = FALSE) +/datum/controller/subsystem/zcopy/proc/discover_movable(atom/movable/object) ASSERT(!QDELETED(object)) - if(init_state < SS_INITSTATE_STARTED) - return FALSE // no-op, discover_movable is only valid during or after zcopy init if (!isturf(object.loc)) return TRUE var/turf/Tloc = object.loc - var/turf/T = Tloc.above + var/turf/T = Tloc.above || GetAbove(Tloc) // it is valid for Tloc itself to not be a mimic due to LOOKAHEAD/LOOKBESIDE // ??? ASSERT(T != null) @@ -589,14 +601,46 @@ SUBSYSTEM_DEF(zcopy) if (MOVABLE_IS_BELOW_ZTURF(M)) defer = M + var/atom/movable/openspace/mimic/OO = object.bound_overlay + + // If the OO was queued for destruction but was claimed by another OT, stop the destruction timer. + if (OO.destruction_timer) + deltimer(OO.destruction_timer) + OO.destruction_timer = null + + update_mimic_layering(OO) + + // Multi-queue to maintain ordering of updates to these + // queueing it multiple times will result in only the most recent + // actually processing. + OO.queued += 1 + queued_overlays += OO + + total_updates_discovery += 1 + + ZM_RECORD_STOP + ZM_RECORD_WRITE(discovery_stats, "Depth [OO.depth] on [OO.z]") + + if (defer) + deferred_discoveries += 1 + .(defer) + + return FALSE + +/// Regenerate a mimic's layering and render slice membership information. This does not recursively update. It's valid to call this on a mimic that is located on a non-mimic turf, but it must be on a turf. +/datum/controller/subsystem/zcopy/proc/update_mimic_layering(atom/movable/openspace/mimic/target_mimic) var/override_depth - var/original_type = object.type - var/original_z = object.z + var/original_type = target_mimic.associated_atom.type + var/original_z = target_mimic.associated_atom.z - switch (object.type) + var/turf/T = target_mimic.loc + if (!isturf(T)) + CRASH("Attempt to generate mimic layering for orphaned mimic.") + + switch (target_mimic.associated_atom.type) // Depth for recursive mimic needs to be inherited. if (/atom/movable/openspace/mimic) - var/atom/movable/openspace/mimic/OOO = object + var/atom/movable/openspace/mimic/OOO = target_mimic.associated_atom original_type = OOO.mimiced_type override_depth = OOO.override_depth original_z = OOO.original_z @@ -607,49 +651,66 @@ SUBSYSTEM_DEF(zcopy) // Yes, this is an awful hack; I don't want to add yet another override_* var. override_depth = (ZMIMIC_MAXIMUM_PLANE - SPACE_PLANE)/OPENTURF_PLANES_PER_DEPTH // TODO: Please not this -- while it's valid, this is awful. - var/atom/movable/openspace/mimic/OO = object.bound_overlay - - // If the OO was queued for destruction but was claimed by another OT, stop the destruction timer. - if (OO.destruction_timer) - deltimer(OO.destruction_timer) - OO.destruction_timer = null - - OO.depth = override_depth || min(zlev_maximums[T.z] - original_z, OPENTURF_MAX_DEPTH) - OO.target_slot = 0 + target_mimic.depth = override_depth || ZM_COMPUTE_DEPTH(original_z) + target_mimic.target_slot = 0 switch (original_type) // These types need to be pushed a layer down for bigturfs to function correctly. if (/atom/movable/openspace/turf_proxy, /atom/movable/openspace/turf_mimic) - OO.depth += 1 + target_mimic.depth += 1 if (/atom/movable/openspace/multiplier) // Ignore override depth for these. - OO.depth = min(zlev_maximums[OO.z] - original_z + 1, OPENTURF_MAX_DEPTH) - OO.target_slot = ZM_SLICE_SLOT_LIGHTING + target_mimic.depth = min(zlev_maximums[target_mimic.z] - original_z + 1, OPENTURF_MAX_DEPTH) + target_mimic.target_slot = ZM_SLICE_SLOT_LIGHTING - OO.mimiced_type = original_type - OO.override_depth = override_depth - OO.original_z = original_z + target_mimic.mimiced_type = original_type + target_mimic.override_depth = override_depth + target_mimic.original_z = original_z - if (only_reset) - // We're only trying to rebuild layering information, no need to update the appearance. - OO.plane = ZM_COMPUTE_PLANE(OO.depth, OO.target_slot) + // We're only trying to rebuild layering information, no need to update the appearance. + target_mimic.plane = ZM_COMPUTE_PLANE(target_mimic.depth, target_mimic.target_slot) + +/// Update if a mimic should be hidden from right-click, usually by it being underneath a non-mimic turf. +/datum/controller/subsystem/zcopy/proc/update_mimic_occlusion(atom/movable/openspace/mimic/target_mimic) +#ifdef ZM_ENH_DEBUG + var/old_state = target_mimic.hidden +#endif + + var/turf/T = target_mimic.loc + if (!isturf(T)) + ZM_DEBUG_LOG("Mimic of [target_mimic.associated_atom] ([target_mimic.associated_atom.type]) is being hidden because of a non-turf loc") + target_mimic.hidden = ZM_HIDE_NONMIMIC + else if (!MOVABLE_IS_ON_ZTURF(target_mimic)) + target_mimic.hidden = ZM_HIDE_NONMIMIC else - // Multi-queue to maintain ordering of updates to these - // queueing it multiple times will result in only the most recent - // actually processing. - OO.queued += 1 - queued_overlays += OO + target_mimic.hidden = 0 - total_updates_discovery += 1 + // Check if we need to be hiding this movable's mimic from right click. + // If we're only resetting + if (T.mouse_opacity == 2 && (T.z_flags & ZM_HIDE_ATOMS)) + target_mimic.hidden |= ZM_HIDE_OPAQUE + else + target_mimic.hidden &= ~ZM_HIDE_OPAQUE - ZM_RECORD_STOP - ZM_RECORD_WRITE(discovery_stats, "Depth [OO.depth] on [OO.z]") + // If this movable is under a boundary, hide it from right-click since the client shouldn't be able to see it. + // ...but if the movable is a LOOKAHEAD/LOOKBESIDE atom, the client probably *can* see it, so avoid hiding those. + // Mimics inherit z flags from their associated mimic, so we can just check those. + if ((T.z_flags & ZM_BOUNDARY) && !(target_mimic.z_flags & (ZMM_LOOKAHEAD | ZMM_LOOKBESIDE))) + target_mimic.hidden |= ZM_HIDE_BOUNDARY + else + target_mimic.hidden &= ~ZM_HIDE_BOUNDARY - if (defer) - deferred_discoveries += 1 - .(defer, only_reset) +#ifdef ZM_ENH_DEBUG + if (old_state != target_mimic.hidden) + var/old_f = jointext(bitfield2list(old_state, mimic_hide_defines), " | ") + var/new_f = jointext(bitfield2list(target_mimic.hidden, mimic_hide_defines), " | ") + ZM_DEBUG_LOG("Occlusion: mimic transitioning from state ([old_f]) to ([new_f])") +#endif - return FALSE + if (target_mimic.hidden) + target_mimic.name = "" + else + target_mimic.name = target_mimic.cached_name /datum/controller/subsystem/zcopy/proc/flush_z_state(turf/T) if (T.below) // Z-Mimic turfs aren't necessarily above another turf. @@ -808,6 +869,9 @@ SUBSYSTEM_DEF(zcopy) #define FMT_DEPTH(X) (X == null ? "(null)" : X) #define FMT_OK(X) (X) ? "OK" : "MISMATCH" +/// if ROOT is FALSE, also check ALTERNATE and show indeterminate if true +#define FMT_MAYBE(ROOT, ALTERNATE) ((ROOT) ? "OK" : ((ALTERNATE) ? "INDETERMINATE (undefined for this entity)" : "MISMATCH")) +#define FMT_YESNO(X) ((X) ? "Yes" : "No") // This is a dummy object used so overlays can be shown in the analyzer. /atom/movable/openspace/debug @@ -841,14 +905,16 @@ SUBSYSTEM_DEF(zcopy) // Manually compute stack information to check connections var/turf/Tscan = T var/list/computed_stack = list() - if (TURF_IS_MIMIC(T)) // non-mimic turfs can enter this proc due to boundaries - while (Tscan && (Tscan.z_flags & ZM_MIMIC_BELOW) && !(Tscan.z_flags & ZM_OVERRIDE)) + if (TURF_IS_MIMICKING(T)) // non-mimic turfs can enter this proc due to boundaries + while (Tscan && TURF_IS_MIMICKING(Tscan) && !(Tscan.z_flags & ZM_OVERRIDE)) computed_stack += Tscan Tscan = GetBelow(Tscan) - // terminating/root turf - computed_stack += Tscan + // Terminating/root turf -- this may be null if the turf is a mimic but has no below (allowed behavior). + if (Tscan) + computed_stack += Tscan else + computed_stack += T Tscan = null var/root = "(null)" @@ -856,24 +922,34 @@ SUBSYSTEM_DEF(zcopy) root = "[T.z_discovered_root] (z [T.z_discovered_root.z], ty [T.z_discovered_root.type], \ref[T.z_discovered_root])" var/computed_root = "(null)" - if (Tscan) + if (computed_stack.len) + Tscan = computed_stack[computed_stack.len] computed_root = "[Tscan] (z [Tscan.z], ty [Tscan.type], \ref[Tscan])" + var/above_label = "(no above)" + if (T.above) + above_label = "[SSzcopy.fmt_label("Above", T.above, recurse = TRUE)] [T.above] ([T.above.type])" + + var/below_label = "(no below)" + if (T.below) + below_label = "[SSzcopy.fmt_label("Below", T.below, recurse = TRUE)] [T.below] ([T.below.type])" + var/is_above_space = T.is_above_space() var/list/out = list( "", - "

    Analysis of [T] at [T.x],[T.y],[T.z]

    ", + "

    Analysis of [T] at [T.x],[T.y],[T.z] (refresh)

    ", + "Connections: [above_label] / [below_label]", "Queue occurrences: [T.z_queued]", - "Above space: Apparent [T.z_eventually_space ? "Yes" : "No"], Actual [is_above_space ? "Yes" : "No"] - [FMT_OK(T.z_eventually_space == is_above_space)]", - "Root: [FMT_OK(T.z_discovered_root == Tscan)]", + // boundaries don't compute eventually_space, nor do non-z turfs + "Above space: Apparent: [FMT_YESNO(T.z_eventually_space)], Actual: [FMT_YESNO(is_above_space)] - [FMT_MAYBE(T.z_eventually_space == is_above_space, !TURF_IS_MIMIC(T))]", + "Root: [FMT_MAYBE(T.z_discovered_root == Tscan, !TURF_IS_MIMIC(T))]", // boundaries don't set this, nor do non-z turfs "- Apparent [root]", "- Actual [computed_root]", "Z Flags: [english_list(bitfield2list(T.z_flags, global.mimic_defines), "(none)")]", - "Has Shadower: [T.shadower ? "Yes" : "No"]", - "Has turf proxy: [T.mimic_proxy ? "Yes" : "No"]", - "Has above copy: [T.mimic_above_copy ? "Yes" : "No"]", - "Has mimic underlay: [T.mimic_underlay ? "Yes" : "No"]", - "Below: [!T.below ? "(nothing)" : "[T.below] at [T.below.x],[T.below.y],[T.below.z]"]", + "Has shadower: [FMT_YESNO(T.shadower)]", + "Has turf proxy: [FMT_YESNO(T.mimic_proxy)]", + "Has above copy: [FMT_YESNO(T.mimic_above_copy)]", + "Has mimic underlay: [FMT_YESNO(T.mimic_underlay)]", "Depth: [FMT_DEPTH(T.z_depth)] [T.z_depth == OPENTURF_MAX_DEPTH ? "(max)" : ""]", "Generation: [T.z_generation] general, [T.z_generation_lighting] lighting", "Update count: Claimed [claimed_update_count], Actual [real_update_count] - [FMT_OK(claimed_update_count == real_update_count)]" @@ -883,7 +959,11 @@ SUBSYSTEM_DEF(zcopy) var/list/shadow_stack = list(T.shadower) var/list/apparent_stack = list() - for (var/turf/Tbelow = T; TURF_IS_MIMIC(Tbelow); Tbelow = Tbelow.below) + apparent_stack += T + if (T.shadower) + shadow_stack += T.shadower + + for (var/turf/Tbelow = T.below; TURF_IS_MIMICKING(Tbelow); Tbelow = Tbelow.below) var/atom/movable/openspace/debug/turf/VTO = new VTO.computed_depth = SSzcopy.zlev_maximums[Tbelow.z] - Tbelow.z VTO.appearance = Tbelow @@ -896,27 +976,29 @@ SUBSYSTEM_DEF(zcopy) // manually add root, since above loop (intentionally) omits it if (apparent_stack.len) - apparent_stack += apparent_stack[apparent_stack.len]?:below + var/turf/apparent_root = apparent_stack[apparent_stack.len]?:below + if (apparent_root) // This can be null if a mimic turf is above nothing, which is valid. + apparent_stack += apparent_root if (computed_stack ~= apparent_stack) - out += "Z-stack: OK" + out += "Z-stack: OK" out += SSzcopy.debug_fmt_turf_list(computed_stack) else - out += "Z-stack: MISMATCH" + out += "Z-stack: MISMATCH" out += "Expected:" out += SSzcopy.debug_fmt_turf_list(computed_stack) out += "Actual:" out += SSzcopy.debug_fmt_turf_list(apparent_stack) - out += "
    " - - if (!TURF_IS_MIMIC(T)) + if (!TURF_IS_MIMICKING(T)) out += "

    Not a mimic.

    " else if (!T.below) out += "

    Using synthetic rendering (Not Z).

    " else if (T.z_flags & ZM_OVERRIDE) out += "

    Using synthetic rendering (OVERRIDE) — override is [T.z_appearance ? "MANUAL" : "BASETURF (resolved to [get_base_turf_by_area(T)])"].

    " + out += "
    " + for (var/atom/movable/openspace/O in T) found_oo += O @@ -947,7 +1029,7 @@ SUBSYSTEM_DEF(zcopy) atoms_list_list -= "[DEFAULT_PLANE]" if (atoms_list_list["[LIGHTING_PLANE]"]) - out += "Upper lighting plane" + out += "Lighting plane" SSzcopy.debug_fmt_planelist(atoms_list_list["[LIGHTING_PLANE]"], out, T) atoms_list_list -= "[LIGHTING_PLANE]" @@ -997,7 +1079,7 @@ SUBSYSTEM_DEF(zcopy) /datum/controller/subsystem/zcopy/proc/debug_fmt_thing(atom/A, list/out, turf/original) if (istype(A, /atom/movable/openspace/mimic)) var/atom/movable/openspace/mimic/OO = A - var/base = "
  • [fmt_label("Mimic", A)] plane [A.plane], layer [A.layer], depth [FMT_DEPTH(OO.depth)], override depth [FMT_DEPTH(OO.override_depth)], [OO.reset_generation] resets" + var/base = "
  • [fmt_label("Mimic", A)] plane [A.plane], layer [A.layer], depth [FMT_DEPTH(OO.depth)], override depth [FMT_DEPTH(OO.override_depth)]" if (QDELETED(OO.associated_atom)) // This shouldn't happen, but can if the deletion hook is not working. return "[base] - [OO.type] copying ([OO.mimiced_type]) - ORPHANED
  • " @@ -1020,14 +1102,14 @@ SUBSYSTEM_DEF(zcopy) else if (A.type == /atom/movable/openspace/multiplier) - return "
  • [fmt_label("Shadower", A)] plane [A.plane], layer [A.layer], Z-level [A.z] - [A] ([A.type]), generation dyn [A:lighting_generation], static [A:lighting_generation_static]
  • " + return "
  • [fmt_label("Shadower", A)] plane [A.plane], layer [A.layer], Z-level [A.z] - [A] ([A.type]), generation [A:lighting_generation]
  • " else if (A.type == /atom/movable/openspace/debug/shadower) // These are fake objects that exist just to show the shadower's overlays in this list. return "
  • [fmt_label("Shadower True Overlay", A, vv = FALSE)] depth [A:associated_depth], plane [A.plane], layer [A.layer] - VIRTUAL
  • " else if (A.type == /atom/movable/openspace/debug/turf) var/atom/movable/openspace/debug/turf/VTO = A - return "
  • [fmt_label("VTO", VTO.parent)] plane [VTO.plane], layer [VTO.layer], computed depth [FMT_DEPTH(VTO.computed_depth)] - [VTO.parent] ([VTO.parent.type]) - FOREIGN" + return "
  • [fmt_label("Foreign Turf", VTO.parent, recurse = TRUE)] plane [VTO.plane], layer [VTO.layer], computed depth [FMT_DEPTH(VTO.computed_depth)] - [VTO.parent] ([VTO.parent.type]) - FOREIGN" else if (A.type == /atom/movable/openspace/turf_proxy) return "
  • [fmt_label("Turf Proxy", A)] plane [A.plane], layer [A.layer], Z-level [A.z] - [A] ([A.type])
  • " @@ -1035,10 +1117,15 @@ SUBSYSTEM_DEF(zcopy) else return "
  • [fmt_label("?", A)] plane [A.plane], layer [A.layer], Z-level [A.z] - [A] ([A.type])
  • " -/datum/controller/subsystem/zcopy/proc/fmt_label(label, atom/target, vv = TRUE) +/datum/controller/subsystem/zcopy/proc/fmt_label(label, atom/target, vv = TRUE, recurse = FALSE) . = "\icon[target] \[[label]\] " if (vv) - . += "(VV) " + . += "(VV" + + if (recurse) + . += ", OA) " + else + . += ") " /datum/controller/subsystem/zcopy/proc/debug_fmt_planelist(list/things, list/out, turf/original) if (things) @@ -1054,7 +1141,7 @@ SUBSYSTEM_DEF(zcopy) for (var/item in turfs) var/turf/T = astype(item, /turf) if (!T) - working += "
  • (Non-turf: [item?:type || ""])
  • " + working += "
  • (Non-turf: [item?:type || "(null)"])
  • " continue var/list/flags = bitfield2list(T.z_flags, global.mimic_defines) @@ -1070,6 +1157,8 @@ SUBSYSTEM_DEF(zcopy) #undef FMT_DEPTH #undef FMT_OK +#undef FMT_MAYBE +#undef FMT_YESNO #undef ZM_RECORD_START #undef ZM_RECORD_STOP #undef ZM_RECORD_WRITE diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm index 73a26e876e9d..539cad7e1b34 100644 --- a/code/game/atoms_movable.dm +++ b/code/game/atoms_movable.dm @@ -211,43 +211,7 @@ // Z-Mimic. if (bound_overlay) - // Some types (like humantypes) change this per Move. - if (bound_overlay.glide_size != glide_size) - bound_overlay.glide_size = glide_size - // The overlay will handle cleaning itself up on non-openspace turfs. - bound_overlay.forceMove(get_step(src, UP)) - if (bound_overlay.dir != dir) - bound_overlay.set_dir(dir) - -#if ZM_STATEFUL_MIMIC_FLAGS != 0 - var/turf/Told = astype(origin, /turf)?.above -#endif - var/turf/Tnew = astype(loc, /turf)?.above - - if (Tnew) -#if ZM_STATEFUL_MIMIC_FLAGS != 0 - if (Told) - var/old_flags = Told.z_flags & ZM_STATEFUL_MIMIC_FLAGS - var/new_flags = Tnew.z_flags & ZM_STATEFUL_MIMIC_FLAGS - - if (old_flags != new_flags) - ZM_DEBUG_LOG("Told = [old_flags], Tnew = [new_flags]") - bound_overlay.reset_internal_layering() -#endif - var/target_state = bound_overlay.hidden - if (Tnew.mouse_opacity == MOUSE_OPACITY_PRIORITY && (Tnew.z_flags & ZM_HIDE_ATOMS)) - target_state |= ZM_HIDE_OPAQUE - else - target_state &= ~ZM_HIDE_OPAQUE - - if (Tnew.z_flags & ZM_BOUNDARY) - target_state |= ZM_HIDE_BOUNDARY - else - target_state &= ~ZM_HIDE_BOUNDARY - - if (bound_overlay.hidden != target_state) - bound_overlay.name = target_state ? "" : bound_overlay.cached_name - bound_overlay.hidden = target_state + move_mimic(origin) else if (isturf(loc) && (!origin || !TURF_IS_MIMICKING(origin)) && MOVABLE_SHALL_MIMIC(src) && MOVABLE_IS_BELOW_ZTURF(src)) SSzcopy.discover_movable(src) @@ -305,43 +269,7 @@ // Z-Mimic. if (bound_overlay) - // Some types (like humantypes) change this per Move. - if (bound_overlay.glide_size != glide_size) - bound_overlay.glide_size = glide_size - // The overlay will handle cleaning itself up on non-openspace turfs. - bound_overlay.forceMove(get_step(src, UP)) - if (bound_overlay.dir != dir) - bound_overlay.set_dir(dir) - -#if ZM_STATEFUL_MIMIC_FLAGS != 0 - var/turf/Told = astype(old_loc, /turf)?.above -#endif - var/turf/Tnew = astype(loc, /turf)?.above - - if (Tnew) -#if ZM_STATEFUL_MIMIC_FLAGS != 0 - if (Told) - var/old_flags = Told.z_flags & ZM_STATEFUL_MIMIC_FLAGS - var/new_flags = Tnew.z_flags & ZM_STATEFUL_MIMIC_FLAGS - - if (old_flags != new_flags) - ZM_DEBUG_LOG("Told = [old_flags], Tnew = [new_flags]") - bound_overlay.reset_internal_layering() -#endif - var/target_state = bound_overlay.hidden - if (Tnew.mouse_opacity == 2 && (Tnew.z_flags & ZM_HIDE_ATOMS)) - target_state |= ZM_HIDE_OPAQUE - else - target_state &= ~ZM_HIDE_OPAQUE - - if (Tnew.z_flags & ZM_BOUNDARY) - target_state |= ZM_HIDE_BOUNDARY - else - target_state &= ~ZM_HIDE_BOUNDARY - - if (bound_overlay.hidden != target_state) - bound_overlay.name = target_state ? "" : bound_overlay.cached_name - bound_overlay.hidden = target_state + move_mimic(old_loc) else if (isturf(loc) && (!old_loc || !TURF_IS_MIMICKING(old_loc)) && MOVABLE_SHALL_MIMIC(src) && MOVABLE_IS_BELOW_ZTURF(src)) SSzcopy.discover_movable(src) @@ -355,6 +283,17 @@ if(!storage.can_view(viewer)) storage.close(viewer) +/atom/movable/proc/move_mimic(atom/old_loc) + // Some types (like humantypes) change this per Move. + if (bound_overlay.glide_size != glide_size) + bound_overlay.glide_size = glide_size + + if (bound_overlay.dir != dir) + bound_overlay.set_dir(dir) + + // The overlay will handle cleaning itself up on non-openspace turfs. Moving to `get_step(UP)` is invalid here, since that might place us in an unrelated Z-group. + bound_overlay.forceMove(astype(loc, /turf)?.above) + //called when src is thrown into hit_atom /atom/movable/proc/throw_impact(atom/hit_atom, var/datum/thrownthing/TT) SHOULD_CALL_PARENT(TRUE) diff --git a/code/modules/admin/view_variables/topic.dm b/code/modules/admin/view_variables/topic.dm index 562c9f1f486b..e4f79a7bfc8b 100644 --- a/code/modules/admin/view_variables/topic.dm +++ b/code/modules/admin/view_variables/topic.dm @@ -4,6 +4,8 @@ return if(href_list["Vars"]) debug_variables(locate(href_list["Vars"])) + if(href_list["zm_analyze"]) + analyze_openturf(locate(href_list["zm_analyze"])) //~CARN: for renaming mobs (updates their name, real_name, mind.name, their ID/PDA and datacore records). else if(href_list["rename"]) diff --git a/code/modules/multiz/zmimic/mimic_movable.dm b/code/modules/multiz/zmimic/mimic_movable.dm index 86c1659324ed..e33e21821747 100644 --- a/code/modules/multiz/zmimic/mimic_movable.dm +++ b/code/modules/multiz/zmimic/mimic_movable.dm @@ -127,7 +127,6 @@ var/mimiced_type var/original_z var/override_depth - var/reset_generation = 0 var/hidden = FALSE var/cached_name var/have_performed_fixup = FALSE @@ -177,61 +176,53 @@ return associated_atom.try_make_grab(user, defer_hand) /atom/movable/openspace/mimic/forceMove(turf/dest) - var/atom/old_loc = loc - . = ..() if (QDELING(src)) // Everything in this block is nonsense if we're being destroyed. + return ..() + + if (!dest) + ZM_DEBUG_LOG("Mimic moved to nullspace; deleting self.") + qdel(src) return - // The mimic might be reclaimed from the destruction timer, so do this regardless of if this mimic is likely to continue existing. - // It might be more efficient to do this on reclaim instead. - if (old_loc?.z != loc?.z) - reset_internal_layering() - var/new_hide_state = FALSE + var/turf/old_loc = loc + var/force_layering_update = FALSE + + if (isnull(override_depth) && isturf(old_loc) && isturf(dest) && old_loc.z != dest.z) + var/atom/movable/openspace/mimic/parent_mimic = astype(associated_atom) + // If we're the top-most mimic, we can directly compute depth. Otherwise, pull it off our parent mimic since it should already have either computed it itself, or stolen it from _its_ parent. + depth = parent_mimic?.depth + if (depth == null) // depth of 0 is valid + depth = ZM_COMPUTE_DEPTH(associated_atom.z) + ZM_DEBUG_LOG("Recomputing depth, no parent: result is [depth]") + force_layering_update = TRUE + + . = ..() + if (MOVABLE_IS_ON_ZTURF(src)) if (destruction_timer) deltimer(destruction_timer) destruction_timer = null + + if (isturf(old_loc)) + var/flag_difference = old_loc.z_flags ^ dest.z_flags + + if (force_layering_update || (flag_difference & ZM_FLAGS_AFFECTS_LAYERING)) + SSzcopy.update_mimic_layering(src) + + if (flag_difference & ZM_FLAGS_AFFECTS_VIS) + SSzcopy.update_mimic_occlusion(src) + else // If we're moving from null to a z-turf, just rebuild both. + SSzcopy.update_mimic_layering(src) + SSzcopy.update_mimic_occlusion(src) + else if (!destruction_timer) destruction_timer = ZM_DESTRUCTION_TIMER(src, "forceMove") - new_hide_state = TRUE - - var/target_state = ZM_DIFF_HIDE_STATE(new_hide_state, ZM_HIDE_NONMIMIC, src) - if (hidden != target_state) - name = target_state ? "" : cached_name - hidden = target_state // Called when the turf we're on is deleted/changed. /atom/movable/openspace/mimic/proc/owning_turf_changed() if (!destruction_timer) destruction_timer = ZM_DESTRUCTION_TIMER(src, "OTC") -/atom/movable/openspace/mimic/proc/reset_internal_layering(depth_hint, no_discover = FALSE) - reset_generation += 1 - var/root_z - if (bound_overlay?.override_depth) - depth = bound_overlay.override_depth - - else if (isturf(associated_atom.loc)) - // Find the new root. - root_z = depth_hint // If we were reset by the mimic below us, they will have already calculated the root and we can just use that. - if (!root_z) - var/atom/movable/openspace/mimic/M - for (M = src; istype(M); M = M.associated_atom) - // body intentionally left empty - - root_z = M.z - -#ifdef ZM_ENH_DEBUG - var/old_depth = depth -#endif - depth = min(SSzcopy.zlev_maximums[associated_atom.z] - root_z, OPENTURF_MAX_DEPTH) - original_z = root_z - ZM_DEBUG_LOG("Resetting mimic ([src], copying [associated_atom.type], ([x], [y], [z])) layering: [old_depth] -> [depth]") - - bound_overlay?.reset_internal_layering(root_z, TRUE) - if (!no_discover) - SSzcopy.discover_movable(associated_atom, only_reset = TRUE) - // -- TURF PROXY -- // This thing holds the mimic appearance for non-OVERWRITE turfs. From 654e93e1744fd8fe5cbc60c2d5354e04e253da9a Mon Sep 17 00:00:00 2001 From: Lohikar Date: Sun, 5 Jul 2026 18:32:31 -0500 Subject: [PATCH 09/12] professional on closed course, do not attempt (ZM needs to iterate all turfs in world for init) --- test/check-paths.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/check-paths.sh b/test/check-paths.sh index f59aa02ce4c7..56dbc5b9add7 100755 --- a/test/check-paths.sh +++ b/test/check-paths.sh @@ -32,7 +32,7 @@ exactly 2 "/mob text paths" '"/mob' exactly 6 "/obj text paths" '"/obj' exactly 10 "/turf text paths" '"/turf' exactly 1 "world<< uses" 'world\s*<<' -exactly 75 "'in world' uses" '\s+\bin world\b(?=\s*$|\s*//|\s*\))' -P +exactly 76 "'in world' uses" '\s+\bin world\b(?=\s*$|\s*//|\s*\))' -P exactly 1 "world.log<< uses" 'world.log\s*<<' exactly 23 "<< uses" '(? Date: Sun, 5 Jul 2026 18:42:35 -0500 Subject: [PATCH 10/12] zm: Fix atom init hook --- code/game/atoms_init.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/game/atoms_init.dm b/code/game/atoms_init.dm index 4c5d7124b885..4a145c41d55b 100644 --- a/code/game/atoms_init.dm +++ b/code/game/atoms_init.dm @@ -114,7 +114,7 @@ // Changing this behavior will almost certainly break power; update accordingly. if (!ml && loc) loc.Entered(src, null) - if(loc && (z_flags & ZMM_WIDE_LOAD)) + if(loc && MOVABLE_SHALL_MIMIC(src) && MOVABLE_IS_BELOW_ZTURF(src)) SSzcopy.discover_movable(src) /atom/movable/EarlyDestroy(force = FALSE) From 21a7afcf1a9e9ed62893d4588a28c22e6faca0b6 Mon Sep 17 00:00:00 2001 From: Lohikar Date: Tue, 7 Jul 2026 17:35:11 -0500 Subject: [PATCH 11/12] zm: Fix ZM_COMPUTE_DEPTH always returning 10 --- code/controllers/subsystems/zcopy.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/controllers/subsystems/zcopy.dm b/code/controllers/subsystems/zcopy.dm index 1ba9247e4cd8..fa36a64e7fbc 100644 --- a/code/controllers/subsystems/zcopy.dm +++ b/code/controllers/subsystems/zcopy.dm @@ -16,7 +16,7 @@ #define ZM_DEPTH_TO_OFFSET(X) ZM_DEPTH_TO_OFFSET_RAW(X, OPENTURF_PLANES_PER_DEPTH) /// Compute the final target plane given a stack depth number and a slot offset. #define ZM_COMPUTE_PLANE(DEPTH, SLOT) (ZMIMIC_MAXIMUM_PLANE - ZM_DEPTH_TO_OFFSET(DEPTH) + (SLOT)) -#define ZM_COMPUTE_DEPTH(Z) max((SSzcopy.zlev_maximums[Z] - (Z)), OPENTURF_MAX_DEPTH) +#define ZM_COMPUTE_DEPTH(Z) min((SSzcopy.zlev_maximums[Z] - (Z)), OPENTURF_MAX_DEPTH) #define SHADOWER_DARKENING_FACTOR 0.6 // The multiplication factor for openturf shadower darkness. Lighting will be multiplied by this. #define SHADOWER_DARKENING_COLOR "#999999" // The above, but as an RGB string for lighting-less turfs. From 9ceec1769d203fd632cbcf632d892f2bb1ff3444 Mon Sep 17 00:00:00 2001 From: Lohikar Date: Tue, 7 Jul 2026 17:35:44 -0500 Subject: [PATCH 12/12] zm: Remove some unused code --- code/controllers/subsystems/zcopy.dm | 10 +--------- code/modules/multiz/zmimic/mimic_masters.dm | 7 ------- 2 files changed, 1 insertion(+), 16 deletions(-) diff --git a/code/controllers/subsystems/zcopy.dm b/code/controllers/subsystems/zcopy.dm index fa36a64e7fbc..869d7843dd1c 100644 --- a/code/controllers/subsystems/zcopy.dm +++ b/code/controllers/subsystems/zcopy.dm @@ -34,12 +34,11 @@ #define ZM_SLICE_SLOT_LIGHTING 1 //! Shadowers and other *BLEND_MULTIPLY* objects. #define ZM_SLICE_SLOT_CAP 2 //! ZAO and other non-MULTIPLY effects that must render on top of everything else, including lighting. -#define ZM_BASEMENT_CAP_PLANE -399 #define ZM_BASEMENT_MAX_PLANE -400 #define ZM_BASEMENT_PLANES_PER_DEPTH 1 #define ZM_VSLICE_SLOT_ZSUM 0 -/// How many items should we process before we check for yield? Increasing this increases efficiency, but also raises risk of overrun. +/// How many items should we process before we check for yield? Increasing this increases efficiency, but also raises risk of overrun. This is tuned for world.fps = 100. #define ZM_PUMP_RATIO 4 /// Initialize state required for ZM_MC_TRY_YIELD. #define ZM_PUMP_INIT var/__yield @@ -853,11 +852,6 @@ SUBSYSTEM_DEF(zcopy) C.screen += slice LAZYADD(C.zm_objs[slice.slice_prefix], slice) -/datum/controller/subsystem/zcopy/proc/CreatePrimordialSlice(client/C, path) - var/obj/mimic_master/master = new path - C.screen += master - LAZYADD(C.zm_objs["primordial"], master) - /datum/controller/subsystem/zcopy/proc/CreatePlanesFor(client/C) for (var/i in 0 to OPENTURF_MAX_DEPTH) CreateSlice(C, /obj/mimic_master/slice/basic, i) @@ -865,8 +859,6 @@ SUBSYSTEM_DEF(zcopy) CreateSlice(C, /obj/mimic_master/slice/cap, i) CreateSlice(C, /obj/mimic_master/slice/virtual/zsum, i) - CreatePrimordialSlice(C, /obj/mimic_master/plane_zero) - #define FMT_DEPTH(X) (X == null ? "(null)" : X) #define FMT_OK(X) (X) ? "OK" : "MISMATCH" /// if ROOT is FALSE, also check ALTERNATE and show indeterminate if true diff --git a/code/modules/multiz/zmimic/mimic_masters.dm b/code/modules/multiz/zmimic/mimic_masters.dm index d2c121a636b5..74a2e192bf5f 100644 --- a/code/modules/multiz/zmimic/mimic_masters.dm +++ b/code/modules/multiz/zmimic/mimic_masters.dm @@ -62,10 +62,3 @@ filter(type = "layer", render_source = ZM_SLICE(ZM_SLICE_TY_BASIC, depth)) ) render_target = ZM_SLICE_VIRTUAL(ZM_SLICE_TY_ZSUM, depth) - -// -- Non-slice Z masters - - -/// Contains game world. -/obj/mimic_master/plane_zero - plane = DEFAULT_PLANE - render_target = "plane_zero"