Files
VMSolidus 582d213b8d Some Misc Bugfixes (#22225)
This PR fixes a few bugs that were recently reported both by players and
by our new bug monitoring system. An AI agent (specifically Gemini 3)
was used to do some of the code diving process, while the fixes were
tested and verified myself manually. Yes I have actually verified the
fixes in this PR in testing.

The swap to Calculus methods for organ EMP was done by me, no AI. I love
Calculus to much to let a bot do it for me. In particular my fix to
organ EMP was to make it so that EMP damage is handled directly as
"number of fractional seconds the EMP will last", and that they tick
down cleanly per second. I did this because after fixing mechanical
organs not doing ANY EMP effects at all, I tested them and discovered
that the EMP effects were extremely underwhelming. They were like, "Your
screen only briefly filling with static for like 2 ticks", and "your
heart skips a single beat", which was lame and nonthreatening.
Significantly more fun is making the static last for N number of
seconds, and for the Heart to fail to pump for N number of seconds (very
life threatening!)

The new organ EMP handling vars are per-organ rather than a define, so
if we want there to be different EMP times for hearts vs eyes, that's
totally doable. Just make the heart have a larger recovery rate, or the
eyes have a smaller recovery rate.

---------

Signed-off-by: VMSolidus <evilexecutive@gmail.com>
Co-authored-by: Arrow768 <1331699+Arrow768@users.noreply.github.com>
2026-05-03 17:07:44 +00:00

234 lines
7.3 KiB
Plaintext

//Beam Datum and effect
/datum/beam
var/atom/origin = null
var/atom/target = null
var/list/elements = list()
var/icon/base_icon = null
var/icon
var/icon_state = "" //icon state of the main segments of the beam
var/max_distance = 0
var/curr_distance = 0
var/sleep_time = 3
var/finished = 0
var/turf/target_oldloc = null
var/turf/origin_oldloc = null
var/beam_type = /obj/effect/ebeam //must be subtype
var/timing_id = null
var/recalculating = FALSE
/datum/beam/New(beam_origin,beam_target,beam_icon='icons/effects/beam.dmi',beam_icon_state="b_beam",time=50,maxdistance=10,btype = /obj/effect/ebeam,beam_sleep_time=3)
origin = beam_origin
target = beam_target
var/turf/origin_turf = get_turf(origin)
var/turf/target_turf = get_turf(target)
max_distance = maxdistance
curr_distance = get_dist(origin_turf, target_turf)
if((curr_distance == -1 && origin_turf != target_turf) || curr_distance >= max_distance || origin_turf.z != target_turf.z)
qdel(src)
return
sleep_time = beam_sleep_time
base_icon = new(beam_icon,beam_icon_state)
icon = beam_icon
icon_state = beam_icon_state
beam_type = btype
if(time != -1)
addtimer(CALLBACK(src, PROC_REF(End), time))
/datum/beam/proc/Start()
recalculate()
/datum/beam/proc/recalculate()
if(QDELETED(src))
return
if(recalculating)
recalculate_in(sleep_time)
return
recalculating = TRUE
timing_id = null
var/turf/origin_turf = get_turf(origin)
var/turf/target_turf = get_turf(target)
if(!istype(origin_turf) || !istype(target_turf) || QDELETED(origin) || QDELETED(target))
End()
return
curr_distance = get_dist(origin_turf, target_turf)
if(!(curr_distance == -1 && origin_turf != target_turf) && curr_distance < max_distance && origin_turf.z == target_turf.z)
if((origin_turf != origin_oldloc || target_turf != target_oldloc))
origin_oldloc = origin_turf //so we don't keep checking against their initial positions, leading to endless Reset()+Draw() calls
target_oldloc = target_turf
Reset()
Draw()
after_calculate()
recalculating = FALSE
else
End()
/datum/beam/proc/afterDraw()
return
/datum/beam/proc/recalculate_in(time)
if(QDELETED(src))
return
timing_id = addtimer(CALLBACK(src, PROC_REF(recalculate)), time, TIMER_STOPPABLE | TIMER_UNIQUE | TIMER_NO_HASH_WAIT | TIMER_OVERRIDE)
/datum/beam/proc/after_calculate()
if(QDELETED(src) || (sleep_time == null) || finished) //Does not automatically recalculate.
return
timing_id = addtimer(CALLBACK(src, PROC_REF(recalculate)), sleep_time, TIMER_STOPPABLE | TIMER_UNIQUE | TIMER_NO_HASH_WAIT)
/datum/beam/proc/End(destroy_self = TRUE)
finished = TRUE
if(timing_id)
deltimer(timing_id)
if(!QDELING(src) && destroy_self)
qdel(src)
/datum/beam/proc/Reset()
QDEL_LIST(elements)
/datum/beam/Destroy()
if(timing_id)
deltimer(timing_id)
timing_id = null
Reset()
target = null
origin = null
target_oldloc = null
origin_oldloc = null
return ..()
/datum/beam/proc/Draw()
var/Angle = round(get_angle(origin.z ? origin : get_turf(origin), target.z ? target : get_turf(target)))
var/matrix/rot_matrix = matrix()
rot_matrix.Turn(Angle)
//Translation vector for origin and target
var/DX = get_x_translation_vector()
var/DY = get_y_translation_vector()
var/N = 0
var/length = round(sqrt((DX)**2+(DY)**2)) //hypotenuse of the triangle formed by target and origin's displacement
for(N in 0 to length-1 step world.icon_size)//-1 as we want < not <=, but we want the speed of X in Y to Z and step X
var/obj/effect/ebeam/segment = new beam_type(origin_oldloc)
segment.owner = src
elements += segment
//Assign icon, for main segments it's base_icon, for the end, it's icon+icon_state
//cropped by a transparent box of length-N pixel size
if(N + world.icon_size > length)
var/icon/II = new(icon, icon_state)
II.DrawBox(null, 1, (length-N), world.icon_size, world.icon_size)
segment.icon = II
else
segment.icon = base_icon
segment.transform = rot_matrix
//Calculate pixel offsets (If necessary)
var/x_offset = round(sin(Angle) * (N + world.icon_size/2))
var/y_offset = round(cos(Angle) * (N + world.icon_size/2))
//Position the effect so the beam is one continuous line
segment.x += SIMPLE_SIGN(x_offset) * FLOOR(abs(x_offset)/world.icon_size, 1)
x_offset %= world.icon_size
segment.y += SIMPLE_SIGN(y_offset) * FLOOR(abs(y_offset)/world.icon_size, 1)
y_offset %= world.icon_size
segment.pixel_x = x_offset
segment.pixel_y = y_offset
CHECK_TICK
afterDraw()
/datum/beam/proc/get_x_translation_vector()
return (world.icon_size*target.x+target.pixel_x)-(world.icon_size*origin.x+origin.pixel_x)
/datum/beam/proc/get_y_translation_vector()
return (world.icon_size*target.y+target.pixel_y)-(world.icon_size*origin.y+origin.pixel_y)
/datum/beam/exploration
var/obj/item/tethering_device/owner
/datum/beam/exploration/End()
owner.active_beams -= target
owner.untether(target, FALSE)
return ..()
/datum/beam/exploration/get_x_translation_vector()
return (world.icon_size * target_oldloc.x) - (world.icon_size * origin_oldloc.x)
/datum/beam/exploration/get_y_translation_vector()
return (world.icon_size * target_oldloc.y) - (world.icon_size * origin_oldloc.y)
/datum/beam/exploration/afterDraw()
var/distance = curr_distance / max_distance
var/set_color = COLOR_GREEN
if(distance >= 0.8)
set_color = COLOR_MAROON
else if(distance >= 0.3)
set_color = COLOR_BLUE
else
set_color = COLOR_GREEN
var/beam_index = 1
var/elements_length = length(elements)
var/half_elements = elements_length / 2
for(var/beam in elements)
var/obj/effect/ebeam/B = beam
B.color = set_color
if(beam_index > half_elements)
B.alpha = clamp(255 - (40 * (elements_length - beam_index)), 0, 255)
else
B.alpha = clamp(255 - (40 * (beam_index - 1)), 0, 255)
beam_index++
/datum/beam/power
var/obj/item/computer_hardware/tesla_link/charging_cable/owner
/datum/beam/power/End()
owner.beam = null
if(owner.source)
owner.untether(FALSE)
return ..()
/datum/beam/power/get_x_translation_vector()
return (world.icon_size * target_oldloc.x + target.pixel_x) - (world.icon_size * origin_oldloc.x + origin.pixel_x)
/datum/beam/power/get_y_translation_vector()
return (world.icon_size * target_oldloc.y + target.pixel_y) - (world.icon_size * origin_oldloc.y + origin.pixel_y)
/datum/beam/power/afterDraw()
for(var/beam in elements)
var/obj/effect/ebeam/B = beam
B.color = COLOR_GRAY40
// this simplified datum will work with timed beams that are held
/datum/beam/held/get_x_translation_vector()
return (world.icon_size * target_oldloc.x) - (world.icon_size * origin_oldloc.x)
/datum/beam/held/get_y_translation_vector()
return (world.icon_size * target_oldloc.y) - (world.icon_size * origin_oldloc.y)
/obj/effect/ebeam
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
anchored = 1
plane = ABOVE_LIGHTING_PLANE
layer = BEAM_PROJECTILE_LAYER
blend_mode = BLEND_ADD
var/datum/beam/owner
/obj/effect/ebeam/tesla_act()
return
/obj/effect/ebeam/Destroy()
owner = null
return ..()
/atom/proc/Beam(atom/BeamTarget,icon_state="b_beam",icon='icons/effects/beam.dmi',time=50, maxdistance=10,beam_type=/obj/effect/ebeam,beam_sleep_time = 3, beam_datum_type=/datum/beam)
if(time >= INFINITY)
crash_with("Tried to create beam with infinite time!")
return null
var/datum/beam/newbeam = new beam_datum_type(src,BeamTarget,icon,icon_state,time,maxdistance,beam_type,beam_sleep_time)
INVOKE_ASYNC(newbeam, TYPE_PROC_REF(/datum/beam, Start))
return newbeam