diff --git a/code/__DEFINES/dcs/atom_signals.dm b/code/__DEFINES/dcs/atom_signals.dm index 574a00cb2f6..f5f9a204bf6 100644 --- a/code/__DEFINES/dcs/atom_signals.dm +++ b/code/__DEFINES/dcs/atom_signals.dm @@ -6,6 +6,8 @@ // /atom +// from SSatoms InitAtom - Only if the atom was not deleted or failed initialization +#define COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZE "atom_init_success" ///from base of atom/attackby(): (/obj/item, /mob/living, params) #define COMSIG_PARENT_ATTACKBY "atom_attackby" ///Return this in response if you don't want afterattack to be called @@ -69,6 +71,8 @@ #define COMSIG_ATOM_SET_LIGHT "atom_set_light" ///from base of atom/setDir(): (old_dir, new_dir) #define COMSIG_ATOM_DIR_CHANGE "atom_dir_change" +///from [/datum/controller/subsystem/processing/dcs/proc/rotate_decals]: (list/datum/element/decal/rotating) +#define COMSIG_ATOM_DECALS_ROTATING "atom_decals_rotating" ///from base of atom/has_gravity(): (turf/location, list/forced_gravities) #define COMSIG_ATOM_HAS_GRAVITY "atom_has_gravity" ///from proc/get_rad_contents(): () diff --git a/code/__DEFINES/dcs/basetype_signals.dm b/code/__DEFINES/dcs/basetype_signals.dm index 6a5230caa46..6affef128aa 100644 --- a/code/__DEFINES/dcs/basetype_signals.dm +++ b/code/__DEFINES/dcs/basetype_signals.dm @@ -27,3 +27,5 @@ ///from base of turf/ChangeTurf(): (path, list/new_baseturfs, flags, list/transferring_comps) #define COMSIG_TURF_CHANGE "turf_change" +///from base of turf/proc/onShuttleMove(): (turf/new_turf) +#define COMSIG_TURF_ON_SHUTTLE_MOVE "turf_on_shuttle_move" diff --git a/code/__DEFINES/dcs/datum_signals.dm b/code/__DEFINES/dcs/datum_signals.dm index 6850155f345..1851311a8ab 100644 --- a/code/__DEFINES/dcs/datum_signals.dm +++ b/code/__DEFINES/dcs/datum_signals.dm @@ -53,10 +53,12 @@ #define COMSIG_SONG_END "song_end" -// /datum/component/decal +// /datum/element/decal -///called on an object to clean it of cleanables. Usualy with soap: (num/strength) +///called on an object to clean it of cleanables. #define COMSIG_COMPONENT_CLEAN_ACT "clean_act" + ///Returned by cleanable components when they are cleaned. + #define COMPONENT_CLEANED (1<<0) // /datum/component/two_handed diff --git a/code/__DEFINES/flags.dm b/code/__DEFINES/flags.dm index b42f5f9b6e8..08c5087b4b0 100644 --- a/code/__DEFINES/flags.dm +++ b/code/__DEFINES/flags.dm @@ -45,6 +45,9 @@ #define INFORM_ADMINS_ON_RELOCATE_2 (1<<5) #define BANG_PROTECT_2 (1<<6) #define BLOCKS_LIGHT_2 (1<<7) // Light sources placed in anything with that flag will not emit light through them. +/// Whether a decal element's parent has already been initialized and thus has already had its decals attached. +/// see https://github.com/tgstation/tgstation/pull/71658 for a detailed explanation of the flag. +#define DECAL_INIT_UPDATE_EXPERIENCED_2 (1<<8) // A mob with OMNITONGUE has no restriction in the ability to speak // languages that they know. So even if they wouldn't normally be able to diff --git a/code/__DEFINES/misc_defines.dm b/code/__DEFINES/misc_defines.dm index ebcf72a653d..a5995f17c6f 100644 --- a/code/__DEFINES/misc_defines.dm +++ b/code/__DEFINES/misc_defines.dm @@ -466,14 +466,6 @@ #define PLACE_SPACE_RUIN "space" #define PLACE_LAVA_RUIN "lavaland" -//Cleaning tool strength -// 1 is also a valid cleaning strength but completely unused so left undefined -#define CLEAN_WEAK 2 -#define CLEAN_MEDIUM 3 // Acceptable tools -#define CLEAN_STRONG 4 // Industrial strength -#define CLEAN_IMPRESSIVE 5 // Cleaning strong enough your granny would be proud -#define CLEAN_GOD 6 // Cleans things spotless down to the atomic structure - //Ghost orbit types: #define GHOST_ORBIT_CIRCLE "circle" #define GHOST_ORBIT_TRIANGLE "triangle" diff --git a/code/controllers/subsystem/non_firing/SSatoms.dm b/code/controllers/subsystem/non_firing/SSatoms.dm index fb541968037..d40c236192b 100644 --- a/code/controllers/subsystem/non_firing/SSatoms.dm +++ b/code/controllers/subsystem/non_firing/SSatoms.dm @@ -100,6 +100,8 @@ SUBSYSTEM_DEF(atoms) qdeleted = TRUE else if(!A.initialized) BadInitializeCalls[the_type] |= BAD_INIT_DIDNT_INIT + else + SEND_SIGNAL(A, COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZE) return qdeleted || QDELING(A) diff --git a/code/datums/components/decal.dm b/code/datums/components/decal.dm deleted file mode 100644 index b0cc7617e3c..00000000000 --- a/code/datums/components/decal.dm +++ /dev/null @@ -1,75 +0,0 @@ -/datum/component/decal - dupe_mode = COMPONENT_DUPE_ALLOWED - can_transfer = TRUE - var/cleanable - var/description - var/mutable_appearance/pic - - var/first_dir // This only stores the dir arg from init - -/datum/component/decal/Initialize(_icon, _icon_state, _dir, _cleanable = CLEAN_GOD, _color, _layer = TURF_LAYER, _description, _alpha = 255) - if(!isatom(parent) || !generate_appearance(_icon, _icon_state, _dir, _layer, _color, _alpha)) - return COMPONENT_INCOMPATIBLE - first_dir = _dir - description = _description - cleanable = _cleanable - - apply() - -/datum/component/decal/RegisterWithParent() - if(first_dir) - RegisterSignal(parent, COMSIG_ATOM_DIR_CHANGE, PROC_REF(rotate_react)) - if(cleanable) - RegisterSignal(parent, COMSIG_COMPONENT_CLEAN_ACT, PROC_REF(clean_react)) - if(description) - RegisterSignal(parent, COMSIG_PARENT_EXAMINE, PROC_REF(examine)) - -/datum/component/decal/UnregisterFromParent() - UnregisterSignal(parent, list(COMSIG_ATOM_DIR_CHANGE, COMSIG_COMPONENT_CLEAN_ACT, COMSIG_PARENT_EXAMINE)) - -/datum/component/decal/Destroy() - remove() - return ..() - -/datum/component/decal/PreTransfer() - remove() - -/datum/component/decal/PostTransfer() - remove() - apply() - -/datum/component/decal/proc/generate_appearance(_icon, _icon_state, _dir, _layer, _color, _alpha) - if(!_icon || !_icon_state) - return FALSE - // It has to be made from an image or dir breaks because of a byond bug - var/temp_image = image(_icon, null, _icon_state, _layer, _dir) - pic = new(temp_image) - pic.color = _color - pic.alpha = _alpha - return TRUE - -/datum/component/decal/proc/apply(atom/thing) - var/atom/master = thing || parent - master.add_overlay(pic, TRUE) - if(isitem(master)) - addtimer(CALLBACK(master, TYPE_PROC_REF(/obj/item, update_slot_icon)), 0, TIMER_UNIQUE) - -/datum/component/decal/proc/remove(atom/thing) - var/atom/master = thing || parent - master.cut_overlay(pic, TRUE) - if(isitem(master)) - addtimer(CALLBACK(master, TYPE_PROC_REF(/obj/item, update_slot_icon)), 0, TIMER_UNIQUE) - -/datum/component/decal/proc/rotate_react(datum/source, old_dir, new_dir) - if(old_dir == new_dir) - return - remove() - pic.dir = turn(pic.dir, dir2angle(old_dir) - dir2angle(new_dir)) - apply() - -/datum/component/decal/proc/clean_react(datum/source, strength) - if(strength >= cleanable) - qdel(src) - -/datum/component/decal/proc/examine(datum/source, mob/user, list/examine_list) - examine_list += description diff --git a/code/datums/elements/decal_element.dm b/code/datums/elements/decal_element.dm new file mode 100644 index 00000000000..c15e9f43a48 --- /dev/null +++ b/code/datums/elements/decal_element.dm @@ -0,0 +1,177 @@ +// NOTE: +// This is an incredibly piecemeal port of /tg/'s decal element. +// It does not include several pieces of functionality that exist in /tg/. +// +// Namely: +// - It does not support smoothing decals +// - It does not send a signal when a decal is detached (used for trapdoors on /tg/) +// - It does not support custom plane configuration as this behavior seems primarily concerned with multi-z + +/datum/element/decal + element_flags = ELEMENT_BESPOKE|ELEMENT_DETACH_ON_HOST_DESTROY + argument_hash_start_idx = 2 + /// Whether this decal can be cleaned. + var/cleanable + /// A description this decal appends to the target's examine message. + var/description + /// If true this was initialized with no set direction - will follow the parent dir. + var/directional + /// The base icon state that this decal was initialized with. + var/base_icon_state + /// The overlay applied by this decal to the target. + var/mutable_appearance/pic + +/datum/element/decal/Attach(atom/target, _icon, _icon_state, _dir, _layer=TURF_LAYER, _alpha=255, _color, _cleanable=FALSE, _description, mutable_appearance/_pic) + . = ..() + if(!isatom(target)) + return ELEMENT_INCOMPATIBLE + if(_pic) + pic = _pic + else if(!generate_appearance(_icon, _icon_state, _dir, _layer, _color, _alpha, target)) + return ELEMENT_INCOMPATIBLE + description = _description + cleanable = _cleanable + directional = _dir + base_icon_state = _icon_state + + RegisterSignal(target, COMSIG_ATOM_UPDATE_OVERLAYS, PROC_REF(apply_overlay), TRUE) + if(target.initialized) + target.update_appearance(UPDATE_OVERLAYS) //could use some queuing here now maybe. + else + RegisterSignal(target, COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZE, PROC_REF(late_update_icon), TRUE) + if(isitem(target)) + INVOKE_ASYNC(target, TYPE_PROC_REF(/obj/item/, update_slot_icon), TRUE) + if(_dir) + RegisterSignal(target, COMSIG_ATOM_DECALS_ROTATING, PROC_REF(shuttle_rotate), TRUE) + SSdcs.RegisterSignal(target, COMSIG_ATOM_DIR_CHANGE, TYPE_PROC_REF(/datum/controller/subsystem/processing/dcs, rotate_decals), override=TRUE) + if(_cleanable) + RegisterSignal(target, COMSIG_COMPONENT_CLEAN_ACT, PROC_REF(clean_react), TRUE) + if(_description) + RegisterSignal(target, COMSIG_PARENT_EXAMINE, PROC_REF(examine), TRUE) + + RegisterSignal(target, COMSIG_TURF_ON_SHUTTLE_MOVE, PROC_REF(shuttle_move_react), TRUE) + +/// Remove old decals and apply new decals after rotation as necessary +/datum/controller/subsystem/processing/dcs/proc/rotate_decals(datum/source, old_dir, new_dir) + SIGNAL_HANDLER // COMSIG_ATOM_DIR_CHANGE + + if(old_dir == new_dir) + return + + var/list/datum/element/decal/old_decals = list() //instances + SEND_SIGNAL(source, COMSIG_ATOM_DECALS_ROTATING, old_decals) + + if(!length(old_decals)) + UnregisterSignal(source, COMSIG_ATOM_DIR_CHANGE) + return + + var/list/resulting_decals_params = list() // param lists + for(var/datum/element/decal/rotating as anything in old_decals) + resulting_decals_params += list(rotating.get_rotated_parameters(old_dir,new_dir)) + + //Instead we could generate ids and only remove duplicates to save on churn on four-corners symmetry ? + for(var/datum/element/decal/decal in old_decals) + decal.Detach(source) + + for(var/result in resulting_decals_params) + source.AddElement(/datum/element/decal, result["icon"], result["icon_state"], result["dir"], result["layer"], result["alpha"], result["color"], result["cleanable"], result["desc"]) + +/datum/element/decal/proc/get_rotated_parameters(old_dir,new_dir) + var/rotation = 0 + if(directional) //Even when the dirs are the same rotation is coming out as not 0 for some reason + rotation = SIMPLIFY_DEGREES(dir2angle(new_dir)-dir2angle(old_dir)) + new_dir = turn(pic.dir,-rotation) + return list( + "icon" = pic.icon, + "icon_state" = base_icon_state, + "dir" = new_dir, + "plane" = pic.plane, + "layer" = pic.layer, + "alpha" = pic.alpha, + "color" = pic.color, + "cleanable" = cleanable, + "desc" = description + ) + +/datum/element/decal/proc/late_update_icon(atom/source) + SIGNAL_HANDLER // COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZE + + if(istype(source) && !(source.flags_2 & DECAL_INIT_UPDATE_EXPERIENCED_2)) + source.flags_2 |= DECAL_INIT_UPDATE_EXPERIENCED_2 + source.update_appearance(UPDATE_OVERLAYS) + UnregisterSignal(source, COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZE) + +/** + * If the decal was not given an appearance, it will generate one based on the other given arguments. + * element won't be compatible if it cannot do either + * all args are fed into creating an image, they are byond vars for images you'll recognize in the byond docs + * (except source, source is the object whose appearance we're copying.) + */ +/datum/element/decal/proc/generate_appearance(_icon, _icon_state, _dir, _layer, _color, _alpha, source) + if(!_icon || !_icon_state) + return FALSE + var/temp_image = image(_icon, null, _icon_state, _layer, _dir) + pic = new(temp_image) + pic.color = _color + pic.alpha = _alpha + return TRUE + +/datum/element/decal/Detach(atom/source) + UnregisterSignal(source, list( + COMSIG_ATOM_DIR_CHANGE, + COMSIG_COMPONENT_CLEAN_ACT, + COMSIG_PARENT_EXAMINE, + COMSIG_ATOM_UPDATE_OVERLAYS, + COMSIG_TURF_ON_SHUTTLE_MOVE, + COMSIG_ATOM_DECALS_ROTATING + )) + SSdcs.UnregisterSignal(source, COMSIG_ATOM_DIR_CHANGE) + source.update_appearance(UPDATE_OVERLAYS) + if(isitem(source)) + INVOKE_ASYNC(source, TYPE_PROC_REF(/obj/item/, update_slot_icon)) + return ..() + +/datum/element/decal/proc/apply_overlay(atom/source) + SIGNAL_HANDLER // COMSIG_ATOM_UPDATE_OVERLAYS + + source.add_overlay(pic) + // TODO: Fix this disgusting hack + // + // `COMSIG_ATOM_UPDATE_OVERLAYS` is sent at the end of + // /atom/proc/update_icon's stanza for updating overlays, instead + // somewhere useful, like, during it. /tg/ handles this by sending + // a list of overlays with the signal, allowing receivers to add to + // the list, instead of returning their own. + // + // This is much saner and more flexible, but would require refactoring + // many many uses of update_overlay() across the code base, which is left + // as an exercise for the next poor sap to touch this code (probably me). + if(source.managed_overlays && !islist(source.managed_overlays)) + source.managed_overlays = list(source.managed_overlays, pic) + else + LAZYDISTINCTADD(source.managed_overlays, pic) + +/datum/element/decal/proc/clean_react(datum/source, clean_types) + SIGNAL_HANDLER // COMSIG_COMPONENT_CLEAN_ACT + + if(clean_types & cleanable) + Detach(source) + return COMPONENT_CLEANED + return NONE + +/datum/element/decal/proc/examine(datum/source, mob/user, list/examine_list) + SIGNAL_HANDLER // COMSIG_PARENT_EXAMINE + + examine_list += description + +/datum/element/decal/proc/shuttle_move_react(datum/source, turf/new_turf) + SIGNAL_HANDLER // COMSIG_TURF_ON_SHUTTLE_MOVE + + if(new_turf == source) + return + Detach(source) + new_turf.AddElement(type, pic.icon, base_icon_state, directional, pic.layer, pic.alpha, pic.color, cleanable, description) + +/datum/element/decal/proc/shuttle_rotate(datum/source, list/datum/element/decal/rotating) + SIGNAL_HANDLER // COMSIG_ATOM_DECALS_ROTATING + rotating += src diff --git a/code/game/objects/effects/decals/turf_decal.dm b/code/game/objects/effects/decals/turf_decal.dm index 69cc4461587..8d6315aad78 100644 --- a/code/game/objects/effects/decals/turf_decal.dm +++ b/code/game/objects/effects/decals/turf_decal.dm @@ -9,4 +9,5 @@ var/turf/T = loc if(!istype(T)) //you know this will happen somehow CRASH("Turf decal initialized in an object/nullspace") - T.AddComponent(/datum/component/decal, icon, icon_state, dir, CLEAN_GOD, color, null, null, alpha) + + T.AddElement(/datum/element/decal, icon, icon_state, dir, layer, alpha, color, FALSE, null) diff --git a/code/modules/shuttle/shuttle.dm b/code/modules/shuttle/shuttle.dm index 35ae17be544..5e373f5c410 100644 --- a/code/modules/shuttle/shuttle.dm +++ b/code/modules/shuttle/shuttle.dm @@ -534,6 +534,8 @@ continue AM.onShuttleMove(T0, T1, rotation, mobile_port.last_caller) + SEND_SIGNAL(T0, COMSIG_TURF_ON_SHUTTLE_MOVE, T1) + if(rotation) T1.shuttleRotate(rotation) diff --git a/paradise.dme b/paradise.dme index d31bab4f1ff..5d16c47ddfd 100644 --- a/paradise.dme +++ b/paradise.dme @@ -431,7 +431,6 @@ #include "code\datums\components\cult_held_body.dm" #include "code\datums\components\deadchat_control.dm" #include "code\datums\components\debris.dm" -#include "code\datums\components\decal.dm" #include "code\datums\components\defibrillator.dm" #include "code\datums\components\ducttape.dm" #include "code\datums\components\edit_complainer.dm" @@ -527,6 +526,7 @@ #include "code\datums\elements\atmos_requirements.dm" #include "code\datums\elements\body_temperature.dm" #include "code\datums\elements\bombable_turf.dm" +#include "code\datums\elements\decal_element.dm" #include "code\datums\elements\earhealing.dm" #include "code\datums\elements\rad_insulation.dm" #include "code\datums\elements\ridable.dm"