From 70d356506fe69636fee4c3756cc9b18ae4d238bc Mon Sep 17 00:00:00 2001
From: Jeremiah <42397676+jlsnow301@users.noreply.github.com>
Date: Thu, 20 Mar 2025 05:51:47 -0700
Subject: [PATCH] Fixes a bitrunning runtime & adds a component initialize
result (#90099)
## About The Pull Request
Noticed that bitrunning virtual spawners were runtiming if the server
was already emagged. Basically, the guardrail component is added
(virtual_entity), then immediately deleted because it's emagged, leading
to a race condition in `JoinParent()` where parent is null. I still want
to keep this "valid, but delete me" state for components, so I made
`COMPONENT_REDUNDANT` (thx @LemonInTheDark).
I can't say for certain because I couldn't repro, but this /probably/
fixes #89992
## Why It's Good For The Game
Fixes a runtime
Allows devs to add components that execute an arbitrary amount of logic
while still qdeling themselves due to some in-game incompatibility issue
## Changelog
N/A
---
code/__DEFINES/dcs/declarations.dm | 2 +
code/datums/components/_component.dm | 12 +++++-
.../components/toggle_attached_clothing.dm | 2 +-
.../components/bitrunning_points.dm | 1 -
.../bitrunning/components/virtual_entity.dm | 39 +++++++++++--------
code/modules/bitrunning/server/_parent.dm | 10 +++++
6 files changed, 47 insertions(+), 19 deletions(-)
diff --git a/code/__DEFINES/dcs/declarations.dm b/code/__DEFINES/dcs/declarations.dm
index 04b31f4e185..95ad093f995 100644
--- a/code/__DEFINES/dcs/declarations.dm
+++ b/code/__DEFINES/dcs/declarations.dm
@@ -4,6 +4,8 @@
#define COMPONENT_INCOMPATIBLE 1
/// Returned in PostTransfer to prevent transfer, similar to `COMPONENT_INCOMPATIBLE`
#define COMPONENT_NOTRANSFER 2
+/// Deletes the component silently. This is for valid, non-error cases where you still want to execute some of the component's logic.
+#define COMPONENT_REDUNDANT 3
/// Return value to cancel attaching
#define ELEMENT_INCOMPATIBLE 1
diff --git a/code/datums/components/_component.dm b/code/datums/components/_component.dm
index 9fc1e13e1b4..3cc0738e38d 100644
--- a/code/datums/components/_component.dm
+++ b/code/datums/components/_component.dm
@@ -43,11 +43,21 @@
/datum/component/New(list/raw_args)
parent = raw_args[1]
var/list/arguments = raw_args.Copy(2)
- if(Initialize(arglist(arguments)) == COMPONENT_INCOMPATIBLE)
+
+ var/result = Initialize(arglist(arguments))
+
+ if(result == COMPONENT_INCOMPATIBLE)
stack_trace("Incompatible [type] assigned to a [parent.type]! args: [json_encode(arguments)]")
qdel(src, TRUE, TRUE)
return
+ if(result == COMPONENT_REDUNDANT)
+ qdel(src, TRUE, TRUE)
+ return
+
+ if(QDELETED(src) || QDELETED(parent))
+ CRASH("Component [type] was created with a deleted parent or was deleted itself before it could be added to a parent")
+
_JoinParent()
/**
diff --git a/code/datums/components/toggle_attached_clothing.dm b/code/datums/components/toggle_attached_clothing.dm
index 8321119d85e..a1cde8cba63 100644
--- a/code/datums/components/toggle_attached_clothing.dm
+++ b/code/datums/components/toggle_attached_clothing.dm
@@ -48,7 +48,7 @@
if (!isitem(parent))
return COMPONENT_INCOMPATIBLE
if (!deployable_type || !equipped_slot)
- return COMPONENT_INCOMPATIBLE // Not strictly true but INITIALIZE_HINT_QDEL doesn't work from components
+ return COMPONENT_REDUNDANT
src.deployable_type = deployable_type
src.equipped_slot = equipped_slot
src.destroy_on_removal = destroy_on_removal
diff --git a/code/modules/bitrunning/components/bitrunning_points.dm b/code/modules/bitrunning/components/bitrunning_points.dm
index ae20ec41fb4..b800d586e94 100644
--- a/code/modules/bitrunning/components/bitrunning_points.dm
+++ b/code/modules/bitrunning/components/bitrunning_points.dm
@@ -7,7 +7,6 @@
/datum/component/bitrunning_points/Initialize(datum/lazy_template/virtual_domain/domain)
- . = ..()
if(!isturf(parent))
return COMPONENT_INCOMPATIBLE
diff --git a/code/modules/bitrunning/components/virtual_entity.dm b/code/modules/bitrunning/components/virtual_entity.dm
index dba5e9db34e..52a259a6d04 100644
--- a/code/modules/bitrunning/components/virtual_entity.dm
+++ b/code/modules/bitrunning/components/virtual_entity.dm
@@ -1,19 +1,35 @@
/// Handles all special considerations for "virtual entities" such as bitrunning ghost roles or digital anomaly antagonists.
/datum/component/virtual_entity
- ///The cooldown for balloon alerts, so the player isn't spammed while trying to enter a restricted area.
+ /// The cooldown for balloon alerts, so the player isn't spammed while trying to enter a restricted area.
COOLDOWN_DECLARE(OOB_cooldown)
-/datum/component/virtual_entity/Initialize(obj/machinery/quantum_server)
- . = ..()
+/datum/component/virtual_entity/Initialize(obj/machinery/quantum_server)
if(quantum_server.obj_flags & EMAGGED)
- jailbreak_mobs() //This just sends a message and self-deletes, a bit messy but it works.
- return
+ jailbreak_mobs()
+ return COMPONENT_REDUNDANT
RegisterSignal(parent, COMSIG_MOVABLE_PRE_MOVE, PROC_REF(on_parent_pre_move))
- RegisterSignal(quantum_server, COMSIG_ATOM_EMAG_ACT, PROC_REF(jailbreak_mobs))
+ RegisterSignal(quantum_server, COMSIG_ATOM_EMAG_ACT, PROC_REF(on_emagged))
-///Prevents entry to a certain area if it has flags preventing virtual entities from entering.
+
+/// Self-destructs the component, allowing free-roam by all entities with this restriction.
+/datum/component/virtual_entity/proc/jailbreak_mobs()
+ to_chat(parent, span_bolddanger("You shiver for a moment with a sense of clarity you haven't felt before."))
+ to_chat(parent, span_notice("You could go anywhere, do anything! You could leave this simulation right now if you wanted!"))
+ to_chat(parent, span_danger("But be warned, quantum entanglement will interfere with any previous lives."))
+ to_chat(parent, span_notice("You'll have just one chance to go nova, and there's no turning back."))
+
+
+/// Remove any restrictions AFTER the mob has spawned
+/datum/component/virtual_entity/proc/on_emagged(datum/source)
+ SIGNAL_HANDLER
+
+ jailbreak_mobs()
+ qdel(src)
+
+
+/// Prevents entry to a certain area if it has flags preventing virtual entities from entering.
/datum/component/virtual_entity/proc/on_parent_pre_move(atom/movable/source, atom/new_location)
SIGNAL_HANDLER
@@ -27,12 +43,3 @@
COOLDOWN_START(src, OOB_cooldown, 2 SECONDS)
return COMPONENT_MOVABLE_BLOCK_PRE_MOVE
-///Self-destructs the component, allowing free-roam by all entities with this restriction.
-/datum/component/virtual_entity/proc/jailbreak_mobs()
- SIGNAL_HANDLER
-
- to_chat(parent, span_bolddanger("You shiver for a moment with a sense of clarity you haven't felt before."))
- to_chat(parent, span_notice("You could go anywhere, do anything! You could leave this simulation right now if you wanted!"))
- to_chat(parent, span_danger("But be warned, quantum entanglement will interfere with any previous lives."))
- to_chat(parent, span_notice("You'll have just one chance to go nova, and there's no turning back."))
- qdel(src)
diff --git a/code/modules/bitrunning/server/_parent.dm b/code/modules/bitrunning/server/_parent.dm
index f166a82c817..0f34826d1c8 100644
--- a/code/modules/bitrunning/server/_parent.dm
+++ b/code/modules/bitrunning/server/_parent.dm
@@ -49,12 +49,14 @@
/// Cooldown between being able to toggle broadcasting
COOLDOWN_DECLARE(broadcast_toggle_cd)
+
/obj/machinery/quantum_server/post_machine_initialize()
. = ..()
RegisterSignals(src, list(COMSIG_MACHINERY_BROKEN, COMSIG_MACHINERY_POWER_LOST), PROC_REF(on_broken))
RegisterSignal(src, COMSIG_QDELETING, PROC_REF(on_delete))
+
/obj/machinery/quantum_server/Destroy(force)
. = ..()
@@ -64,6 +66,7 @@
QDEL_NULL(exit_turfs)
QDEL_NULL(generated_domain)
+
/obj/machinery/quantum_server/examine(mob/user)
. = ..()
@@ -88,6 +91,7 @@
if(isobserver(user) && (obj_flags & EMAGGED))
. += span_notice("Ominous warning lights are blinking red. This server has been tampered with.")
+
/obj/machinery/quantum_server/emag_act(mob/user, obj/item/card/emag/emag_card)
. = ..()
@@ -102,6 +106,7 @@
balloon_alert(user, "system jailbroken...")
playsound(src, 'sound/effects/sparks/sparks1.ogg', 35, vary = TRUE)
+
/obj/machinery/quantum_server/update_appearance(updates)
if(isnull(generated_domain) || !is_operational)
set_light(l_on = FALSE)
@@ -110,6 +115,7 @@
set_light(l_range = 2, l_power = 1.5, l_color = is_ready ? LIGHT_COLOR_BABY_BLUE : LIGHT_COLOR_FIRE, l_on = TRUE)
return ..()
+
/obj/machinery/quantum_server/update_icon_state()
if(isnull(generated_domain) || !is_operational)
icon_state = base_icon_state
@@ -118,6 +124,7 @@
icon_state = "[base_icon_state]_[is_ready ? "on" : "off"]"
return ..()
+
/obj/machinery/quantum_server/attackby(obj/item/weapon, mob/user, params)
. = ..()
@@ -129,6 +136,7 @@
capacitor_coefficient = 0.1
points = 100
+
/obj/machinery/quantum_server/crowbar_act(mob/living/user, obj/item/crowbar)
. = ..()
@@ -142,6 +150,7 @@
return TRUE
return FALSE
+
/obj/machinery/quantum_server/screwdriver_act(mob/living/user, obj/item/screwdriver)
. = ..()
@@ -152,6 +161,7 @@
return TRUE
return FALSE
+
/obj/machinery/quantum_server/RefreshParts()
var/capacitor_rating = 1.15
var/datum/stock_part/capacitor/cap = locate() in component_parts