Files
Bubberstation/code/game/objects/items/tools/engineering/weldingtool.dm
T
LemonInTheDark 9d14e327b5 Begins Improving Sparks/Flares Somewhat, Adds Animatable Light Overlays (#95362)
## About The Pull Request

[Adds a visual tick helper, integrates it into SSmove and
such](https://github.com/tgstation/tgstation/commit/e97035f9f74fad5c67c5bf19d8d5d3bb4bd476b4)

Basically, if we do "stuff" during verb time then the next chance
clients have to actually see it is on the next visual tick (rather then
the normal "this tick"). This is cause clients get their next frame
during maptick, and maptick runs before verbs.

We want to be able to handle this properly because if you say, create an
object and then move it on the same VISUAL tick (NOT game tick), it will
just teleport instead of playing out the move. I don't want this for
stuff like sparks, so we need a way to work around it.

[Moves most users of the _FAST flag to
_INSTANT](https://github.com/tgstation/tgstation/commit/6f96daac00519c69adc7554f52114798a65f3ad5)

These are the kids that don't immediately spawn something and the move
it, and we want to allow them to move actually as soon as possible
(important for stuff like space)

[Improves basic effect systems, makes their products delete when they
stop
moving](https://github.com/tgstation/tgstation/commit/172cb25d80ed34e1ec523172a1677fb524239fba)

Moves some stuff out to getters or vars so children can better decide
how long effects should last/how fast they should move. Uses this to
clean up weird dupe code used by explosions.

Makes all these effects delete on contact with something that stops
them. I'm doing this because an effect just hanging in the air looks
really really odd. Does have consequences for sparks that are already
moving at a wall though, might need a better way to handle that.

Makes all these effects use _FAST loops so they don't just hang in the
air for a second on spawn

Adds a setter proc on sparks for their duration, gonna use this to
improve their effects some

[Refactors overlay lights, adds support for animating their
images](https://github.com/tgstation/tgstation/commit/3ad0083cf2b536df51a6d93dca40eac20c1d62d1)

Implements light_render_source and relevant setters, this allows us to
replace the components of an overlay light with basically whatever we
want

Refactors overlay lighting to handle its images more consistently,
allowing us to hook into an image being modified

Combining the two of these will allow us to consistently copy a light's
image, modify it in some way, and then relay that modification back
down. Allowing us to animate it or do more advanced effects painlessly

Also, fixes ranges of 1 or less not rendering at all on initial set
(thank you kapu)

[In which I get fed up and add a macro helper for UID
generation](https://github.com/tgstation/tgstation/commit/aab48b03d407104d4f9cf9acb034494237def911)

[adds vv hooking for all existing lighting
vars](https://github.com/tgstation/tgstation/commit/b81c6200a0d74c36b440aa3f4c1f22c422090a2d)

[Upgrade effect system's dir picking to avoid duplicates when
possible](https://github.com/tgstation/tgstation/commit/18b622586b509c6be4c4bca4e3e7c175ad75fe91)

[Uses the technique described above to animate spark's lights out as
they
move](https://github.com/tgstation/tgstation/commit/67ba177982213799984a70e89536c5efb3d17e14)

This is a decently nice effect imo, it allows us to bump their power
(read, alpha) since it'll get animated away. I try to sync the animation
to the actual icon state's flow (it's 0.7s long). I also sped them up
somewhat to hopefully have a nicer looking effect? we'll see.

[Abstracts away intercepting overlay lights into a holder
datum](https://github.com/tgstation/tgstation/pull/95362/commits/b3f1fe74f2c3bab1d8912ab8a666bd05677ad032)

This should make it far easier to reuse this pattern!

[Fixes overlay lights flashing to double intensity when picked up off
the
ground](https://github.com/tgstation/tgstation/pull/95362/commits/1d83f2031fa2b33312b2aea4359c0c37c9d04ac7)

We needed to clear out their underlays BEFORE the animation

[Adds a flickering effect to flares and their
children](https://github.com/tgstation/tgstation/pull/95362/commits/b7a858e04a607c58b6c7fbe1476ffe2239e63bde)

I'm still not 100% happy with this, I was trying to avoid it feeling
like a heartbeat with random noise and I.. THINK it worked? it's
honestly quite hard to tell

[Adds the same flickering to lighters, welding tools and life
candles](https://github.com/tgstation/tgstation/pull/95362/commits/3ec44027e17835ae96702cec5f0b12d1f4deb32b)

Also, updated light candles to mirror the appearance of normal candles
and use overlay lighting

EDIT:
I realized while working on flares that I accidentally double applied
color, so if you saw the sparks animations before now it was different
(less vibrant). IDK if I like this better or worse but it is RIGHT and
that's what matters.

## Why It's Good For The Game

I got mad about how bad these looked, and this is a start at improving
them.
Also, adds a framework for more dynamic effects applied to overlay
lights (you could use this to apply a sort of "emergency rotating"
effect, or flicker/buzz for example).

<details>
 <summary>Before</summary>


https://github.com/user-attachments/assets/66437f27-ee3c-4f14-a7ee-4a1c3e68533a


https://github.com/user-attachments/assets/ed14fff8-a7eb-47fe-bab5-9a490ac96629

</details>

<details>
 <summary>After</summary>


https://github.com/user-attachments/assets/fb24ff2e-c745-42a5-8e11-c8a1eeef35a5


https://github.com/user-attachments/assets/fd8c2116-cb92-4fe6-ad3e-786a6538e52a

</details>

## Changelog
🆑
add: Reworks how sparks render. They're now a bit brighter, will fade
out as they move/if they hit something, will stack with each other less
and also won't start hang in the air on spawn.
add: Added a flickering effect to lighters, welding tools, flares,
torches and candles (since they're flames).
fix: Overlay based lights (think flashlights) will no longer flash to
double intensity while being picked up.
refactor: Reworked how some effects (explosion particles, sparks, some
reagent stuff) function, report any bugs!
/🆑
2026-03-20 14:48:57 +13:00

423 lines
14 KiB
Plaintext

/obj/item/weldingtool
name = "welding tool"
desc = "A standard edition welder provided by Nanotrasen."
icon = 'icons/obj/tools.dmi'
icon_state = "welder"
inhand_icon_state = "welder"
worn_icon_state = "welder"
lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi'
righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi'
obj_flags = CONDUCTS_ELECTRICITY
slot_flags = ITEM_SLOT_BELT
force = 3
throwforce = 5
hitsound = SFX_SWING_HIT
usesound = list('sound/items/tools/welder.ogg', 'sound/items/tools/welder2.ogg')
drop_sound = 'sound/items/handling/tools/weldingtool_drop.ogg'
pickup_sound = 'sound/items/handling/tools/weldingtool_pickup.ogg'
light_system = OVERLAY_LIGHT
light_range = 2
light_power = 1.5
light_color = LIGHT_COLOR_FIRE
light_on = FALSE
throw_speed = 3
throw_range = 5
w_class = WEIGHT_CLASS_SMALL
armor_type = /datum/armor/item_weldingtool
resistance_flags = FIRE_PROOF
heat = 3800
tool_behaviour = TOOL_WELDER
toolspeed = 1
wound_bonus = 10
exposed_wound_bonus = 15
custom_materials = list(/datum/material/iron=SMALL_MATERIAL_AMOUNT*0.7, /datum/material/glass=SMALL_MATERIAL_AMOUNT*0.3)
/// Whether the welding tool is on or off.
var/welding = FALSE
/// Whether the welder is secured or unsecured (able to attach rods to it to make a flamethrower)
var/status = TRUE
/// The max amount of fuel the welder can hold
var/max_fuel = 20
/// Does the welder start with fuel.
var/starting_fuel = TRUE
/// Whether or not we're changing the icon based on fuel left.
var/change_icons = TRUE
/// Used in process(), dictates whether or not we're calling STOP_PROCESSING whilst we're not welding.
var/can_off_process = FALSE
/// When fuel was last removed.
var/burned_fuel_for = 0
var/activation_sound = 'sound/items/tools/welderactivate.ogg'
var/deactivation_sound = 'sound/items/tools/welderdeactivate.ogg'
/// Lighting middleman, lets us do a flicker effect
var/datum/light_middleman/middleman
/datum/armor/item_weldingtool
fire = 100
acid = 30
/obj/item/weldingtool/Initialize(mapload)
. = ..()
if(IS_OVERLAY_LIGHT_SYSTEM(light_system))
middleman = new(src, "flashlight")
RegisterSignal(middleman, COMSIG_LIGHT_MIDDLEMAN_UPDATED, PROC_REF(light_updated))
middleman.being_overriding_light()
AddElement(/datum/element/update_icon_updates_onmob)
AddElement(/datum/element/tool_flash, light_range)
AddElement(/datum/element/falling_hazard, damage = force, wound_bonus = wound_bonus, hardhat_safety = TRUE, crushes = FALSE, impact_sound = hitsound)
create_reagents(max_fuel)
if(starting_fuel)
reagents.add_reagent(/datum/reagent/fuel, max_fuel)
update_appearance()
/obj/item/weldingtool/Destroy(force)
QDEL_NULL(middleman)
return ..()
/obj/item/weldingtool/update_icon_state()
if(welding)
inhand_icon_state = "[initial(inhand_icon_state)]1"
else
inhand_icon_state = "[initial(inhand_icon_state)]"
return ..()
/obj/item/weldingtool/update_overlays()
. = ..()
if(change_icons)
var/ratio = get_fuel() / max_fuel
ratio = CEILING(ratio*4, 1) * 25
. += "[initial(icon_state)][ratio]"
if(welding)
. += "[initial(icon_state)]-on"
/obj/item/weldingtool/process(seconds_per_tick)
if(welding)
force = 15
damtype = BURN
burned_fuel_for += seconds_per_tick
if(burned_fuel_for >= TOOL_FUEL_BURN_INTERVAL)
use(TRUE)
update_appearance()
//Welders left on now use up fuel, but lets not have them run out quite that fast
else
force = 3
damtype = BRUTE
update_appearance()
if(!can_off_process)
STOP_PROCESSING(SSobj, src)
return
//This is to start fires. process() is only called if the welder is on.
open_flame()
/obj/item/weldingtool/suicide_act(mob/living/user)
user.visible_message(span_suicide("[user] welds [user.p_their()] every orifice closed! It looks like [user.p_theyre()] trying to commit suicide!"))
return FIRELOSS
/obj/item/weldingtool/screwdriver_act(mob/living/user, obj/item/tool)
flamethrower_screwdriver(tool, user)
return ITEM_INTERACT_SUCCESS
/obj/item/weldingtool/attackby(obj/item/tool, mob/user, list/modifiers, list/attack_modifiers)
if(istype(tool, /obj/item/stack/rods))
flamethrower_rods(tool, user)
else
. = ..()
update_appearance()
/obj/item/weldingtool/cyborg_unequip(mob/user)
if(!isOn())
return
switched_on(user)
/obj/item/weldingtool/use_tool(atom/target, mob/living/user, delay, amount, volume, datum/callback/extra_checks)
var/mutable_appearance/sparks = mutable_appearance('icons/effects/welding_effect.dmi', "welding_sparks", GASFIRE_LAYER, src, ABOVE_LIGHTING_PLANE)
target.add_overlay(sparks)
LAZYADD(target.update_overlays_on_z, sparks)
. = ..()
LAZYREMOVE(target.update_overlays_on_z, sparks)
target.cut_overlay(sparks)
/obj/item/weldingtool/interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers)
if(!status && interacting_with.is_refillable())
reagents.trans_to(interacting_with, reagents.total_volume, transferred_by = user)
to_chat(user, span_notice("You empty [src]'s fuel tank into [interacting_with]."))
update_appearance()
return ITEM_INTERACT_SUCCESS
if(!ishuman(interacting_with))
return NONE
if(user.combat_mode)
return NONE
return try_heal_loop(interacting_with, user)
/obj/item/weldingtool/proc/light_updated(datum/source)
SIGNAL_HANDLER
fire_flicker_middleman(middleman)
/obj/item/weldingtool/proc/try_heal_loop(atom/interacting_with, mob/living/user, repeating = FALSE)
var/mob/living/carbon/human/attacked_humanoid = interacting_with
var/obj/item/bodypart/affecting = attacked_humanoid.get_bodypart(check_zone(user.zone_selected))
if(isnull(affecting) || !IS_ROBOTIC_LIMB(affecting))
return NONE
if (!affecting.brute_dam)
balloon_alert(user, "limb not damaged")
return ITEM_INTERACT_BLOCKING
user.visible_message(span_notice("[user] starts to fix some of the dents on [attacked_humanoid == user ? user.p_their() : "[attacked_humanoid]'s"] [affecting.name]."),
span_notice("You start fixing some of the dents on [attacked_humanoid == user ? "your" : "[attacked_humanoid]'s"] [affecting.name]."))
var/use_delay = repeating ? 1 SECONDS : 0
if(user == attacked_humanoid)
use_delay = 5 SECONDS
if(!use_tool(attacked_humanoid, user, use_delay, volume=50, amount=1))
return ITEM_INTERACT_BLOCKING
if (!attacked_humanoid.item_heal(user, brute_heal = 15, burn_heal = 0, heal_message_brute = "dents", heal_message_burn = "burnt wires", required_bodytype = BODYTYPE_ROBOTIC))
return ITEM_INTERACT_BLOCKING
INVOKE_ASYNC(src, PROC_REF(try_heal_loop), interacting_with, user, TRUE)
return ITEM_INTERACT_SUCCESS
/obj/item/weldingtool/afterattack(atom/target, mob/user, list/modifiers, list/attack_modifiers)
if(!isOn())
return
use(1)
var/turf/location = get_turf(user)
location.hotspot_expose(700, 50, 1)
if(QDELETED(target) || !isliving(target)) // can't ignite something that doesn't exist
return
var/mob/living/attacked_mob = target
if(attacked_mob.ignite_mob())
message_admins("[ADMIN_LOOKUPFLW(user)] set [key_name_admin(attacked_mob)] on fire with [src] at [AREACOORD(user)]")
user.log_message("set [key_name(attacked_mob)] on fire with [src].", LOG_ATTACK)
/obj/item/weldingtool/attack_self(mob/user)
if(reagents.spark_act(0, SPARK_ACT_ENCLOSED, banned_reagents = /datum/reagent/fuel) & SPARK_ACT_DESTRUCTIVE)
message_admins("[ADMIN_LOOKUPFLW(user)] activated a rigged welder at [AREACOORD(user)].")
user.log_message("activated a rigged welder", LOG_VICTIM)
qdel(src)
return
switched_on(user)
update_appearance()
/// Returns the amount of fuel in the welder
/obj/item/weldingtool/proc/get_fuel()
return reagents.get_reagent_amount(/datum/reagent/fuel) + reagents.get_reagent_amount(/datum/reagent/toxin/plasma)
/// Uses fuel from the welding tool.
/obj/item/weldingtool/use(used = 0)
if(!isOn() || !check_fuel())
return FALSE
if(used > 0)
burned_fuel_for = 0
if(get_fuel() >= used)
reagents.remove_reagent(/datum/reagent/fuel, used)
check_fuel()
return TRUE
else
return FALSE
/// Toggles the welding value.
/obj/item/weldingtool/proc/set_welding(new_value)
if(welding == new_value)
return
. = welding
welding = new_value
set_light_on(welding)
/// Turns off the welder if there is no more fuel (does this really need to be its own proc?)
/obj/item/weldingtool/proc/check_fuel(mob/user)
if(get_fuel() <= 0 && welding)
set_light_on(FALSE)
switched_on(user)
update_appearance()
return FALSE
return TRUE
// /Switches the welder on
/obj/item/weldingtool/proc/switched_on(mob/user)
if(!status)
balloon_alert(user, "unsecured!")
return
set_welding(!welding)
if(welding)
if(get_fuel() >= 1)
playsound(loc, activation_sound, 50, TRUE)
force = 15
damtype = BURN
hitsound = 'sound/items/tools/welder.ogg'
update_appearance()
START_PROCESSING(SSobj, src)
else
balloon_alert(user, "no fuel!")
switched_off(user)
else
playsound(loc, deactivation_sound, 50, TRUE)
switched_off(user)
/// Switches the welder off
/obj/item/weldingtool/proc/switched_off(mob/user)
set_welding(FALSE)
force = 3
damtype = BRUTE
hitsound = SFX_SWING_HIT
update_appearance()
/obj/item/weldingtool/examine(mob/user)
. = ..()
. += "It contains [get_fuel()] unit\s of fuel out of [max_fuel]."
/obj/item/weldingtool/get_temperature()
return welding * heat
/// Returns whether or not the welding tool is currently on.
/obj/item/weldingtool/proc/isOn()
return welding
/// If welding tool ran out of fuel during a construction task, construction fails.
/obj/item/weldingtool/tool_use_check(mob/living/user, amount, heat_required)
if(!isOn() || !check_fuel())
to_chat(user, span_warning("[src] has to be on to complete this task!"))
return FALSE
if(get_fuel() < amount)
to_chat(user, span_warning("You need more welding fuel to complete this task!"))
return FALSE
if(heat < heat_required)
to_chat(user, span_warning("[src] is not hot enough to complete this task!"))
return FALSE
return TRUE
/// Ran when the welder is attacked by a screwdriver.
/obj/item/weldingtool/proc/flamethrower_screwdriver(obj/item/tool, mob/user)
if(welding)
to_chat(user, span_warning("Turn it off first!"))
return
status = !status
if(status)
to_chat(user, span_notice("You resecure [src] and close the fuel tank."))
reagents.flags &= ~(OPENCONTAINER)
else
to_chat(user, span_notice("[src] can now be attached, modified, and refuelled."))
reagents.flags |= OPENCONTAINER
add_fingerprint(user)
/// First step of building a flamethrower (when a welder is attacked by rods)
/obj/item/weldingtool/proc/flamethrower_rods(obj/item/tool, mob/user)
if(!status)
var/obj/item/stack/rods/used_rods = tool
if (used_rods.use(1))
var/obj/item/flamethrower/flamethrower_frame = new /obj/item/flamethrower(user.loc)
if(!remove_item_from_storage(flamethrower_frame, user))
user.transferItemToLoc(src, flamethrower_frame, TRUE)
flamethrower_frame.weldtool = src
add_fingerprint(user)
to_chat(user, span_notice("You add a rod to a welder, starting to build a flamethrower."))
user.put_in_hands(flamethrower_frame)
else
to_chat(user, span_warning("You need one rod to start building a flamethrower!"))
/obj/item/weldingtool/ignition_effect(atom/ignitable_atom, mob/user)
if(use_tool(ignitable_atom, user, 0))
return span_rose("[user] casually lights [ignitable_atom] with [src], what a badass.")
else
return ""
/obj/item/weldingtool/empty
starting_fuel = FALSE
/obj/item/weldingtool/largetank
name = "industrial welding tool"
desc = "A slightly larger welder with a larger tank."
icon_state = "indwelder"
inhand_icon_state = "indwelder"
max_fuel = 40
custom_materials = list(/datum/material/glass=SMALL_MATERIAL_AMOUNT*0.6)
/obj/item/weldingtool/largetank/flamethrower_screwdriver()
return
/obj/item/weldingtool/largetank/empty
starting_fuel = FALSE
/obj/item/weldingtool/largetank/cyborg
name = "integrated welding tool"
desc = "An advanced welder designed to be used in robotic systems. Custom framework doubles the speed of welding."
icon = 'icons/obj/items_cyborg.dmi'
icon_state = "indwelder_cyborg"
toolspeed = 0.5
/obj/item/weldingtool/mini
name = "emergency welding tool"
desc = "A miniature welder used during emergencies."
icon_state = "miniwelder"
inhand_icon_state = "miniwelder"
max_fuel = 10
w_class = WEIGHT_CLASS_TINY
custom_materials = list(/datum/material/iron=SMALL_MATERIAL_AMOUNT*0.3, /datum/material/glass=SMALL_MATERIAL_AMOUNT*0.1)
change_icons = FALSE
/obj/item/weldingtool/mini/flamethrower_screwdriver()
return
/obj/item/weldingtool/mini/empty
starting_fuel = FALSE
/obj/item/weldingtool/abductor
name = "alien welding tool"
desc = "An alien welding tool. Whatever fuel it uses, it never runs out."
icon = 'icons/obj/antags/abductor.dmi'
icon_state = "welder"
inhand_icon_state = "abductorwelder"
toolspeed = 0.1
custom_materials = list(/datum/material/iron =SHEET_MATERIAL_AMOUNT * 2.5, /datum/material/silver = SHEET_MATERIAL_AMOUNT*1.25, /datum/material/plasma =SHEET_MATERIAL_AMOUNT * 2.5, /datum/material/titanium =SHEET_MATERIAL_AMOUNT, /datum/material/diamond =SHEET_MATERIAL_AMOUNT)
light_system = NO_LIGHT_SUPPORT
light_range = 0
change_icons = FALSE
/obj/item/weldingtool/abductor/process()
if(get_fuel() <= max_fuel)
reagents.add_reagent(/datum/reagent/fuel, 1)
..()
/obj/item/weldingtool/hugetank
name = "upgraded industrial welding tool"
desc = "An upgraded welder based of the industrial welder."
icon_state = "upindwelder"
inhand_icon_state = "upindwelder"
max_fuel = 80
custom_materials = list(/datum/material/iron=SMALL_MATERIAL_AMOUNT*0.7, /datum/material/glass=SMALL_MATERIAL_AMOUNT*1.2)
/obj/item/weldingtool/experimental
name = "experimental welding tool"
desc = "An experimental welder capable of self-fuel generation and less harmful to the eyes."
icon_state = "exwelder"
inhand_icon_state = "exwelder"
max_fuel = 40
custom_materials = list(/datum/material/iron =HALF_SHEET_MATERIAL_AMOUNT, /datum/material/glass = SMALL_MATERIAL_AMOUNT*5, /datum/material/plasma =HALF_SHEET_MATERIAL_AMOUNT*1.5, /datum/material/uranium =SMALL_MATERIAL_AMOUNT * 2)
change_icons = FALSE
can_off_process = TRUE
light_range = 1
w_class = WEIGHT_CLASS_NORMAL
toolspeed = 0.5
var/last_gen = 0
var/nextrefueltick = 0
/obj/item/weldingtool/experimental/process()
..()
if(get_fuel() < max_fuel && nextrefueltick < world.time)
nextrefueltick = world.time + 10
reagents.add_reagent(/datum/reagent/fuel, 1)