Acid particles (+unsorted acid/burning/thermite component changes) (#75248)

This PR is way less than the file changes make it seem like it is.
Okay, first, the boring part:
- Picking up burning items is now a signal registered on the burning
component itself, instead of being a direct /obj/item/attack_hand()
check
- Sear sound now has an SFX define for convenience, since it is very
commonly used
- Fire stacks when extinguished on mobs will no longer clean acid on
items (WTF?)
This commit is contained in:
ChungusGamer666
2023-06-16 06:39:24 +00:00
committed by GitHub
parent 52d9c20de8
commit 1bede4dcff
20 changed files with 232 additions and 136 deletions
+61 -42
View File
@@ -15,43 +15,45 @@ GLOBAL_DATUM_INIT(acid_overlay, /mutable_appearance, mutable_appearance('icons/e
var/acid_volume
/// The maximum volume of acid on the parent [/atom].
var/max_volume = INFINITY
/// Acid overlay appearance we apply
var/acid_overlay
/// The ambiant sound of acid eating away at the parent [/atom].
var/datum/looping_sound/acid/sizzle
/// Used exclusively for melting turfs. TODO: Move integrity to the atom level so that this can be dealt with there.
var/parent_integrity = 30
/// How far the acid melting of turfs has progressed
var/stage = 0
/// Acid overlay appearance we apply
var/acid_overlay
/// The ambient sound of acid eating away at the parent [/atom].
var/datum/looping_sound/acid/sizzle
/// Particle holder for acid particles (sick)
var/obj/effect/abstract/particle_holder/particle_effect
/// The proc used to handle the parent [/atom] when processing. TODO: Unify damage and resistance flags so that this doesn't need to exist!
var/datum/callback/process_effect
/datum/component/acid/Initialize(acid_power = ACID_POWER_MELT_TURF, acid_volume = 50, acid_overlay = GLOB.acid_overlay)
/datum/component/acid/Initialize(acid_power = ACID_POWER_MELT_TURF, acid_volume = 50, acid_overlay = GLOB.acid_overlay, acid_particles = /particles/acid)
if(!isatom(parent))
return COMPONENT_INCOMPATIBLE
//not incompatible, but pointless
// The parent object cannot have acid. Not incompatible, but should not really happen.
var/atom/atom_parent = parent
if((acid_power) <= 0 || (acid_volume <= 0))
stack_trace("Acid component added to an atom ([atom_parent.type]) with insufficient acid power ([acid_power]) or acid volume ([acid_volume]).")
if(atom_parent.resistance_flags & UNACIDABLE)
qdel(src)
return
//Not incompatible, but pointless
if((acid_power <= 0) || (acid_volume <= 0))
qdel(src)
stack_trace("Tried to add /datum/component/acid to an atom ([atom_parent.type]) with insufficient acid power ([acid_power]) or acid volume ([acid_volume]).")
return
if(isliving(parent))
max_volume = MOB_ACID_VOLUME_MAX
process_effect = CALLBACK(src, PROC_REF(process_mob), parent)
src.max_volume = MOB_ACID_VOLUME_MAX
src.process_effect = CALLBACK(src, PROC_REF(process_mob), parent)
else if(isturf(parent))
max_volume = TURF_ACID_VOLUME_MAX
process_effect = CALLBACK(src, PROC_REF(process_turf), parent)
src.max_volume = TURF_ACID_VOLUME_MAX
src.process_effect = CALLBACK(src, PROC_REF(process_turf), parent)
//if we failed all other checks, we must be an /atom/movable that uses integrity
else if(atom_parent.uses_integrity)
// The parent object cannot have acid. Not incompatible, but should not really happen.
if(atom_parent.resistance_flags & UNACIDABLE)
qdel(src)
return
max_volume = MOVABLE_ACID_VOLUME_MAX
process_effect = CALLBACK(src, PROC_REF(process_movable), parent)
src.max_volume = MOVABLE_ACID_VOLUME_MAX
src.process_effect = CALLBACK(src, PROC_REF(process_movable), parent)
//or not...
else
stack_trace("Tried to add /datum/component/acid to an atom ([atom_parent.type]) which does not use atom_integrity!")
@@ -61,23 +63,28 @@ GLOBAL_DATUM_INIT(acid_overlay, /mutable_appearance, mutable_appearance('icons/e
set_volume(acid_volume)
src.acid_overlay = acid_overlay
sizzle = new(parent, TRUE)
sizzle = new(atom_parent, TRUE)
if(acid_particles)
// acid particles look pretty bad when they stack on mobs, so that behavior is not wanted for items
particle_effect = new(atom_parent, acid_particles, isitem(atom_parent) ? NONE : PARTICLE_ATTACH_MOB)
START_PROCESSING(SSacid, src)
/datum/component/acid/Destroy(force, silent)
STOP_PROCESSING(SSacid, src)
if(sizzle)
QDEL_NULL(sizzle)
if(particle_effect)
QDEL_NULL(particle_effect)
if(process_effect)
QDEL_NULL(process_effect)
return ..()
/datum/component/acid/RegisterWithParent()
RegisterSignal(parent, COMSIG_ATOM_ATTACK_HAND, PROC_REF(on_attack_hand))
RegisterSignal(parent, COMSIG_ATOM_EXAMINE, PROC_REF(on_examine))
RegisterSignal(parent, COMSIG_ATOM_EXPOSE_REAGENT, PROC_REF(on_expose_reagent))
RegisterSignal(parent, COMSIG_ATOM_UPDATE_OVERLAYS, PROC_REF(on_update_overlays))
RegisterSignal(parent, COMSIG_COMPONENT_CLEAN_ACT, PROC_REF(on_clean))
RegisterSignal(parent, COMSIG_ATOM_ATTACK_HAND, PROC_REF(on_attack_hand))
RegisterSignal(parent, COMSIG_ATOM_EXPOSE_REAGENT, PROC_REF(on_expose_reagent))
if(isturf(parent))
RegisterSignal(parent, COMSIG_ATOM_ENTERED, PROC_REF(on_entered))
var/atom/atom_parent = parent
@@ -85,11 +92,12 @@ GLOBAL_DATUM_INIT(acid_overlay, /mutable_appearance, mutable_appearance('icons/e
/datum/component/acid/UnregisterFromParent()
UnregisterSignal(parent, list(
COMSIG_ATOM_ATTACK_HAND,
COMSIG_ATOM_EXAMINE,
COMSIG_ATOM_EXPOSE_REAGENT,
COMSIG_ATOM_UPDATE_OVERLAYS,
COMSIG_COMPONENT_CLEAN_ACT,
COMSIG_ATOM_ATTACK_HAND,
COMSIG_ATOM_EXPOSE_REAGENT))
))
if(isturf(parent))
UnregisterSignal(parent, COMSIG_ATOM_ENTERED)
var/atom/atom_parent = parent
@@ -98,6 +106,8 @@ GLOBAL_DATUM_INIT(acid_overlay, /mutable_appearance, mutable_appearance('icons/e
/// Averages corrosive power and sums volume.
/datum/component/acid/InheritComponent(datum/component/new_comp, i_am_original, acid_power, acid_volume)
if(!i_am_original)
return
acid_power = ((src.acid_power * src.acid_volume) + (acid_power * acid_volume)) / (src.acid_volume + acid_volume)
set_volume(src.acid_volume + acid_volume)
@@ -109,6 +119,11 @@ GLOBAL_DATUM_INIT(acid_overlay, /mutable_appearance, mutable_appearance('icons/e
/// Handles the slow corrosion of the parent [/atom].
/datum/component/acid/process(seconds_per_tick)
// If we somehow got unacidable, we need to bail out
var/atom/parent_atom = parent
if(parent_atom.resistance_flags & UNACIDABLE)
qdel(src)
return
process_effect?.InvokeAsync(seconds_per_tick)
if(QDELING(src)) //The process effect deals damage, and on turfs diminishes the acid volume, potentially destroying the component. Let's not destroy it twice.
return
@@ -122,6 +137,8 @@ GLOBAL_DATUM_INIT(acid_overlay, /mutable_appearance, mutable_appearance('icons/e
/// Handles processing on a [/mob/living].
/datum/component/acid/proc/process_mob(mob/living/target, seconds_per_tick)
if(target.resistance_flags & ACID_PROOF)
return
target.acid_act(acid_power, acid_volume * seconds_per_tick)
/// Handles processing on a [/turf].
@@ -135,7 +152,11 @@ GLOBAL_DATUM_INIT(acid_overlay, /mutable_appearance, mutable_appearance('icons/e
if(applied_targets)
set_volume(acid_volume - (acid_used * applied_targets))
// Snowflake code for handling acid melting walls. TODO: Move integrity handling to the atom level so this can be desnowflaked.
if(target_turf.resistance_flags & ACID_PROOF)
return
// Snowflake code for handling acid melting walls.
// We really should consider making turfs use atom_integrity, but for now this is just for acids.
if(acid_power < ACID_POWER_MELT_TURF)
return
@@ -189,25 +210,22 @@ GLOBAL_DATUM_INIT(acid_overlay, /mutable_appearance, mutable_appearance('icons/e
set_volume(acid_volume + reac_volume)
return NONE
/// Handles searing the hand of anyone who tries to touch this without protection.
/datum/component/acid/proc/on_attack_hand(atom/parent_atom, mob/living/carbon/user)
/// Handles searing the hand of anyone who tries to touch parent without protection.
/datum/component/acid/proc/on_attack_hand(atom/source, mob/living/carbon/user)
SIGNAL_HANDLER
if(!istype(user))
return NONE
if((parent_atom == user) || (parent_atom.loc == user))
return NONE // So people can take their own clothes off.
if((acid_power * acid_volume) < ACID_LEVEL_HANDBURN)
return NONE
if(user.gloves?.resistance_flags & (UNACIDABLE|ACID_PROOF))
if(!iscarbon(user) || user.can_touch_acid(source, acid_power, acid_volume))
return NONE
var/obj/item/bodypart/affecting = user.get_bodypart("[(user.active_hand_index % 2 == 0) ? "r" : "l" ]_arm")
if(!affecting?.receive_damage(0, 5))
var/obj/item/bodypart/affecting = user.get_active_hand()
//Should not happen!
if(!affecting)
return NONE
to_chat(user, span_warning("The acid on \the [parent_atom] burns your hand!"))
playsound(parent_atom, 'sound/weapons/sear.ogg', 50, TRUE)
affecting.receive_damage(burn = 5)
to_chat(user, span_userdanger("The acid on \the [source] burns your hand!"))
INVOKE_ASYNC(user, TYPE_PROC_REF(/mob, emote), "scream")
playsound(source, SFX_SEAR, 50, TRUE)
user.update_damage_overlays()
return COMPONENT_CANCEL_ATTACK_CHAIN
@@ -226,7 +244,8 @@ GLOBAL_DATUM_INIT(acid_overlay, /mutable_appearance, mutable_appearance('icons/e
return
var/acid_used = min(acid_volume * 0.05, 20)
if(crosser.acid_act(acid_power, acid_used, FEET))
playsound(crosser, 'sound/weapons/sear.ogg', 50, TRUE)
to_chat(crosser, span_userdanger("The acid on the [parent] burns you!"))
set_volume(max(acid_volume - acid_used, 10))
if(!crosser.acid_act(acid_power, acid_used, FEET))
return
playsound(crosser, SFX_SEAR, 50, TRUE)
to_chat(crosser, span_userdanger("The acid on the [parent] burns you!"))
set_volume(max(acid_volume - acid_used, 10))
+33 -3
View File
@@ -18,10 +18,12 @@ GLOBAL_DATUM_INIT(fire_overlay, /mutable_appearance, mutable_appearance('icons/e
if(!atom_parent.uses_integrity)
stack_trace("Tried to add /datum/component/burning to an atom ([atom_parent.type]) that does not use atom_integrity!")
return COMPONENT_INCOMPATIBLE
// only flammable atoms should have this component, but it's not really an error if we try to apply this to a non flammable one
if(!(atom_parent.resistance_flags & FLAMMABLE) || (atom_parent.resistance_flags & FIRE_PROOF))
qdel(src)
return
src.fire_overlay = fire_overlay
if(fire_particles)
// burning particles look pretty bad when they stack on mobs, so that behavior is not wanted for items
@@ -30,20 +32,27 @@ GLOBAL_DATUM_INIT(fire_overlay, /mutable_appearance, mutable_appearance('icons/e
/datum/component/burning/Destroy(force, silent)
STOP_PROCESSING(SSburning, src)
fire_overlay = null
if(particle_effect)
QDEL_NULL(particle_effect)
return ..()
/datum/component/burning/RegisterWithParent()
RegisterSignal(parent, COMSIG_ATOM_EXAMINE, PROC_REF(on_examine))
RegisterSignal(parent, COMSIG_ATOM_ATTACK_HAND, PROC_REF(on_attack_hand))
RegisterSignal(parent, COMSIG_ATOM_UPDATE_OVERLAYS, PROC_REF(on_update_overlays))
RegisterSignal(parent, COMSIG_ATOM_EXAMINE, PROC_REF(on_examine))
RegisterSignal(parent, COMSIG_ATOM_EXTINGUISH, PROC_REF(on_extinguish))
var/atom/atom_parent = parent
atom_parent.resistance_flags |= ON_FIRE
atom_parent.update_appearance()
/datum/component/burning/UnregisterFromParent()
UnregisterSignal(parent, list(COMSIG_ATOM_EXAMINE, COMSIG_ATOM_UPDATE_OVERLAYS, COMSIG_ATOM_EXTINGUISH))
UnregisterSignal(parent, list(
COMSIG_ATOM_ATTACK_HAND,
COMSIG_ATOM_UPDATE_OVERLAYS,
COMSIG_ATOM_EXAMINE,
COMSIG_ATOM_EXTINGUISH,
))
var/atom/atom_parent = parent
if(!QDELETED(atom_parent))
atom_parent.resistance_flags &= ~ON_FIRE
@@ -51,7 +60,7 @@ GLOBAL_DATUM_INIT(fire_overlay, /mutable_appearance, mutable_appearance('icons/e
/datum/component/burning/process(seconds_per_tick)
var/atom/atom_parent = parent
// Check if the parent somehow became fireproof
// Check if the parent somehow became fireproof, remove component if so
if(atom_parent.resistance_flags & FIRE_PROOF)
atom_parent.extinguish()
return
@@ -63,6 +72,27 @@ GLOBAL_DATUM_INIT(fire_overlay, /mutable_appearance, mutable_appearance('icons/e
examine_list += span_danger("[source.p_theyre(TRUE)] burning!")
/// Handles searing the hand of anyone who tries to touch parent without protection.
/datum/component/burning/proc/on_attack_hand(atom/source, mob/living/carbon/user)
SIGNAL_HANDLER
if(!iscarbon(user) || user.can_touch_burning(source))
to_chat(user, span_notice("You put out the fire on [source]."))
source.extinguish()
return COMPONENT_CANCEL_ATTACK_CHAIN
var/obj/item/bodypart/affecting = user.get_active_hand()
//Should not happen!
if(!affecting)
return NONE
affecting.receive_damage(burn = 5)
to_chat(user, span_userdanger("You burn your hand on [source]!"))
INVOKE_ASYNC(user, TYPE_PROC_REF(/mob, emote), "scream")
playsound(source, SFX_SEAR, 50, TRUE)
user.update_damage_overlays()
return COMPONENT_CANCEL_ATTACK_CHAIN
/// Maintains the burning overlay on the parent atom
/datum/component/burning/proc/on_update_overlays(atom/source, list/overlays)
SIGNAL_HANDLER
+35 -10
View File
@@ -6,6 +6,8 @@
var/burn_require
/// The thermite overlay
var/thermite_overlay
/// Default thermite overlay, do not touch
var/static/mutable_appearance/default_thermite_overlay = mutable_appearance('icons/effects/effects.dmi', "thermite")
/// Callback related to burning, stored so the timer can be easily reset without losing the user
var/datum/callback/burn_callback
/// The timer for burning parent, calls burn_callback when done
@@ -13,8 +15,6 @@
/// The thermite fire overlay
var/obj/effect/overlay/thermite/fakefire
/// Default thermite overlay, do not touch
var/static/mutable_appearance/default_thermite_overlay = mutable_appearance('icons/effects/effects.dmi', "thermite")
/// Blacklist of turfs that cannot have thermite on it
var/static/list/blacklist = typecacheof(list(
/turf/open/lava,
@@ -54,32 +54,34 @@
/datum/component/thermite/Destroy()
thermite_overlay = null
if(burn_callback)
QDEL_NULL(burn_callback)
if(burn_timer)
deltimer(burn_timer)
burn_timer = null
if(burn_callback)
QDEL_NULL(burn_callback)
if(fakefire)
QDEL_NULL(fakefire)
return ..()
/datum/component/thermite/RegisterWithParent()
RegisterSignal(parent, COMSIG_ATOM_ATTACKBY, PROC_REF(attackby_react))
RegisterSignal(parent, COMSIG_ATOM_ATTACK_HAND, PROC_REF(on_attack_hand))
RegisterSignal(parent, COMSIG_ATOM_EXAMINE, PROC_REF(on_examine))
RegisterSignal(parent, COMSIG_ATOM_FIRE_ACT, PROC_REF(on_fire_act))
RegisterSignal(parent, COMSIG_ATOM_UPDATE_OVERLAYS, PROC_REF(on_update_overlays))
RegisterSignal(parent, COMSIG_COMPONENT_CLEAN_ACT, PROC_REF(clean_react))
RegisterSignal(parent, COMSIG_ATOM_ATTACKBY, PROC_REF(attackby_react))
RegisterSignal(parent, COMSIG_ATOM_EXAMINE, PROC_REF(on_examine))
RegisterSignal(parent, COMSIG_QDELETING, PROC_REF(parent_qdeleting)) //probably necessary because turfs are wack
var/turf/turf_parent = parent
turf_parent.update_appearance()
/datum/component/thermite/UnregisterFromParent()
UnregisterSignal(parent, list(
COMSIG_ATOM_ATTACKBY,
COMSIG_ATOM_ATTACK_HAND,
COMSIG_ATOM_EXAMINE,
COMSIG_ATOM_FIRE_ACT,
COMSIG_ATOM_UPDATE_OVERLAYS,
COMSIG_COMPONENT_CLEAN_ACT,
COMSIG_ATOM_ATTACKBY,
COMSIG_ATOM_EXAMINE,
COMSIG_QDELETING,
))
var/turf/turf_parent = parent
@@ -118,7 +120,7 @@
fakefire = new(parent_turf)
burn_callback = CALLBACK(src, PROC_REF(burn_parent), user)
burn_timer = addtimer(burn_callback, min(amount * 0.35 SECONDS, 20 SECONDS), TIMER_STOPPABLE)
//unregister everything mechanical, we are burning up
//unregister everything related to burning
UnregisterSignal(parent, list(COMSIG_COMPONENT_CLEAN_ACT, COMSIG_ATOM_ATTACKBY, COMSIG_ATOM_FIRE_ACT))
/**
@@ -145,7 +147,7 @@
/datum/component/thermite/proc/clean_react(datum/source, strength)
SIGNAL_HANDLER
//Thermite is just some loose powder, you could probably clean it with your hands. << todo?
//Thermite is just some loose powder, you could probably clean it with your hands
qdel(src)
return COMPONENT_CLEANED
@@ -166,6 +168,29 @@
if(exposed_temperature >= 1922)
thermite_melt()
/// Handles searing the hand of anyone who tries to touch parent without protection, while burning
/datum/component/thermite/proc/on_attack_hand(atom/source, mob/living/carbon/user)
SIGNAL_HANDLER
//not burning
if(!fakefire)
return NONE
if(!iscarbon(user) || user.can_touch_burning(source))
return NONE
var/obj/item/bodypart/affecting = user.get_active_hand()
//Should not happen!
if(!affecting)
return NONE
affecting.receive_damage(burn = 5)
to_chat(user, span_userdanger("The ignited thermite on \the [source] burns your hand!"))
INVOKE_ASYNC(user, TYPE_PROC_REF(/mob, emote), "scream")
playsound(source, SFX_SEAR, 50, TRUE)
user.update_damage_overlays()
return COMPONENT_CANCEL_ATTACK_CHAIN
/**
* attackby reaction, ignites the thermite if its a flame creating object
*
@@ -272,11 +272,7 @@
cache_stacks()
update_overlay()
update_particles()
if(!iscarbon(owner))
return
for(var/obj/item/equipped in owner.get_equipped_items())
equipped.wash(CLEAN_TYPE_ACID)
equipped.extinguish()
/datum/status_effect/fire_handler/fire_stacks/on_remove()