mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-01-17 12:35:26 +00:00
## About The Pull Request Fixes #79764 I was going to tackle this issue by slamming `TRAIT_NO_SLIP_ALL` on Atrocinator users and calling it a day, but like, that didn't feel proper. So I thought hey, we could just give them the flying movetype, even though they technically aren't flying it means they're unaffected by things that flying would make you unaffected by. Nope, this means the mob technically "negates gravity", so no falling and no feetsteps. Let's try floating - this give us feetsteps but no falling upwards. So instead of going back to square one, with `TRAIT_NO_SLIP_ALL`, I decided to go for the more complex route of just adding a movetype. Hence, move type `UPSIDE_DOWN`. This covers situations where a mob would be "floating" above the ground, but still walking. ...Negative gravity. This means overall the Atrociator acts more as you'd expect - you don't slip on ice, you don't trigger bear traps or mouse traps, you can walk over railings, unaffected by conveyor belts, etc. ## Why It's Good For The Game Makes the Atrocinator a lot more consistent with how you'd expect for it to work. Admittedly it is a bit niche use of movetypes, but it can possibly be expanded to more things in the future, who knows? I applied it to mobs on meat spikes (even though they don't move), just for proof of concept. ## Changelog 🆑 Melbert fix: Atrocinating mobs will now behave more as you'd expect. Meaning they don't slip on wet patches, can't trigger bear traps / landmines / mouse traps, ignore conveyors, and can walk over tables and railings. fix: Floating mobs are unaffected by conveyor belts, acid (on the ground), glass tables fix: Floating mobs won't squish stuff like roaches anymore fix: Fixes bear traps triggering on floating / flying mobs /🆑 --------- Co-authored-by: Ghom <42542238+Ghommie@users.noreply.github.com>
414 lines
19 KiB
Plaintext
414 lines
19 KiB
Plaintext
/**
|
|
* A visual element that makes movables entering the attached turfs look immersed into that turf.
|
|
*
|
|
* Abandon all hope, ye who read forth, for this immerse works on mind-numbing workarounds,
|
|
*/
|
|
/datum/element/immerse
|
|
element_flags = ELEMENT_DETACH_ON_HOST_DESTROY | ELEMENT_BESPOKE
|
|
argument_hash_start_idx = 2
|
|
///An association list of turfs that have this element attached and their affected contents.
|
|
var/list/attached_turfs_and_movables = list()
|
|
|
|
/**
|
|
* A list of movables that shouldn't be affected by the element, either because it'd look bad
|
|
* or barely perceptible.
|
|
*/
|
|
var/static/list/movables_to_ignore
|
|
///A list of icons generated from a target and a mask, later used as appearances for the overlays.
|
|
var/static/list/generated_immerse_icons = list()
|
|
///A list of instances of /atom/movable/immerse_overlay then used as visual overlays for the immersed movables.
|
|
var/list/generated_visual_overlays = list()
|
|
///An association list of movables as key and overlays as assoc.
|
|
var/list/immersed_movables
|
|
|
|
var/icon
|
|
var/icon_state
|
|
var/mask_icon
|
|
var/color
|
|
var/alpha
|
|
|
|
/datum/element/immerse/Attach(turf/target, icon, icon_state, mask_icon, color = "#777777", alpha = 180)
|
|
. = ..()
|
|
if(!isturf(target) || !icon || !icon_state || !mask_icon)
|
|
return ELEMENT_INCOMPATIBLE
|
|
|
|
if(!movables_to_ignore)
|
|
movables_to_ignore = typecacheof(list(
|
|
/obj/effect,
|
|
/mob/dead,
|
|
/obj/projectile,
|
|
))
|
|
|
|
movables_to_ignore += GLOB.WALLITEMS_INTERIOR
|
|
movables_to_ignore += GLOB.WALLITEMS_EXTERIOR
|
|
|
|
src.icon = icon
|
|
src.icon_state = icon_state
|
|
src.color = color
|
|
src.alpha = alpha
|
|
src.mask_icon = mask_icon
|
|
|
|
/**
|
|
* Hello, you may be wondering why we're blending icons and not simply
|
|
* overlaying one mutable appearance with the blend multiply on another.
|
|
* Well, the latter option doesn't work as neatly when added
|
|
* to an atom with the KEEP_TOGETHER appearance flag, with the mask icon also
|
|
* showing on said atom, while we don't want it to.
|
|
*
|
|
* Also using KEEP_APART isn't an option, because unless it's drawn as one with
|
|
* its visual loation, the whole plane the atom belongs to will count as part of the
|
|
* mask of the final visual overlay since that's how the BLEND_INSET_OVERLAY blend mode works here.
|
|
* In layman terms, with KEEP_APART on, if a flying monkey gets nears an immersed
|
|
* human, the visual overlay will appear on the flying monkey even if it shouldn't.
|
|
*/
|
|
var/icon/immerse_icon = generated_immerse_icons["[icon]-[icon_state]-[mask_icon]"]
|
|
if(!immerse_icon)
|
|
immerse_icon = icon(icon, icon_state)
|
|
var/icon/sub_mask = icon('icons/effects/effects.dmi', mask_icon)
|
|
immerse_icon.Blend(sub_mask, ICON_MULTIPLY)
|
|
immerse_icon = fcopy_rsc(immerse_icon)
|
|
generated_immerse_icons["[icon]-[icon_state]-[mask_icon]"] = immerse_icon
|
|
|
|
RegisterSignal(target, SIGNAL_ADDTRAIT(TRAIT_IMMERSE_STOPPED), PROC_REF(stop_immersion))
|
|
RegisterSignal(target, SIGNAL_REMOVETRAIT(TRAIT_IMMERSE_STOPPED), PROC_REF(start_immersion))
|
|
|
|
if(!HAS_TRAIT(target, TRAIT_IMMERSE_STOPPED))
|
|
start_immersion(target)
|
|
|
|
/datum/element/immerse/Detach(turf/source)
|
|
UnregisterSignal(source, list(SIGNAL_ADDTRAIT(TRAIT_IMMERSE_STOPPED), SIGNAL_REMOVETRAIT(TRAIT_IMMERSE_STOPPED)))
|
|
if(!HAS_TRAIT(source, TRAIT_IMMERSE_STOPPED))
|
|
stop_immersion(source)
|
|
return ..()
|
|
|
|
///Makes the element start affecting the turf and its contents. Called on Attach() or when TRAIT_IMMERSE_STOPPED is removed.
|
|
/datum/element/immerse/proc/start_immersion(turf/source)
|
|
SIGNAL_HANDLER
|
|
RegisterSignals(source, list(COMSIG_ATOM_ABSTRACT_ENTERED, COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZED_ON), PROC_REF(on_init_or_entered))
|
|
RegisterSignal(source, COMSIG_ATOM_ABSTRACT_EXITED, PROC_REF(on_atom_exited))
|
|
attached_turfs_and_movables += source
|
|
for(var/atom/movable/movable as anything in source)
|
|
if(!(movable.flags_1 & INITIALIZED_1))
|
|
continue
|
|
on_init_or_entered(source, movable)
|
|
|
|
///Stops the element from affecting on the turf and its contents. Called on Detach() or when TRAIT_IMMERSE_STOPPED is added.
|
|
/datum/element/immerse/proc/stop_immersion(turf/source)
|
|
SIGNAL_HANDLER
|
|
UnregisterSignal(source, list(COMSIG_ATOM_ABSTRACT_ENTERED, COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZED_ON, COMSIG_ATOM_ABSTRACT_EXITED))
|
|
for(var/atom/movable/movable as anything in attached_turfs_and_movables[source])
|
|
remove_from_element(source, movable)
|
|
attached_turfs_and_movables -= source
|
|
|
|
/**
|
|
* If the movable is within the right layers and planes, not in the list of movable types to ignore,
|
|
* or already affected by the element for that matter, Signals will be registered and,
|
|
* unless the movable (or whatever it's buckled to) is flying, it'll appear as if immersed in that water.
|
|
*/
|
|
/datum/element/immerse/proc/on_init_or_entered(turf/source, atom/movable/movable)
|
|
SIGNAL_HANDLER
|
|
if(HAS_TRAIT(movable, TRAIT_IMMERSED))
|
|
return
|
|
if(movable.layer >= ABOVE_ALL_MOB_LAYER || !ISINRANGE(movable.plane, MUTATE_PLANE(FLOOR_PLANE, source), MUTATE_PLANE(GAME_PLANE_UPPER_FOV_HIDDEN, source)))
|
|
return
|
|
if(is_type_in_typecache(movable, movables_to_ignore))
|
|
return
|
|
|
|
var/atom/movable/buckled
|
|
if(isliving(movable))
|
|
var/mob/living/living_mob = movable
|
|
RegisterSignal(living_mob, COMSIG_LIVING_SET_BUCKLED, PROC_REF(on_set_buckled))
|
|
buckled = living_mob.buckled
|
|
|
|
try_immerse(movable, buckled)
|
|
RegisterSignal(movable, COMSIG_QDELETING, PROC_REF(on_movable_qdel))
|
|
LAZYADD(attached_turfs_and_movables[source], movable)
|
|
ADD_TRAIT(movable, TRAIT_IMMERSED, ELEMENT_TRAIT(src))
|
|
|
|
/datum/element/immerse/proc/on_movable_qdel(atom/movable/source)
|
|
SIGNAL_HANDLER
|
|
remove_from_element(source.loc, source)
|
|
|
|
/**
|
|
* The main proc, which adds a visual overlay to the movable that has entered the turf to make it look immersed.
|
|
* It's kind of iffy but basically, we want the overlay to cover as much area as needed to
|
|
* avoid the movable's icon from spilling horizontally or below.
|
|
* Also, while these visual overlays are mainly cached movables, for certain movables, such as living mobs,
|
|
* we want them to have their own unique vis overlay with additional signals registered.
|
|
* This allows the vis overlay to look more or less unchanged while its owner is spinning or resting
|
|
* without otherwise affecting other movables with identical overlays.
|
|
*/
|
|
/datum/element/immerse/proc/add_immerse_overlay(atom/movable/movable)
|
|
var/list/icon_dimensions = get_icon_dimensions(movable.icon)
|
|
var/width = icon_dimensions["width"] || world.icon_size
|
|
var/height = icon_dimensions["height"] || world.icon_size
|
|
|
|
var/is_below_water = movable.layer < WATER_LEVEL_LAYER ? "underwater-" : ""
|
|
|
|
var/atom/movable/immerse_overlay/vis_overlay = generated_visual_overlays["[is_below_water][width]x[height]"]
|
|
|
|
if(!vis_overlay) //create the overlay if not already done.
|
|
vis_overlay = generate_vis_overlay(width, height, is_below_water)
|
|
|
|
|
|
ADD_KEEP_TOGETHER(movable, ELEMENT_TRAIT(src))
|
|
|
|
/**
|
|
* Let's give an unique immerse visual only to those movables that would
|
|
* benefit from this the most, for the sake of a smidge of lightweightness.
|
|
*/
|
|
if(HAS_TRAIT(movable, TRAIT_UNIQUE_IMMERSE))
|
|
var/atom/movable/immerse_overlay/original_vis_overlay = vis_overlay
|
|
vis_overlay = new(null)
|
|
vis_overlay.appearance = original_vis_overlay
|
|
vis_overlay.extra_width = original_vis_overlay.extra_width
|
|
vis_overlay.extra_height = original_vis_overlay.extra_height
|
|
vis_overlay.overlay_appearance = original_vis_overlay.overlay_appearance
|
|
SEND_SIGNAL(movable, COMSIG_MOVABLE_EDIT_UNIQUE_IMMERSE_OVERLAY, vis_overlay)
|
|
RegisterSignal(movable, COMSIG_ATOM_SPIN_ANIMATION, PROC_REF(on_spin_animation))
|
|
RegisterSignal(movable, COMSIG_LIVING_POST_UPDATE_TRANSFORM, PROC_REF(on_update_transform))
|
|
|
|
movable.vis_contents |= vis_overlay
|
|
|
|
LAZYSET(immersed_movables, movable, vis_overlay)
|
|
|
|
///Initializes and caches a new visual overlay given parameters such as width, height and whether it should appear fully underwater.
|
|
/datum/element/immerse/proc/generate_vis_overlay(width, height, is_below_water)
|
|
|
|
var/atom/movable/immerse_overlay/vis_overlay = new(null, src)
|
|
|
|
/**
|
|
* vis contents spin around the center of the icon of their vis locs
|
|
* but since we want the appearance to stay where it should be,
|
|
* we have to counteract this one.
|
|
*/
|
|
var/extra_width = (width - world.icon_size) * 0.5
|
|
var/extra_height = (height - world.icon_size) * 0.5
|
|
var/mutable_appearance/overlay_appearance = new()
|
|
var/icon/immerse_icon = generated_immerse_icons["[icon]-[icon_state]-[mask_icon]"]
|
|
var/last_i = width/world.icon_size
|
|
for(var/i in -1 to last_i)
|
|
var/mutable_appearance/underwater = mutable_appearance(icon, icon_state)
|
|
underwater.pixel_x = world.icon_size * i - extra_width
|
|
underwater.pixel_y = -world.icon_size - extra_height
|
|
overlay_appearance.overlays += underwater
|
|
|
|
var/mutable_appearance/water_level = is_below_water ? underwater : mutable_appearance(immerse_icon)
|
|
water_level.pixel_x = world.icon_size * i - extra_width
|
|
water_level.pixel_y = -extra_height
|
|
overlay_appearance.overlays += water_level
|
|
|
|
|
|
vis_overlay.color = color
|
|
vis_overlay.alpha = alpha
|
|
vis_overlay.overlays = list(overlay_appearance)
|
|
|
|
vis_overlay.extra_width = extra_width
|
|
vis_overlay.extra_height = extra_height
|
|
vis_overlay.overlay_appearance = overlay_appearance
|
|
|
|
generated_visual_overlays["[is_below_water][width]x[height]"] = vis_overlay
|
|
return vis_overlay
|
|
|
|
///This proc removes the vis_overlay, the keep together trait and some signals from the movable.
|
|
/datum/element/immerse/proc/remove_immerse_overlay(atom/movable/movable)
|
|
var/atom/movable/immerse_overlay/vis_overlay = LAZYACCESS(immersed_movables, movable)
|
|
if(!vis_overlay)
|
|
return
|
|
movable.vis_contents -= vis_overlay
|
|
LAZYREMOVE(immersed_movables, movable)
|
|
if(HAS_TRAIT(movable, TRAIT_UNIQUE_IMMERSE))
|
|
UnregisterSignal(movable, list(COMSIG_ATOM_SPIN_ANIMATION, COMSIG_LIVING_POST_UPDATE_TRANSFORM))
|
|
qdel(vis_overlay)
|
|
REMOVE_KEEP_TOGETHER(movable, ELEMENT_TRAIT(src))
|
|
|
|
/**
|
|
* Called by init_or_entered() and on_set_buckled().
|
|
* This applies the overlay if neither the movable or whatever is buckled to (exclusive to living mobs) are flying
|
|
* as well as movetype signals when the movable isn't buckled.
|
|
*/
|
|
/datum/element/immerse/proc/try_immerse(atom/movable/movable, atom/movable/buckled)
|
|
var/atom/movable/to_check = buckled || movable
|
|
if(!(to_check.movement_type & MOVETYPES_NOT_TOUCHING_GROUND) && !movable.throwing)
|
|
add_immerse_overlay(movable)
|
|
if(!buckled)
|
|
RegisterSignal(movable, COMSIG_MOVETYPE_FLAG_ENABLED, PROC_REF(on_move_flag_enabled))
|
|
RegisterSignal(movable, COMSIG_MOVETYPE_FLAG_DISABLED, PROC_REF(on_move_flag_disabled))
|
|
RegisterSignal(movable, COMSIG_MOVABLE_POST_THROW, PROC_REF(on_throw))
|
|
RegisterSignal(movable, COMSIG_MOVABLE_THROW_LANDED, PROC_REF(on_throw_landed))
|
|
|
|
/**
|
|
* Called by on_set_buckled() and remove_from_element().
|
|
* This removes the filter and signals from the movable unless it doesn't have them.
|
|
*/
|
|
/datum/element/immerse/proc/try_unimmerse(atom/movable/movable, atom/movable/buckled)
|
|
var/atom/movable/to_check = buckled || movable
|
|
if(!(to_check.movement_type & MOVETYPES_NOT_TOUCHING_GROUND) && !movable.throwing)
|
|
remove_immerse_overlay(movable)
|
|
if(!buckled)
|
|
UnregisterSignal(movable, list(COMSIG_MOVETYPE_FLAG_ENABLED, COMSIG_MOVETYPE_FLAG_DISABLED, COMSIG_MOVABLE_POST_THROW, COMSIG_MOVABLE_THROW_LANDED))
|
|
|
|
/datum/element/immerse/proc/on_set_buckled(mob/living/source, atom/movable/new_buckled)
|
|
SIGNAL_HANDLER
|
|
try_unimmerse(source, source.buckled)
|
|
try_immerse(source, new_buckled)
|
|
|
|
///Removes the overlay from mob and bucklees is flying.
|
|
/datum/element/immerse/proc/on_move_flag_enabled(atom/movable/source, flag, old_movement_type)
|
|
SIGNAL_HANDLER
|
|
if(!(flag & MOVETYPES_NOT_TOUCHING_GROUND) || (old_movement_type & MOVETYPES_NOT_TOUCHING_GROUND) || source.throwing)
|
|
return
|
|
remove_immerse_overlay(source)
|
|
for(var/mob/living/buckled_mob as anything in source.buckled_mobs)
|
|
remove_immerse_overlay(buckled_mob)
|
|
|
|
///Works just like on_move_flag_enabled, except it only has to check that movable isn't flying
|
|
/datum/element/immerse/proc/on_throw(atom/movable/source)
|
|
SIGNAL_HANDLER
|
|
if(source.movement_type & MOVETYPES_NOT_TOUCHING_GROUND)
|
|
return
|
|
remove_immerse_overlay(source)
|
|
for(var/mob/living/buckled_mob as anything in source.buckled_mobs)
|
|
remove_immerse_overlay(buckled_mob)
|
|
|
|
///Readds the overlay to the mob and bucklees if no longer flying.
|
|
/datum/element/immerse/proc/on_move_flag_disabled(atom/movable/source, flag, old_movement_type)
|
|
SIGNAL_HANDLER
|
|
if(!(flag & MOVETYPES_NOT_TOUCHING_GROUND) || (source.movement_type & MOVETYPES_NOT_TOUCHING_GROUND) || source.throwing)
|
|
return
|
|
add_immerse_overlay(source)
|
|
for(var/mob/living/buckled_mob as anything in source.buckled_mobs)
|
|
add_immerse_overlay(buckled_mob)
|
|
|
|
///Works just like on_move_flag_disabled, except it only has to check that movable isn't flying
|
|
/datum/element/immerse/proc/on_throw_landed(atom/movable/source)
|
|
SIGNAL_HANDLER
|
|
if(source.movement_type & MOVETYPES_NOT_TOUCHING_GROUND)
|
|
return
|
|
add_immerse_overlay(source)
|
|
for(var/mob/living/buckled_mob as anything in source.buckled_mobs)
|
|
add_immerse_overlay(buckled_mob)
|
|
|
|
/**
|
|
* Called when a movable exits the turf. If its new location is not in the list of turfs with this element,
|
|
* Remove the movable from the element.
|
|
*/
|
|
/datum/element/immerse/proc/on_atom_exited(turf/source, atom/movable/exited, direction)
|
|
SIGNAL_HANDLER
|
|
if(!(exited.loc in attached_turfs_and_movables))
|
|
remove_from_element(source, exited)
|
|
else
|
|
LAZYREMOVE(attached_turfs_and_movables[source], exited)
|
|
LAZYADD(attached_turfs_and_movables[exited.loc], exited)
|
|
|
|
///Remove any signal, overlay, trait given to the movable and reference to it within the element.
|
|
/datum/element/immerse/proc/remove_from_element(turf/source, atom/movable/movable)
|
|
var/atom/movable/buckled
|
|
if(isliving(movable))
|
|
var/mob/living/living_mob = movable
|
|
buckled = living_mob.buckled
|
|
try_unimmerse(movable, buckled)
|
|
|
|
UnregisterSignal(movable, list(COMSIG_LIVING_SET_BUCKLED, COMSIG_QDELETING))
|
|
REMOVE_TRAIT(movable, TRAIT_IMMERSED, ELEMENT_TRAIT(src))
|
|
LAZYREMOVE(attached_turfs_and_movables[source], movable)
|
|
|
|
/// A band-aid to keep the (unique) visual overlay from scaling and rotating along with its owner. I'm sorry.
|
|
/datum/element/immerse/proc/on_update_transform(mob/living/source, resize, new_lying_angle, is_opposite_angle)
|
|
SIGNAL_HANDLER
|
|
var/matrix/new_transform = matrix()
|
|
new_transform.Scale(1/source.current_size)
|
|
new_transform.Turn(-new_lying_angle)
|
|
|
|
var/atom/movable/immerse_overlay/vis_overlay = immersed_movables[source]
|
|
if(is_opposite_angle)
|
|
vis_overlay.transform = new_transform
|
|
vis_overlay.adjust_living_overlay_offset(source)
|
|
return
|
|
|
|
/**
|
|
* Here, we temporarily switch from the offset of the mutable appearance to one for movable used as visual overlay.
|
|
* Why? While visual overlays can be animated, their fixed point stays at the center of the icon of the atom
|
|
* they're attached to and not theirs, which can make manipulating the transform var a pain, but because
|
|
* we cannot do that with normal overlay or filters (reliably), we have to bend a knee and try to compensate it.
|
|
*/
|
|
vis_overlay.overlays = list(vis_overlay.overlay_appearance)
|
|
|
|
/// Oh, yeah, didn't I mention turning a visual overlay affects its pixel x/y/w/z too? Yeah, it sucks.
|
|
var/new_x = vis_overlay.extra_width
|
|
var/new_y = vis_overlay.extra_height
|
|
var/old_div = source.current_size/resize
|
|
var/offset_lying = source.rotate_on_lying ? PIXEL_Y_OFFSET_LYING : source.get_pixel_y_offset_standing(source.current_size/resize)
|
|
switch(source.lying_prev)
|
|
if(270)
|
|
vis_overlay.pixel_x += -offset_lying / old_div
|
|
if(90)
|
|
vis_overlay.pixel_x += offset_lying / old_div
|
|
if(0)
|
|
vis_overlay.pixel_y += -source.get_pixel_y_offset_standing(source.current_size/resize) / old_div
|
|
|
|
switch(new_lying_angle)
|
|
if(270)
|
|
new_x += -source.body_position_pixel_y_offset / source.current_size
|
|
if(90)
|
|
new_x += source.body_position_pixel_y_offset / source.current_size
|
|
if(0)
|
|
new_y += -source.body_position_pixel_y_offset / source.current_size
|
|
|
|
animate(vis_overlay, transform = new_transform, pixel_x = new_x, pixel_y = new_y, time = UPDATE_TRANSFORM_ANIMATION_TIME, easing = (EASE_IN|EASE_OUT))
|
|
addtimer(CALLBACK(vis_overlay, TYPE_PROC_REF(/atom/movable/immerse_overlay, adjust_living_overlay_offset), source), UPDATE_TRANSFORM_ANIMATION_TIME)
|
|
|
|
///Spin the overlay in the opposite direction so it doesn't look like it's spinning at all.
|
|
/datum/element/immerse/proc/on_spin_animation(atom/source, speed, loops, segments, segment)
|
|
SIGNAL_HANDLER
|
|
var/atom/movable/immerse_overlay/vis_overlay = immersed_movables[source]
|
|
vis_overlay.do_spin_animation(speed, loops, segments, -segment)
|
|
|
|
///We need to make sure to remove hard refs from the element when deleted.
|
|
/datum/element/immerse/proc/clear_overlay_refs(atom/movable/immerse_overlay/source)
|
|
//Assume that every vis loc is also in the immersed_movables list
|
|
for(var/atom/movable/vis_loc as anything in source.vis_locs)
|
|
remove_from_element(vis_loc.loc, vis_loc)
|
|
LAZYREMOVE(generated_visual_overlays, source)
|
|
source.overlay_appearance = null
|
|
|
|
///The not-quite-perfect movable used by the immerse element for its nefarious deeds.
|
|
/atom/movable/immerse_overlay
|
|
appearance_flags = RESET_TRANSFORM|RESET_COLOR|RESET_ALPHA|KEEP_TOGETHER
|
|
vis_flags = VIS_INHERIT_PLANE|VIS_INHERIT_ID
|
|
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
|
|
blend_mode = BLEND_INSET_OVERLAY
|
|
layer = WATER_VISUAL_OVERLAY_LAYER
|
|
plane = FLOAT_PLANE
|
|
/**
|
|
* The actual overlay used to make the mob look like it's half-covered in water.
|
|
*
|
|
* For visual overlays, pixel y/x/w/z are amplified by the a, b, d, e variables
|
|
* of the transform matrix of the movable they're attached to.
|
|
* For example, if a mob is twice its normal size (a = 2, e = 2),
|
|
* offsetting the movable used as visual overlay by 4 pixels to the right will result
|
|
* in the visual overlay moving 8 pixels to the right.
|
|
*
|
|
* This however, doesn't extend to the overlays of our visual overlay. which is why there's
|
|
* a mutable appearance variable that we use for those pixel offsets that really shouldn't be affected
|
|
* by the transform of our vis loc(s) in the first place.
|
|
*/
|
|
var/mutable_appearance/overlay_appearance
|
|
///The base pixel x offset of this movable
|
|
var/extra_width = 0
|
|
///The base pixel y offset of this movable
|
|
var/extra_height = 0
|
|
|
|
/atom/movable/immerse_overlay/Initialize(mapload, datum/element/immerse/element)
|
|
. = ..()
|
|
verbs.Cut() //"Cargo cultttttt" or something. Either way, they're better off without verbs.
|
|
element?.RegisterSignal(src, COMSIG_QDELETING, TYPE_PROC_REF(/datum/element/immerse, clear_overlay_refs))
|
|
|
|
///Called by COMSIG_MOVABLE_EDIT_UNIQUE_IMMERSE_OVERLAY for living mobs and a few procs from the immerse element.
|
|
/atom/movable/immerse_overlay/proc/adjust_living_overlay_offset(mob/living/source)
|
|
pixel_x = extra_width
|
|
pixel_y = extra_height
|
|
overlay_appearance.pixel_y = -source.body_position_pixel_y_offset
|
|
overlays = list(overlay_appearance)
|
|
overlay_appearance.pixel_y = 0
|