mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-22 04:30:44 +01:00
Merge branch 'master' of https://github.com/tgstation/tgstation into upstream-sync
This commit is contained in:
@@ -70,7 +70,7 @@
|
||||
/datum/element/art/commoner/apply_moodlet(atom/source, mob/living/user, impress)
|
||||
var/msg
|
||||
var/list/haters = list()
|
||||
for(var/hater_department_type as anything in list(/datum/job_department/security, /datum/job_department/command))
|
||||
for(var/hater_department_type in list(/datum/job_department/security, /datum/job_department/command))
|
||||
var/datum/job_department/hater_department = SSjob.get_department_type(hater_department_type)
|
||||
for(var/datum/job/hater_job as anything in hater_department.department_jobs)
|
||||
haters += hater_job.title
|
||||
|
||||
+158
-263
@@ -5,61 +5,31 @@ GLOBAL_LIST_INIT(immerse_ignored_movable, typecacheof(list(
|
||||
/obj/projectile,
|
||||
)))
|
||||
|
||||
/**
|
||||
* 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,
|
||||
*/
|
||||
/// A visual element that makes movables entering the attached turfs look immersed into that turf.
|
||||
/// May the gods forgive me for the bullshit you're about to witness
|
||||
/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 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.
|
||||
/// An association list of turfs that have this element attached and their affected contents.
|
||||
var/list/attached_turf_contents = list()
|
||||
|
||||
/// A list of generated immersion masks based on object width, height and whever they're fully immersed underwater
|
||||
var/list/immersion_masks = list()
|
||||
/// An assoc list of instances of /atom/movable/immerse_mask used as abstract effect relays, because god is dead
|
||||
var/list/generated_visual_overlays = list()
|
||||
///An association list of movables as key and overlays as assoc.
|
||||
var/list/immersed_movables
|
||||
/// icon_state used as a mask by our turf
|
||||
var/mask_icon = "immerse"
|
||||
/// Alpha of the mask, to make the liquid partially transparent
|
||||
var/alpha = 180
|
||||
|
||||
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)
|
||||
/datum/element/immerse/Attach(turf/target, mask_icon = "immerse", alpha = 180)
|
||||
. = ..()
|
||||
if(!isturf(target) || !icon || !icon_state || !mask_icon)
|
||||
if(!isturf(target) || !mask_icon)
|
||||
return ELEMENT_INCOMPATIBLE
|
||||
|
||||
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
|
||||
src.alpha = alpha
|
||||
|
||||
RegisterSignal(target, SIGNAL_ADDTRAIT(TRAIT_IMMERSE_STOPPED), PROC_REF(stop_immersion))
|
||||
RegisterSignal(target, SIGNAL_REMOVETRAIT(TRAIT_IMMERSE_STOPPED), PROC_REF(start_immersion))
|
||||
@@ -73,29 +43,30 @@ GLOBAL_LIST_INIT(immerse_ignored_movable, typecacheof(list(
|
||||
stop_immersion(source)
|
||||
return ..()
|
||||
|
||||
///Makes the element start affecting the turf and its contents. Called on Attach() or when TRAIT_IMMERSE_STOPPED is removed.
|
||||
|
||||
/// 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
|
||||
attached_turf_contents += source
|
||||
for(var/atom/movable/movable as anything in source)
|
||||
if(!(movable.flags_1 & INITIALIZED_1) || movable.invisibility >= INVISIBILITY_OBSERVER)
|
||||
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.
|
||||
/// 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])
|
||||
for(var/atom/movable/movable as anything in attached_turf_contents[source])
|
||||
remove_from_element(source, movable)
|
||||
attached_turfs_and_movables -= source
|
||||
attached_turf_contents -= 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.
|
||||
* or already affected by the element for that matter, signals will be registered and,
|
||||
* unless the movable 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
|
||||
@@ -103,123 +74,34 @@ GLOBAL_LIST_INIT(immerse_ignored_movable, typecacheof(list(
|
||||
return
|
||||
if(HAS_TRAIT(movable, TRAIT_IMMERSED) || HAS_TRAIT(movable, TRAIT_WALLMOUNTED))
|
||||
return
|
||||
if(!ISINRANGE(movable.plane, MUTATE_PLANE(FLOOR_PLANE, source), MUTATE_PLANE(GAME_PLANE, source)))
|
||||
if(!ISINRANGE(PLANE_TO_TRUE(movable.plane), FLOOR_PLANE, GAME_PLANE))
|
||||
return
|
||||
|
||||
// First, floor plane objects use TOPDOWN_LAYER, second this check shouldn't apply to them anyway.
|
||||
var/layer_to_check = IS_TOPDOWN_PLANE(source.plane) ? TOPDOWN_ABOVE_WATER_LAYER : ABOVE_ALL_MOB_LAYER
|
||||
//First, floor plane objects use TOPDOWN_LAYER, second this check shouldn't apply to them anyway.
|
||||
if(movable.layer >= layer_to_check)
|
||||
return
|
||||
if(is_type_in_typecache(movable, GLOB.immerse_ignored_movable))
|
||||
return
|
||||
|
||||
var/atom/movable/buckled
|
||||
var/atom/movable/buckled = null
|
||||
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
|
||||
RegisterSignal(living_mob, COMSIG_LIVING_SET_BUCKLED, PROC_REF(on_set_buckled))
|
||||
RegisterSignal(living_mob, COMSIG_LIVING_UPDATE_OFFSETS, PROC_REF(on_update_offsets))
|
||||
RegisterSignal(movable, COMSIG_LIVING_POST_UPDATE_TRANSFORM, PROC_REF(on_update_transform))
|
||||
|
||||
try_immerse(movable, buckled)
|
||||
RegisterSignal(movable, COMSIG_ATOM_SPIN_ANIMATION, PROC_REF(on_spin_animation))
|
||||
RegisterSignal(movable, COMSIG_QDELETING, PROC_REF(on_movable_qdel))
|
||||
LAZYADD(attached_turfs_and_movables[source], movable)
|
||||
try_immerse(movable, buckled)
|
||||
LAZYADD(attached_turf_contents[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"] || ICON_SIZE_X
|
||||
var/height = icon_dimensions["height"] || ICON_SIZE_Y
|
||||
|
||||
///This determines if the overlay should cover the entire surface of the object or not
|
||||
var/layer_to_check = IS_TOPDOWN_PLANE(movable.plane) ? TOPDOWN_WATER_LEVEL_LAYER : WATER_LEVEL_LAYER
|
||||
var/is_below_water = (movable.layer < layer_to_check) ? "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 - ICON_SIZE_X) * 0.5
|
||||
var/extra_height = (height - ICON_SIZE_Y) * 0.5
|
||||
var/mutable_appearance/overlay_appearance = new()
|
||||
var/icon/immerse_icon = generated_immerse_icons["[icon]-[icon_state]-[mask_icon]"]
|
||||
var/last_i = width/ICON_SIZE_X
|
||||
for(var/i in -1 to last_i)
|
||||
var/mutable_appearance/underwater = mutable_appearance(icon, icon_state)
|
||||
underwater.pixel_w = ICON_SIZE_X * i - extra_width
|
||||
underwater.pixel_z = -ICON_SIZE_Y - extra_height
|
||||
overlay_appearance.overlays += underwater
|
||||
|
||||
var/mutable_appearance/water_level = is_below_water ? underwater : mutable_appearance(immerse_icon)
|
||||
water_level.pixel_w = ICON_SIZE_X * i - extra_width
|
||||
water_level.pixel_z = -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)
|
||||
LAZYREMOVE(immersed_movables, movable)
|
||||
REMOVE_KEEP_TOGETHER(movable, ELEMENT_TRAIT(src))
|
||||
movable.vis_contents -= vis_overlay
|
||||
if(HAS_TRAIT(movable, TRAIT_UNIQUE_IMMERSE))
|
||||
UnregisterSignal(movable, list(COMSIG_ATOM_SPIN_ANIMATION, COMSIG_LIVING_POST_UPDATE_TRANSFORM, COMSIG_QDELETING))
|
||||
if(!QDELETED(vis_overlay))
|
||||
qdel(vis_overlay)
|
||||
/**
|
||||
* 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
|
||||
@@ -229,29 +111,34 @@ GLOBAL_LIST_INIT(immerse_ignored_movable, typecacheof(list(
|
||||
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))
|
||||
if(buckled)
|
||||
return
|
||||
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.
|
||||
*/
|
||||
/// 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))
|
||||
if(buckled)
|
||||
return
|
||||
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.
|
||||
/// 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)
|
||||
@@ -260,7 +147,7 @@ GLOBAL_LIST_INIT(immerse_ignored_movable, typecacheof(list(
|
||||
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
|
||||
/// 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)
|
||||
@@ -269,7 +156,7 @@ GLOBAL_LIST_INIT(immerse_ignored_movable, typecacheof(list(
|
||||
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.
|
||||
/// 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)
|
||||
@@ -278,7 +165,7 @@ GLOBAL_LIST_INIT(immerse_ignored_movable, typecacheof(list(
|
||||
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
|
||||
/// 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)
|
||||
@@ -287,123 +174,131 @@ GLOBAL_LIST_INIT(immerse_ignored_movable, typecacheof(list(
|
||||
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.
|
||||
*/
|
||||
/// 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))
|
||||
if(!attached_turf_contents[exited.loc])
|
||||
remove_from_element(source, exited)
|
||||
else
|
||||
LAZYREMOVE(attached_turfs_and_movables[source], exited)
|
||||
LAZYADD(attached_turfs_and_movables[exited.loc], exited)
|
||||
return
|
||||
LAZYREMOVE(attached_turf_contents[source], exited)
|
||||
LAZYADD(attached_turf_contents[exited.loc], exited)
|
||||
|
||||
///Remove any signal, overlay, trait given to the movable and reference to it within the element.
|
||||
//// 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
|
||||
var/atom/movable/buckled = null
|
||||
if(isliving(movable))
|
||||
var/mob/living/living_mob = movable
|
||||
buckled = living_mob.buckled
|
||||
try_unimmerse(movable, buckled)
|
||||
|
||||
LAZYREMOVE(attached_turfs_and_movables[source], movable)
|
||||
UnregisterSignal(movable, list(COMSIG_LIVING_SET_BUCKLED, COMSIG_QDELETING))
|
||||
try_unimmerse(movable, buckled)
|
||||
LAZYREMOVE(attached_turf_contents[source], movable)
|
||||
UnregisterSignal(movable, list(COMSIG_LIVING_SET_BUCKLED, COMSIG_QDELETING, COMSIG_LIVING_UPDATE_OFFSETS, COMSIG_ATOM_SPIN_ANIMATION, COMSIG_LIVING_POST_UPDATE_TRANSFORM))
|
||||
REMOVE_TRAIT(movable, TRAIT_IMMERSED, ELEMENT_TRAIT(src))
|
||||
|
||||
/// Generate a mask filter mutable to use as render_source for the alpha filter based on provided width, height and immersion state
|
||||
/datum/element/immerse/proc/generate_immerse_mask(width, height, is_below_water)
|
||||
var/clean_height = height
|
||||
width = ceil(width / ICON_SIZE_X) * ICON_SIZE_X
|
||||
height = ceil(height / ICON_SIZE_Y) * ICON_SIZE_Y
|
||||
|
||||
var/mask_key = "[width]-[height]-[is_below_water]"
|
||||
var/mutable_appearance/target_mask = immersion_masks[mask_key]
|
||||
if (target_mask)
|
||||
return target_mask
|
||||
|
||||
if (width == ICON_SIZE_X && height == ICON_SIZE_Y)
|
||||
target_mask = mutable_appearance('icons/effects/effects.dmi', mask_icon, alpha = alpha)
|
||||
immersion_masks[mask_key] = target_mask
|
||||
return target_mask
|
||||
|
||||
var/icon/column_icon = icon('icons/effects/effects.dmi', mask_icon)
|
||||
var/y_tiles = 1
|
||||
if (height != ICON_SIZE_Y)
|
||||
column_icon.Crop(1, 1, ICON_SIZE_X, ICON_SIZE_Y) // Use base icon and crop it out so animation frames respect dmi's delays
|
||||
y_tiles = ceil((height / ICON_SIZE_Y - 1) / 2) + 1
|
||||
column_icon.Scale(ICON_SIZE_X, y_tiles * ICON_SIZE_Y)
|
||||
var/icon/effect_icon = icon('icons/effects/effects.dmi', mask_icon)
|
||||
var/icon/fill_icon = icon('icons/effects/alphacolors.dmi', "white")
|
||||
for (var/y_tile in 1 to y_tiles - 1)
|
||||
column_icon.Blend(fill_icon, ICON_OVERLAY, 1, 1 + (y_tile - 1) * ICON_SIZE_Y)
|
||||
column_icon.Blend(effect_icon, ICON_OVERLAY, 1, 1 + (y_tiles - 1) * ICON_SIZE_Y)
|
||||
|
||||
var/icon/immerse_icon = null
|
||||
if (width == ICON_SIZE_X)
|
||||
immerse_icon = column_icon
|
||||
else
|
||||
immerse_icon = icon('icons/effects/effects.dmi', mask_icon) // Use base icon and crop it out so animation frames respect dmi's delays
|
||||
immerse_icon.Crop(1, 1, ICON_SIZE_X, ICON_SIZE_Y)
|
||||
immerse_icon.Scale(ceil(width / ICON_SIZE_X) * ICON_SIZE_X, ceil(height / ICON_SIZE_Y) * ICON_SIZE_Y)
|
||||
for (var/x_tile in 1 to ceil(width / ICON_SIZE_X))
|
||||
immerse_icon.Blend(column_icon, ICON_OVERLAY, 1 + (x_tile - 1) * ICON_SIZE_X, 1)
|
||||
target_mask = mutable_appearance(immerse_icon)
|
||||
target_mask.alpha = alpha
|
||||
target_mask.pixel_y = -(y_tiles - 1) * ICON_SIZE_Y + floor((clean_height - ICON_SIZE_Y) / 2)
|
||||
immersion_masks[mask_key] = target_mask
|
||||
return target_mask
|
||||
|
||||
/datum/element/immerse/proc/add_immerse_overlay(atom/movable/movable)
|
||||
// This determines if the overlay should cover the entire surface of the object or not
|
||||
var/layer_to_check = IS_TOPDOWN_PLANE(movable.plane) ? TOPDOWN_WATER_LEVEL_LAYER : WATER_LEVEL_LAYER
|
||||
var/is_below_water = (movable.layer < layer_to_check) ? "underwater-" : ""
|
||||
// Tall mobs still only get covered to their feet, unless they're offset down
|
||||
var/mutable_appearance/immerse_mask = generate_immerse_mask(movable.get_cached_width(), max(ICON_SIZE_Y - movable.pixel_z, ICON_SIZE_Y), is_below_water)
|
||||
var/atom/movable/immerse_mask/effect_relay = generated_visual_overlays[movable]
|
||||
if (!effect_relay)
|
||||
effect_relay = new(movable)
|
||||
movable.vis_contents += effect_relay
|
||||
generated_visual_overlays[movable] = effect_relay
|
||||
var/mutable_appearance/mask_copy = new(immerse_mask)
|
||||
effect_relay.appearance = mask_copy
|
||||
effect_relay.render_target = "*immerse_[REF(movable)]"
|
||||
SEND_SIGNAL(movable, COMSIG_MOVABLE_EDIT_UNIQUE_IMMERSE_OVERLAY, effect_relay)
|
||||
// Should always render above any other filters that could be adding visuals
|
||||
movable.add_filter("immerse_mask", INFINITY, alpha_mask_filter(y = -floor((movable.get_cached_height() - ICON_SIZE_Y) / 2) - movable.pixel_z, render_source = effect_relay.render_target, flags = MASK_INVERSE))
|
||||
|
||||
/datum/element/immerse/proc/remove_immerse_overlay(atom/movable/movable, deleting = TRUE)
|
||||
movable.remove_filter("immerse_mask")
|
||||
if (!deleting)
|
||||
return
|
||||
var/atom/movable/immerse_mask/mask = generated_visual_overlays[movable]
|
||||
movable.vis_contents -= mask
|
||||
generated_visual_overlays -= movable
|
||||
QDEL_NULL(mask)
|
||||
|
||||
/// 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.Scale(1 / source.current_size)
|
||||
new_transform.Turn(-new_lying_angle)
|
||||
var/atom/movable/immerse_mask/effect_relay = generated_visual_overlays[source]
|
||||
var/mutable_appearance/relay_appearance = new(effect_relay.appearance)
|
||||
relay_appearance.transform = new_transform
|
||||
effect_relay.appearance = relay_appearance
|
||||
|
||||
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_transform_translation_size(old_div)
|
||||
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_transform_translation_size(old_div) / old_div
|
||||
|
||||
switch(new_lying_angle)
|
||||
if(270)
|
||||
new_x += -offset_lying / source.current_size
|
||||
if(90)
|
||||
new_x += offset_lying / source.current_size
|
||||
if(0)
|
||||
new_y += -source.get_transform_translation_size(old_div) / old_div
|
||||
|
||||
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.
|
||||
/// 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)
|
||||
var/atom/movable/immerse_mask/immerse_mask = generated_visual_overlays[source]
|
||||
immerse_mask.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
|
||||
/datum/element/immerse/proc/on_update_offsets(mob/living/source, new_x, new_y, new_w, new_z, animate)
|
||||
SIGNAL_HANDLER
|
||||
var/old_height = ceil(max(ICON_SIZE_Y - source.pixel_z, ICON_SIZE_Y) / ICON_SIZE_Y)
|
||||
var/new_height = ceil(max(ICON_SIZE_Y - new_z, ICON_SIZE_Y) / ICON_SIZE_Y)
|
||||
if (old_height != new_height)
|
||||
remove_immerse_overlay(source, FALSE)
|
||||
add_immerse_overlay(source)
|
||||
|
||||
///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
|
||||
if (source.pixel_z == new_z)
|
||||
return
|
||||
|
||||
/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))
|
||||
if (animate)
|
||||
source.transition_filter("immerse_mask", list("y" = -floor((source.get_cached_height() - ICON_SIZE_Y) / 2) - new_z), time = UPDATE_TRANSFORM_ANIMATION_TIME)
|
||||
else
|
||||
source.modify_filter("immerse_mask", list("y" = -floor((source.get_cached_height() - ICON_SIZE_Y) / 2) - new_z))
|
||||
|
||||
///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
|
||||
overlays = list(overlay_appearance)
|
||||
/atom/movable/immerse_mask
|
||||
appearance_flags = RESET_TRANSFORM|RESET_COLOR|RESET_ALPHA|KEEP_APART
|
||||
vis_flags = VIS_HIDE
|
||||
|
||||
Reference in New Issue
Block a user