diff --git a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_lighting.dm b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_lighting.dm index 391ed0112ac..8e713f0260f 100644 --- a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_lighting.dm +++ b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_lighting.dm @@ -39,6 +39,17 @@ #define COMSIG_ATOM_SET_LIGHT_FLAGS "atom_set_light_flags" ///Called right after the atom changes the value of light_flags to a different one, from base of [/atom/proc/set_light_flags]: (old_flags) #define COMSIG_ATOM_UPDATE_LIGHT_FLAGS "atom_update_light_flags" +///Called right before the atom changes the value of light_render_source to a different one, from base [atom/proc/set_light_render_source]: (new_render_source) +#define COMSIG_ATOM_SET_LIGHT_RENDER_SOURCE "atom_set_light_render_source" +///Called right after the atom changes the value of light_render_source to a different one, from base of [/atom/proc/set_light_render_source]: (old_render_source) +#define COMSIG_ATOM_UPDATE_LIGHT_RENDER_SOURCE "atom_update_light_render_source" ///Called when an atom has a light template applied to it. Frombase of [/datum/light_template/proc/mirror_onto]: () #define COMSIG_ATOM_LIGHT_TEMPLATE_MIRRORED "atom_light_template_mirrored" + +///Called when an atom's overlay component applies visuals, from base of [/datum/component/overlay_lighting/proc/show_to_holder]: (atom/movable/light_holder) +#define COMSIG_ATOM_OVERLAY_LIGHT_APPLIED "atom_overlay_light_applied" + +///Called when an atom's overlay component hides its visuals, from base of [/datum/component/overlay_lighting/proc/hide_from_holder]: (atom/movable/light_holder) +#define COMSIG_ATOM_OVERLAY_LIGHT_REMOVED "atom_overlay_light_removed" + diff --git a/code/__DEFINES/dcs/signals/signals_light_intercept.dm b/code/__DEFINES/dcs/signals/signals_light_intercept.dm new file mode 100644 index 00000000000..8b4694d9d0e --- /dev/null +++ b/code/__DEFINES/dcs/signals/signals_light_intercept.dm @@ -0,0 +1,2 @@ +/// from base of [/datum/light_middleman/proc/light_modified] : () +#define COMSIG_LIGHT_MIDDLEMAN_UPDATED "light_middleman_updated" diff --git a/code/__DEFINES/dcs/signals/signals_object.dm b/code/__DEFINES/dcs/signals/signals_object.dm index 3bd83c546d6..319e933cd07 100644 --- a/code/__DEFINES/dcs/signals/signals_object.dm +++ b/code/__DEFINES/dcs/signals/signals_object.dm @@ -151,6 +151,10 @@ #define COMSIG_ITEM_STORED "item_stored" ///from base of datum/storage/handle_exit(): (datum/storage/storage) #define COMSIG_ITEM_UNSTORED "item_unstored" +///from base of obj/item/do_pickup_animation(): () +#define COMSIG_ITEM_BEFORE_PICKUP_ANIMATION "item_before_pickup_animation" +///from base of obj/item/do_drop_animation(): () +#define COMSIG_ITEM_BEFORE_DROP_ANIMATION "item_before_drop_animation" /** * From base of datum/strippable_item/get_alternate_actions(): (atom/owner, mob/user, list/alt_actions) diff --git a/code/__DEFINES/lighting.dm b/code/__DEFINES/lighting.dm index 08e76678fff..9b843a69519 100644 --- a/code/__DEFINES/lighting.dm +++ b/code/__DEFINES/lighting.dm @@ -11,6 +11,11 @@ /// Nonesensical value for light color, used for null checks. #define NONSENSICAL_VALUE -99999 +// Light systems that use the overlay light component +#define IS_OVERLAY_LIGHT_SYSTEM(system) (system == OVERLAY_LIGHT || system == OVERLAY_LIGHT_DIRECTIONAL || system == OVERLAY_LIGHT_BEAM) +// Light systems that use the cone image of the overlay light component +#define IS_OVERLAY_CONE_LIGHT_SYSTEM(system) (system == OVERLAY_LIGHT_DIRECTIONAL || system == OVERLAY_LIGHT_BEAM) + /// Is our overlay light source attached to another movable (its loc), meaning that the lighting component should go one level deeper. #define LIGHT_ATTACHED (1<<0) /// Freezes a light in its current state, blocking any attempts at modification diff --git a/code/__DEFINES/maths.dm b/code/__DEFINES/maths.dm index c4f95c49eae..d2fe40a502c 100644 --- a/code/__DEFINES/maths.dm +++ b/code/__DEFINES/maths.dm @@ -53,6 +53,9 @@ /// Increments a value and wraps it if it exceeds some value. Can be used to circularly iterate through a list through `idx = WRAP_UP(idx, length_of_list)`. #define WRAP_UP(val, max) (((val) % (max)) + 1) +/// Helper that increments and wraps the passed in number when it hits the integer limit +#define WRAP_UID(val) WRAP_UP(val, SHORT_REAL_LIMIT - 1) + // Real modulus that handles decimals #define MODULUS(x, y) ( (x) - FLOOR(x, y)) @@ -233,6 +236,9 @@ #define LORENTZ_DISTRIBUTION(x, s) ( s*tan(TODEGREES(PI*(rand()-0.5))) + x ) #define LORENTZ_CUMULATIVE_DISTRIBUTION(x, y, s) ( (1/PI)*TORADIANS(arctan((x-(y))/s)) + 1/2 ) +/// Fucked up like upside down cauchy dist that I've pinned to 1 so I can use it as a multiplier +/// https://www.desmos.com/calculator/bt4tfavvi7 +#define ANCHORED_INVERSE_CAUCHY(s) (2 - ( 1 / (s * (1 + ((rand() - 0.5) / s) ** 2 ))) * (s + (0.5 ** 2) / s)) #define RULE_OF_THREE(a, b, x) ((a*x)/b) diff --git a/code/__DEFINES/movement.dm b/code/__DEFINES/movement.dm index e07fe65552d..57b7825bcc9 100644 --- a/code/__DEFINES/movement.dm +++ b/code/__DEFINES/movement.dm @@ -29,15 +29,18 @@ GLOBAL_VAR_INIT(glide_size_multiplier, 1.0) //Movement loop flags ///Should the loop act immediately following its addition? -#define MOVEMENT_LOOP_START_FAST (1<<0) +#define MOVEMENT_LOOP_START_INSTANT (1<<0) +///Should the loop act as soon as is reasonable? +///(always 1 tick after the next visual tick, makes behavior consistent regardless of when the SS fires in the tick) +#define MOVEMENT_LOOP_START_FAST (1<<1) ///Do we not use the priority system? -#define MOVEMENT_LOOP_IGNORE_PRIORITY (1<<1) +#define MOVEMENT_LOOP_IGNORE_PRIORITY (1<<2) ///Should we override the loop's glide? -#define MOVEMENT_LOOP_IGNORE_GLIDE (1<<2) +#define MOVEMENT_LOOP_IGNORE_GLIDE (1<<3) ///Should we not update our movables dir on move? -#define MOVEMENT_LOOP_NO_DIR_UPDATE (1<<3) +#define MOVEMENT_LOOP_NO_DIR_UPDATE (1<<4) ///Is the loop moving the movable outside its control, like it's an external force? e.g. footsteps won't play if enabled. -#define MOVEMENT_LOOP_OUTSIDE_CONTROL (1<<4) +#define MOVEMENT_LOOP_OUTSIDE_CONTROL (1<<5) // Movement loop status flags /// Has the loop been paused, soon to be resumed? diff --git a/code/__DEFINES/visual_helpers.dm b/code/__DEFINES/visual_helpers.dm index fda7f664809..d3cd58f672b 100644 --- a/code/__DEFINES/visual_helpers.dm +++ b/code/__DEFINES/visual_helpers.dm @@ -23,3 +23,8 @@ /// Much like [SET_BASE_PIXEL], except it will not effect pixel offsets in mapping programs #define SET_BASE_PIXEL_NOMAP(x, y) MAP_SWITCH(SET_BASE_PIXEL(x, y), _SET_BASE_PIXEL_NO_OFFSET(x, y)) + +/// What world.time will we next complete a visual tick? +/// This is important because we often want to avoid moving an object twice in a tick, since this can lead to teleportation instead of gliding +/// (such as when you move an atom that just spawned) +#define NEXT_VISUAL_TICK (GLOB.last_maptick_time + world.tick_lag) diff --git a/code/controllers/subsystem/atoms.dm b/code/controllers/subsystem/atoms.dm index ad09a0caeb2..0ea51fe9624 100644 --- a/code/controllers/subsystem/atoms.dm +++ b/code/controllers/subsystem/atoms.dm @@ -47,7 +47,7 @@ SUBSYSTEM_DEF(atoms) // Generate a unique mapload source for this run of InitializeAtoms var/static/uid = 0 - uid = (uid + 1) % (SHORT_REAL_LIMIT - 1) + uid = WRAP_UID(uid + 1) var/source = "subsystem init [uid]" set_tracked_initalized(INITIALIZATION_INNEW_MAPLOAD, source) diff --git a/code/controllers/subsystem/movement/movement_types.dm b/code/controllers/subsystem/movement/movement_types.dm index 6acce747bbe..1aa16a42fa0 100644 --- a/code/controllers/subsystem/movement/movement_types.dm +++ b/code/controllers/subsystem/movement/movement_types.dm @@ -59,11 +59,16 @@ SEND_SIGNAL(src, COMSIG_MOVELOOP_START) status |= MOVELOOP_STATUS_RUNNING //If this is our first time starting to move with this loop - //And we're meant to start instantly + //And we want to start consistently fast if(!timer && flags & MOVEMENT_LOOP_START_FAST) - timer = world.time + // + tick_lag because we want to avoid weird jumping in atoms that were just created (and avoid inconsistencies around subsystem timing) + timer = NEXT_VISUAL_TICK + world.tick_lag return - timer = world.time + delay + //And we're meant to start instantly + if(!timer && flags & MOVEMENT_LOOP_START_INSTANT) + timer = NEXT_VISUAL_TICK + return + timer = NEXT_VISUAL_TICK + delay ///Called when a loop is stopped, doesn't stop the loop itself /datum/move_loop/proc/loop_stopped() diff --git a/code/datums/ai/movement/ai_movement_basic_avoidance.dm b/code/datums/ai/movement/ai_movement_basic_avoidance.dm index 6659da244f3..169301f7047 100644 --- a/code/datums/ai/movement/ai_movement_basic_avoidance.dm +++ b/code/datums/ai/movement/ai_movement_basic_avoidance.dm @@ -21,4 +21,4 @@ /// Move immediately and don't update our facing /datum/ai_movement/basic_avoidance/backstep - move_flags = MOVEMENT_LOOP_START_FAST | MOVEMENT_LOOP_NO_DIR_UPDATE + move_flags = MOVEMENT_LOOP_START_INSTANT | MOVEMENT_LOOP_NO_DIR_UPDATE diff --git a/code/datums/components/overlay_lighting.dm b/code/datums/components/overlay_lighting.dm index ca8d4e7b23d..8f07b57d912 100644 --- a/code/datums/components/overlay_lighting.dm +++ b/code/datums/components/overlay_lighting.dm @@ -64,6 +64,8 @@ var/beam = FALSE ///A cone overlay for directional light, its alpha and color are dependent on the light var/image/cone + /// Are we currently displaying light on our holder? + var/currently_displaying = FALSE ///Current tracked direction for the directional cast behaviour var/current_direction ///Tracks current directional x offset so we don't update unnecessarily @@ -78,7 +80,7 @@ return COMPONENT_INCOMPATIBLE var/atom/movable/movable_parent = parent - if(!force && movable_parent.light_system != OVERLAY_LIGHT && movable_parent.light_system != OVERLAY_LIGHT_DIRECTIONAL && movable_parent.light_system != OVERLAY_LIGHT_BEAM) + if(!force && !IS_OVERLAY_LIGHT_SYSTEM(movable_parent.light_system)) stack_trace("[type] added to [parent], with [movable_parent.light_system] value for the light_system var. Use [OVERLAY_LIGHT], [OVERLAY_LIGHT_DIRECTIONAL] or [OVERLAY_LIGHT_BEAM] instead.") return COMPONENT_INCOMPATIBLE @@ -111,7 +113,7 @@ set_color(parent, movable_parent.light_color) if(!isnull(starts_on)) movable_parent.set_light_on(starts_on) - + set_light_render_source(parent, "") /datum/component/overlay_lighting/RegisterWithParent() . = ..() @@ -122,10 +124,12 @@ RegisterSignal(parent, COMSIG_ATOM_UPDATE_LIGHT_COLOR, PROC_REF(set_color)) RegisterSignal(parent, COMSIG_ATOM_UPDATE_LIGHT_ON, PROC_REF(on_toggle)) RegisterSignal(parent, COMSIG_ATOM_UPDATE_LIGHT_FLAGS, PROC_REF(on_light_flags_change)) + RegisterSignal(parent, COMSIG_ATOM_SET_LIGHT_RENDER_SOURCE, PROC_REF(set_light_render_source)) RegisterSignal(parent, COMSIG_ATOM_USED_IN_CRAFT, PROC_REF(on_parent_crafted)) RegisterSignal(parent, COMSIG_LIGHT_EATER_QUEUE, PROC_REF(on_light_eater)) RegisterSignal(parent, COMSIG_MOVABLE_MOVED, PROC_REF(on_parent_moved)) RegisterSignal(parent, COMSIG_MOVABLE_Z_CHANGED, PROC_REF(on_z_move)) + RegisterSignal(parent, COMSIG_ITEM_BEFORE_PICKUP_ANIMATION, PROC_REF(on_pickup_anim)) var/atom/movable/movable_parent = parent if(movable_parent.light_flags & LIGHT_ATTACHED) overlay_lighting_flags |= LIGHTING_ATTACHED @@ -200,18 +204,44 @@ ///Adds the luminosity and source for the affected movable atoms to keep track of their visibility. /datum/component/overlay_lighting/proc/add_dynamic_lumi() LAZYSET(current_holder.affected_dynamic_lights, src, lumcount_range + 1) - current_holder.underlays += visible_mask + show_to_holder() current_holder.update_dynamic_luminosity() - if(directional) - current_holder.underlays += cone ///Removes the luminosity and source for the affected movable atoms to keep track of their visibility. /datum/component/overlay_lighting/proc/remove_dynamic_lumi() LAZYREMOVE(current_holder.affected_dynamic_lights, src) - current_holder.underlays -= visible_mask + hide_from_holder() current_holder.update_dynamic_luminosity() + +/// Adds our overlays to our holder, assuming everything's setup proper +/datum/component/overlay_lighting/proc/show_to_holder() + if(currently_displaying) + return + if(isnull(current_holder) || !(overlay_lighting_flags & LIGHTING_ON)) + currently_displaying = FALSE + return + current_holder.underlays += visible_mask + if(directional) + current_holder.underlays += cone + currently_displaying = TRUE + // These are very intentionally copies so recipients cannot + // Accidentially brick lighting overlays by mutating them + var/mutable_appearance/mask_clone = new (visible_mask) + var/mutable_appearance/cone_clone = directional ? new /mutable_appearance(cone) : null + SEND_SIGNAL(parent, COMSIG_ATOM_OVERLAY_LIGHT_APPLIED, mask_clone, cone_clone, current_holder) + +/// Removes our overlay from our holder, assuming everything's setup proper +/// MUST be called before modifying cone or visible_mask, or you will cause stuck lighting +/datum/component/overlay_lighting/proc/hide_from_holder() + if(!currently_displaying) + return + if(isnull(current_holder) || !(overlay_lighting_flags & LIGHTING_ON)) + return + current_holder.underlays -= visible_mask if(directional) current_holder.underlays -= cone + currently_displaying = FALSE + SEND_SIGNAL(parent, COMSIG_ATOM_OVERLAY_LIGHT_REMOVED, current_holder) ///Called to change the value of parent_attached_to. /datum/component/overlay_lighting/proc/set_parent_attached_to(atom/movable/new_parent_attached_to) @@ -321,15 +351,16 @@ /datum/component/overlay_lighting/proc/on_z_move(atom/source) SIGNAL_HANDLER - if(current_holder && overlay_lighting_flags & LIGHTING_ON) - current_holder.underlays -= visible_mask - current_holder.underlays -= cone + hide_from_holder() SET_PLANE_EXPLICIT(visible_mask, O_LIGHTING_VISUAL_PLANE, source) if(cone) SET_PLANE_EXPLICIT(cone, O_LIGHTING_VISUAL_PLANE, source) - if(current_holder && overlay_lighting_flags & LIGHTING_ON) - current_holder.underlays += visible_mask - current_holder.underlays += cone + show_to_holder() + +// Avoids duplicate overlays (one from our NEXT holder, selected after the animation, one from the pickup animation) +/datum/component/overlay_lighting/proc/on_pickup_anim(atom/source) + SIGNAL_HANDLER + hide_from_holder() ///Called when the current_holder is qdeleted, to remove the light effect. /datum/component/overlay_lighting/proc/on_parent_attached_to_qdel(atom/movable/source, force) @@ -364,20 +395,18 @@ range = clamp(CEILING(new_range, 0.5), 1, 6) var/pixel_bounds = ((range - 1) * 64) + 32 lumcount_range = CEILING(range, 1) - if(current_holder && overlay_lighting_flags & LIGHTING_ON) - current_holder.underlays -= visible_mask + hide_from_holder() + visible_mask.icon = light_overlays["[pixel_bounds]"] if(pixel_bounds == 32) - if(!directional) // it's important that we make it to the end of this function if we are a directional light - visible_mask.transform = null - return + visible_mask.transform = null else var/offset = (pixel_bounds - 32) * 0.5 var/matrix/transform = new transform.Translate(-offset, -offset) visible_mask.transform = transform - if(current_holder && overlay_lighting_flags & LIGHTING_ON) - current_holder.underlays += visible_mask + + show_to_holder() if(directional) if(beam) cast_range = max(round(new_range * 0.5), 1) @@ -393,39 +422,24 @@ var/new_power = source.light_power set_lum_power(new_power >= 0 ? 0.5 : -0.5) set_alpha = min(230, (abs(new_power) * 120) + 30) - if(current_holder && overlay_lighting_flags & LIGHTING_ON) - current_holder.underlays -= visible_mask + hide_from_holder() visible_mask.alpha = set_alpha visible_mask.blend_mode = new_power > 0 ? BLEND_ADD : BLEND_SUBTRACT - if(current_holder && overlay_lighting_flags & LIGHTING_ON) - current_holder.underlays += visible_mask - if(!directional) - return - if(current_holder && overlay_lighting_flags & LIGHTING_ON) - current_holder.underlays -= cone - cone.alpha = min(120, (abs(new_power) * 60) + 15) - cone.blend_mode = new_power > 0 ? BLEND_ADD : BLEND_SUBTRACT - if(current_holder && overlay_lighting_flags & LIGHTING_ON) - current_holder.underlays += cone + if(directional) + cone.alpha = min(120, (abs(new_power) * 60) + 15) + cone.blend_mode = new_power > 0 ? BLEND_ADD : BLEND_SUBTRACT + show_to_holder() ///Changes the light's color, pretty straightforward. /datum/component/overlay_lighting/proc/set_color(atom/source, old_color) SIGNAL_HANDLER var/new_color = source.light_color - if(current_holder && overlay_lighting_flags & LIGHTING_ON) - current_holder.underlays -= visible_mask + hide_from_holder() visible_mask.color = new_color - if(current_holder && overlay_lighting_flags & LIGHTING_ON) - current_holder.underlays += visible_mask - if(!directional) - return - if(current_holder && overlay_lighting_flags & LIGHTING_ON) - current_holder.underlays -= cone - cone.color = new_color - if(current_holder && overlay_lighting_flags & LIGHTING_ON) - current_holder.underlays += cone - + if(directional) + cone.color = new_color + show_to_holder() ///Toggles the light on and off. /datum/component/overlay_lighting/proc/on_toggle(atom/source, old_value) @@ -436,7 +450,6 @@ return turn_off() //Falsey value, turn off. - ///Triggered right after the parent light flags change. /datum/component/overlay_lighting/proc/on_light_flags_change(atom/source, old_flags) SIGNAL_HANDLER @@ -453,6 +466,18 @@ overlay_lighting_flags &= ~LIGHTING_ATTACHED set_parent_attached_to(null) +///Changes the light's color, pretty straightforward. +/datum/component/overlay_lighting/proc/set_light_render_source(atom/source, old_render_source) + SIGNAL_HANDLER + var/new_source = source.light_render_source + hide_from_holder() + visible_mask.render_source = new_source + if(directional) + var/new_cone_source = "" + if(new_source) + new_cone_source = "[new_source]_cone" + cone.render_source = new_cone_source + show_to_holder() ///Toggles the light on. /datum/component/overlay_lighting/proc/turn_on() @@ -504,7 +529,7 @@ break scanning = next_turf - current_holder.underlays -= visible_mask + hide_from_holder() var/translate_x = -((range - 1) * 32) var/translate_y = translate_x @@ -536,8 +561,8 @@ transform.Scale(scale_x, scale_y) transform.Translate(translate_x, translate_y) visible_mask.transform = transform - if(overlay_lighting_flags & LIGHTING_ON) - current_holder.underlays += visible_mask + + show_to_holder() ///Called when current_holder changes loc. /datum/component/overlay_lighting/proc/on_holder_dir_change(atom/movable/source, olddir, newdir) diff --git a/code/datums/drift_handler.dm b/code/datums/drift_handler.dm index 53133025657..0508123c138 100644 --- a/code/datums/drift_handler.dm +++ b/code/datums/drift_handler.dm @@ -23,7 +23,7 @@ parent.drift_handler = src var/flags = MOVEMENT_LOOP_OUTSIDE_CONTROL if(instant) - flags |= MOVEMENT_LOOP_START_FAST + flags |= MOVEMENT_LOOP_START_INSTANT src.drift_force = drift_force drifting_loop = GLOB.move_manager.smooth_move(moving = parent, angle = inertia_angle, delay = get_loop_delay(parent), subsystem = SSnewtonian_movement, priority = MOVEMENT_SPACE_PRIORITY, flags = flags) diff --git a/code/game/atom/_atom.dm b/code/game/atom/_atom.dm index 73a29c11eca..76c5744c0f9 100644 --- a/code/game/atom/_atom.dm +++ b/code/game/atom/_atom.dm @@ -73,17 +73,23 @@ var/light_power = 1 ///Hexadecimal RGB string representing the colour of the light. White by default. var/light_color = COLOR_WHITE + ///Boolean variable for toggleable lights. Has no effect without the proper light_system, light_range and light_power values. + var/light_on = TRUE + ///Bitflags to determine lighting-related atom properties. + var/light_flags = NONE + + // OVERLAY_LIGHT only values + /// An optional render_source to apply to this atom's light overlay + var/light_render_source = "" + + // COMPLEX_LIGHT only values /// Angle of light to show in light_dir /// 360 is a circle, 90 is a cone, etc. var/light_angle = 360 /// What angle to project light in var/light_dir = NORTH - ///Boolean variable for toggleable lights. Has no effect without the proper light_system, light_range and light_power values. - var/light_on = TRUE /// How many tiles "up" this light is. 1 is typical, should only really change this if it's a floor light var/light_height = LIGHTING_HEIGHT - ///Bitflags to determine lighting-related atom properties. - var/light_flags = NONE ///Our light source. Don't fuck with this directly unless you have a good reason! var/tmp/datum/light_source/light ///Any light sources that are "inside" of us, for example, if src here was a mob that's carrying a flashlight, that flashlight's light source would be part of this list. diff --git a/code/game/atom/atom_vv.dm b/code/game/atom/atom_vv.dm index 14e20d23b9d..d9600d4bae9 100644 --- a/code/game/atom/atom_vv.dm +++ b/code/game/atom/atom_vv.dm @@ -263,6 +263,27 @@ // I'm sorry old_light_flags = var_value . = TRUE + if(NAMEOF(src, light_render_source)) + set_light_render_source(var_value) + . = TRUE + if(NAMEOF(src, light_angle)) + if(light_system == COMPLEX_LIGHT) + set_light(l_angle = var_value) + else + set_light_angle(var_value) + . = TRUE + if(NAMEOF(src, light_dir)) + if(light_system == COMPLEX_LIGHT) + set_light(l_dir = var_value) + else + set_light_dir(var_value) + . = TRUE + if(NAMEOF(src, light_height)) + if(light_system == COMPLEX_LIGHT) + set_light(l_height = var_value) + else + set_light_height(var_value) + . = TRUE if(NAMEOF(src, smoothing_junction)) set_smoothed_icon_state(var_value) . = TRUE diff --git a/code/game/objects/effects/effect_system/effect_system.dm b/code/game/objects/effects/effect_system/effect_system.dm index c91b2168454..5bd2f3267ca 100644 --- a/code/game/objects/effects/effect_system/effect_system.dm +++ b/code/game/objects/effects/effect_system/effect_system.dm @@ -57,6 +57,16 @@ var/total_effects = 0 /// Should the system delete itself after finishing? var/autocleanup = FALSE + /// Should the system delete effects that stop moving? + var/delete_on_stop = FALSE + /// How frequently (in deciseconds) should we move our particles? + var/step_delay = 0.5 SECONDS + + // Internal use + /// The length of the previous assigned moveloop in deciseconds + var/last_loop_length = 0 + /// List of dirs avalible to pick, used to avoid accidential duplicates + var/list/pickable_dirs = list() /datum/effect_system/basic/New(turf/location, amount = null, cardinals_only = null) . = ..() @@ -73,25 +83,48 @@ return generate_effect() +/// Returns how many steps to attempt to move a generated effect +/datum/effect_system/basic/proc/get_step_count() + return rand(1, 3) + +/// Generates a effect for our system to control, returns the generated effect /datum/effect_system/basic/proc/generate_effect() if(holder) location = get_turf(holder) var/obj/effect/effect = new effect_type(location) total_effects++ - var/direction - if(cardinals_only) - direction = pick(GLOB.cardinals) - else - direction = pick(GLOB.alldirs) - var/step_amt = rand(1, 3) - var/step_delay = 5 - var/datum/move_loop/loop = GLOB.move_manager.move(effect, direction, step_delay, timeout = step_delay * step_amt, priority = MOVEMENT_ABOVE_SPACE_PRIORITY) - RegisterSignal(loop, COMSIG_QDELETING, PROC_REF(decrement_total_effect)) + if(!length(pickable_dirs)) + if(cardinals_only) + pickable_dirs = GLOB.cardinals.Copy() + else + pickable_dirs = GLOB.alldirs.Copy() + // Try not to reuse dirs if possible to avoid weird stacking + var/direction = pick_n_take(pickable_dirs) -/datum/effect_system/basic/proc/decrement_total_effect(datum/source) + var/step_count = get_step_count() + var/datum/move_loop/loop = GLOB.move_manager.move(effect, direction, step_delay, timeout = step_delay * step_count, priority = MOVEMENT_ABOVE_SPACE_PRIORITY, flags = MOVEMENT_LOOP_START_FAST) + RegisterSignal(loop, COMSIG_MOVELOOP_POSTPROCESS, PROC_REF(post_move)) + RegisterSignal(loop, COMSIG_QDELETING, PROC_REF(loop_end)) + last_loop_length = loop.lifetime + return effect + +/datum/effect_system/basic/proc/post_move(datum/move_loop/source, result, visual_delay) + SIGNAL_HANDLER + if(result == MOVELOOP_FAILURE) + move_failed(source, source.moving) + +/// Allows us to hook into being unable to automatically move +/datum/effect_system/basic/proc/move_failed(datum/move_loop/loop, obj/effect/failed) + if(QDELETED(failed) || !delete_on_stop) + return + qdel(failed) + +/datum/effect_system/basic/proc/loop_end(datum/move_loop/source) SIGNAL_HANDLER total_effects-- + if(delete_on_stop && !QDELETED(source.moving)) + qdel(source.moving) if(autocleanup && total_effects == 0) QDEL_IN(src, 2 SECONDS) diff --git a/code/game/objects/effects/effect_system/effects_explosion.dm b/code/game/objects/effects/effect_system/effects_explosion.dm index 9f4db05d967..5e9ff4e1ade 100644 --- a/code/game/objects/effects/effect_system/effects_explosion.dm +++ b/code/game/objects/effects/effect_system/effects_explosion.dm @@ -4,14 +4,6 @@ opacity = TRUE anchored = TRUE -/obj/effect/particle_effect/expl_particles/Initialize(mapload) - ..() - return INITIALIZE_HINT_LATELOAD - -/obj/effect/particle_effect/expl_particles/LateInitialize() - var/step_amt = pick(25;1, 50;2, 100;3, 200;4) - var/datum/move_loop/loop = GLOB.move_manager.move(src, pick(GLOB.alldirs), 1, timeout = step_amt, priority = MOVEMENT_ABOVE_SPACE_PRIORITY) - RegisterSignal(loop, COMSIG_QDELETING, PROC_REF(end_particle)) /obj/effect/particle_effect/expl_particles/proc/end_particle(datum/source) SIGNAL_HANDLER @@ -19,10 +11,20 @@ qdel(src) /datum/effect_system/basic/expl_particles + effect_type = /obj/effect/particle_effect/expl_particles amount = 10 + step_delay = 0.1 SECONDS + delete_on_stop = TRUE -/datum/effect_system/basic/expl_particles/generate_effect() - new /obj/effect/particle_effect/expl_particles(location) +/datum/effect_system/basic/expl_particles/get_step_count() + return pick(25;1, 50;2, 100;3, 200;4) + +/datum/effect_system/basic/expl_particles/loop_end(datum/move_loop/source) + . = ..() + var/obj/effect/explosion_particle = source.moving + if(QDELETED(explosion_particle)) + return + qdel(explosion_particle) /obj/effect/explosion name = "fire" diff --git a/code/game/objects/effects/effect_system/effects_sparks.dm b/code/game/objects/effects/effect_system/effects_sparks.dm index 8a40659a003..aaf1efa3fba 100644 --- a/code/game/objects/effects/effect_system/effects_sparks.dm +++ b/code/game/objects/effects/effect_system/effects_sparks.dm @@ -18,11 +18,20 @@ anchored = TRUE light_system = OVERLAY_LIGHT light_range = 1.5 - light_power = 0.8 + light_power = 2 light_color = LIGHT_COLOR_FIRE + /// Should this spark's effect be animated + var/animated = TRUE + /// Timer id for the timer that will wipe us out + var/delete_timer_id = TIMER_ID_NULL + /// Middleman object we're using to animate our light + var/datum/light_middleman/middleman /obj/effect/particle_effect/sparks/Initialize(mapload) ..() + if(animated) + middleman = new(src, "sparks") + middleman.being_overriding_light() return INITIALIZE_HINT_LATELOAD /obj/effect/particle_effect/sparks/LateInitialize() @@ -32,7 +41,29 @@ var/turf/location = loc if(isturf(location)) affect_location(location, just_initialized = TRUE) - QDEL_IN(src, 2 SECONDS) + decay_in(2 SECONDS) + +/obj/effect/particle_effect/sparks/Destroy() + if(!isnull(middleman)) + QDEL_NULL(middleman) + return ..() + +/// Sets up our death effects given the passed in duration +/obj/effect/particle_effect/sparks/proc/decay_in(decay_time) + if(delete_timer_id != TIMER_ID_NULL) + deltimer(delete_timer_id) + delete_timer_id = QDEL_IN_STOPPABLE(src, decay_time + world.tick_lag) + if(!animated) + return + var/obj/effect/abstract/main_light = middleman.primary_intercept + // We're going to fade our light out so it's less jarring when we fully disappear + // Note, a refresh of the overlay light would break this, we're basically just sorta assuming that won't happen + // Would need to track time and sort of "replay" where we should be otherwise + if(decay_time >= 0.7 SECONDS) // duration of all animated spark's actual icon state animation + animate(main_light, alpha = 220, time = 0.4 SECONDS) + animate(alpha = 0, time = decay_time - 0.4 SECONDS, easing = CIRCULAR_EASING | EASE_IN) + else + animate(main_light, alpha = 0, time = decay_time) /obj/effect/particle_effect/sparks/Destroy() var/turf/location = loc @@ -91,6 +122,20 @@ /datum/effect_system/basic/spark_spread effect_type = /obj/effect/particle_effect/sparks + step_delay = 0.35 SECONDS // chosen so we will always take at least the duration of our animation to finish + +/datum/effect_system/basic/spark_spread/generate_effect() + var/obj/effect/particle_effect/sparks/spark = ..() + spark.decay_in(last_loop_length) + +/datum/effect_system/basic/spark_spread/get_step_count() + return rand(2, 3) // never 1 cause 1 looks dumb + +/datum/effect_system/basic/spark_spread/move_failed(datum/move_loop/loop, obj/effect/failed) + if(QDELETED(failed)) + return + var/obj/effect/particle_effect/sparks/spark = failed + spark.decay_in(0.1 SECONDS) /datum/effect_system/basic/spark_spread/quantum effect_type = /obj/effect/particle_effect/sparks/quantum @@ -100,6 +145,8 @@ /obj/effect/particle_effect/sparks/electricity name = "lightning" icon_state = "electricity" + animated = FALSE /datum/effect_system/basic/lightning_spread + delete_on_stop = TRUE effect_type = /obj/effect/particle_effect/sparks/electricity diff --git a/code/game/objects/effects/effect_system/effects_water.dm b/code/game/objects/effects/effect_system/effects_water.dm index f8f62c13b1b..cbe4472614f 100644 --- a/code/game/objects/effects/effect_system/effects_water.dm +++ b/code/game/objects/effects/effect_system/effects_water.dm @@ -39,7 +39,7 @@ /// Starts the effect moving at a target with a delay in deciseconds, and a lifetime in moves /// Returns the created loop /obj/effect/particle_effect/water/extinguisher/proc/move_at(atom/target, delay, lifetime) - var/datum/move_loop/loop = GLOB.move_manager.move_towards_legacy(src, target, delay, timeout = delay * lifetime, flags = MOVEMENT_LOOP_START_FAST, priority = MOVEMENT_ABOVE_SPACE_PRIORITY) + var/datum/move_loop/loop = GLOB.move_manager.move_towards_legacy(src, target, delay, timeout = delay * lifetime, priority = MOVEMENT_ABOVE_SPACE_PRIORITY, flags = MOVEMENT_LOOP_START_FAST) RegisterSignal(loop, COMSIG_MOVELOOP_POSTPROCESS, PROC_REF(post_forcemove)) RegisterSignal(loop, COMSIG_QDELETING, PROC_REF(movement_stopped)) return loop @@ -88,4 +88,5 @@ QDEL_IN(src, 2 SECONDS) /datum/effect_system/basic/steam_spread + delete_on_stop = TRUE effect_type = /obj/effect/particle_effect/steam diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm index ec0ff3340cd..21df3b187f2 100644 --- a/code/game/objects/items.dm +++ b/code/game/objects/items.dm @@ -1515,6 +1515,7 @@ if(!istype(loc, /turf)) return source = loc + SEND_SIGNAL(src, COMSIG_ITEM_BEFORE_PICKUP_ANIMATION) var/image/pickup_animation = image(icon = src) SET_PLANE(pickup_animation, GAME_PLANE, source) pickup_animation.transform.Scale(0.75) @@ -1551,6 +1552,7 @@ if(!istype(moving_from)) return + SEND_SIGNAL(src, COMSIG_ITEM_BEFORE_DROP_ANIMATION) var/turf/current_turf = get_turf(src) var/direction = get_dir(moving_from, current_turf) var/from_x = moving_from.base_pixel_x diff --git a/code/game/objects/items/devices/flashlight.dm b/code/game/objects/items/devices/flashlight.dm index f5a29b9f477..73e774205b5 100644 --- a/code/game/objects/items/devices/flashlight.dm +++ b/code/game/objects/items/devices/flashlight.dm @@ -465,9 +465,15 @@ /datum/material/plasma = SMALL_MATERIAL_AMOUNT * 0.5, /datum/material/plastic = SMALL_MATERIAL_AMOUNT * 0.5, ) + /// Lighting middleman, lets us do a flicker effect + var/datum/light_middleman/middleman /obj/item/flashlight/flare/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() if(randomize_fuel) fuel = rand(10 MINUTES, 15 MINUTES) if(light_on) @@ -486,6 +492,8 @@ /obj/item/flashlight/flare/Destroy() STOP_PROCESSING(SSobj, src) + if(middleman) + QDEL_NULL(middleman) return ..() /obj/item/flashlight/flare/afterattack(atom/target, mob/user, click_parameters) @@ -519,6 +527,10 @@ damtype = initial(damtype) update_brightness() +/obj/item/flashlight/flare/proc/light_updated(datum/source) + SIGNAL_HANDLER + fire_flicker_middleman(middleman) + /obj/item/flashlight/flare/extinguish() . = ..() if((fuel != INFINITY) && can_be_extinguished) diff --git a/code/game/objects/items/lighter.dm b/code/game/objects/items/lighter.dm index 60a7bf5271e..77b373617ed 100644 --- a/code/game/objects/items/lighter.dm +++ b/code/game/objects/items/lighter.dm @@ -38,9 +38,15 @@ ) /// Whether the lighter starts with fuel var/spawns_with_reagent = TRUE + /// Lighting middleman, lets us do a flicker effect + var/datum/light_middleman/middleman /obj/item/lighter/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() create_reagents(maximum_fuel, REFILLABLE | DRAINABLE) if(spawns_with_reagent) reagents.add_reagent(/datum/reagent/fuel, maximum_fuel) @@ -54,6 +60,11 @@ ) update_appearance() +/obj/item/lighter/Destroy(force) + if(!isnull(middleman)) + QDEL_NULL(middleman) + return ..() + /obj/item/lighter/grind_results() return list(/datum/reagent/iron = 1, /datum/reagent/fuel = 5, /datum/reagent/fuel/oil = 5) @@ -64,6 +75,10 @@ else . += span_notice("It contains [get_fuel()] units of fuel out of [maximum_fuel].") +/obj/item/lighter/proc/light_updated(datum/source) + SIGNAL_HANDLER + fire_flicker_middleman(middleman) + /// Destroy the lighter when it's shot by a bullet /obj/item/lighter/proc/on_intercepted_bullet(mob/living/victim, obj/projectile/bullet) victim.visible_message(span_warning("\The [bullet] shatters on [victim]'s lighter!")) diff --git a/code/game/objects/items/tools/engineering/weldingtool.dm b/code/game/objects/items/tools/engineering/weldingtool.dm index a82218e845d..68e6020b919 100644 --- a/code/game/objects/items/tools/engineering/weldingtool.dm +++ b/code/game/objects/items/tools/engineering/weldingtool.dm @@ -48,6 +48,8 @@ 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 @@ -55,6 +57,10 @@ /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) @@ -64,6 +70,10 @@ 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" @@ -145,6 +155,10 @@ 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)) diff --git a/code/game/objects/items/tools/extinguisher.dm b/code/game/objects/items/tools/extinguisher.dm index 9f58808a752..689d064c031 100644 --- a/code/game/objects/items/tools/extinguisher.dm +++ b/code/game/objects/items/tools/extinguisher.dm @@ -343,7 +343,7 @@ //Chair movement loop /obj/item/extinguisher/proc/move_chair(obj/buckled_object, movementdirection) - var/datum/move_loop/loop = GLOB.move_manager.move(buckled_object, movementdirection, 1, timeout = 9, flags = MOVEMENT_LOOP_START_FAST, priority = MOVEMENT_ABOVE_SPACE_PRIORITY) + var/datum/move_loop/loop = GLOB.move_manager.move(buckled_object, movementdirection, 1, timeout = 9, flags = MOVEMENT_LOOP_START_INSTANT, priority = MOVEMENT_ABOVE_SPACE_PRIORITY) //This means the chair slowing down is dependant on the extinguisher existing, which is weird //Couldn't figure out a better way though RegisterSignal(loop, COMSIG_MOVELOOP_POSTPROCESS, PROC_REF(manage_chair_speed)) diff --git a/code/game/objects/structures/life_candle.dm b/code/game/objects/structures/life_candle.dm index d9eb81c783c..b21d14619db 100644 --- a/code/game/objects/structures/life_candle.dm +++ b/code/game/objects/structures/life_candle.dm @@ -3,7 +3,11 @@ desc = "You are dead. Insert quarter to continue." icon = 'icons/obj/candle.dmi' icon_state = "candle1" + light_system = OVERLAY_LIGHT light_color = LIGHT_COLOR_FIRE + light_power = 1.5 + light_range = 2 + light_on = FALSE var/icon_state_active = "candle1_lit" var/icon_state_inactive = "candle1" @@ -23,11 +27,21 @@ // How long until we respawn them after their death. var/respawn_time = 50 var/respawn_sound = 'sound/effects/magic/staff_animation.ogg' + /// Lighting middleman, lets us do a flicker effect + var/datum/light_middleman/middleman /obj/structure/life_candle/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/movetype_handler) +/obj/structure/life_candle/Destroy(force) + QDEL_NULL(middleman) + return ..() + /obj/structure/life_candle/attack_hand(mob/user, list/modifiers) . = ..() if(.) @@ -48,10 +62,10 @@ update_appearance() if(linked_minds.len) START_PROCESSING(SSobj, src) - set_light(lit_luminosity) + set_light_on(TRUE) else STOP_PROCESSING(SSobj, src) - set_light(0) + set_light_on(FALSE) /obj/structure/life_candle/update_icon_state() icon_state = linked_minds.len ? icon_state_active : icon_state_inactive @@ -74,6 +88,10 @@ if(!mind.current || (mind.current && mind.current.stat == DEAD)) addtimer(CALLBACK(src, PROC_REF(respawn), mind), respawn_time, TIMER_UNIQUE) +/obj/structure/life_candle/proc/light_updated(datum/source) + SIGNAL_HANDLER + fire_flicker_middleman(middleman) + /obj/structure/life_candle/proc/respawn(datum/mind/mind) var/turf/T = get_turf(src) var/mob/living/body diff --git a/code/game/world.dm b/code/game/world.dm index e2370fedb1f..a243dd09e38 100644 --- a/code/game/world.dm +++ b/code/game/world.dm @@ -276,6 +276,12 @@ GLOBAL_VAR(restart_counter) world.log = file("[GLOB.log_directory]/dd.log") //not all runtimes trigger world/Error, so this is the only way to ensure we can see all of them. #endif +/// The world.time we last ran maptick, used for stupid reasons +GLOBAL_VAR_INIT(last_maptick_time, 0) +/world/Tick() + // We need a hook for if maptick has happen yet + GLOB.last_maptick_time = world.time + /world/Topic(T, addr, master, key) TGS_TOPIC //redirect to server tools if necessary diff --git a/code/modules/atmospherics/machinery/datum_pipeline.dm b/code/modules/atmospherics/machinery/datum_pipeline.dm index 9d9a5a6292f..1c713f47852 100644 --- a/code/modules/atmospherics/machinery/datum_pipeline.dm +++ b/code/modules/atmospherics/machinery/datum_pipeline.dm @@ -259,7 +259,7 @@ var/volume_sum = 0 var/static/process_id = 0 - process_id = (process_id + 1) % (SHORT_REAL_LIMIT - 1) + process_id = WRAP_UID(process_id + 1) for(var/datum/gas_mixture/gas_mixture as anything in gas_mixture_list) // Ensure we never walk the same mix twice diff --git a/code/modules/library/lib_machines.dm b/code/modules/library/lib_machines.dm index 62266481659..50e6d0ac22a 100644 --- a/code/modules/library/lib_machines.dm +++ b/code/modules/library/lib_machines.dm @@ -13,7 +13,7 @@ GLOBAL_VAR_INIT(library_table_modified, 0) /// Increments every time WE update the library db table, causes all existing consoles to repull when they next check /proc/library_updated() - GLOB.library_table_modified = (GLOB.library_table_modified + 1) % (SHORT_REAL_LIMIT - 1) + GLOB.library_table_modified = WRAP_UID(GLOB.library_table_modified + 1) /* * Library Public Computer diff --git a/code/modules/lighting/light_middleman.dm b/code/modules/lighting/light_middleman.dm new file mode 100644 index 00000000000..229c49a4edf --- /dev/null +++ b/code/modules/lighting/light_middleman.dm @@ -0,0 +1,160 @@ +/// Allows us to intercept overlay lighting's well, light overlays +/// Normally these are static, but by giving them a render source and copying their base appearance +/// Animating this datum's child objects allows us to do SO much fun stuff +/datum/light_middleman + /// Owning parent we're interceeding for + /// Could in theory be a turf but lies to areas means we have to pick something to type it as + var/atom/movable/parent + /// The holder we are currently displaying our light on + var/atom/movable/light_holder + /// Holds the primary light source + var/obj/effect/abstract/light_middleman/primary_intercept + /// Exists to hold the cone so children can modify it if they want + var/obj/effect/abstract/light_middleman/cone_intercept + /// Are we overriding the light already? + var/overriding = FALSE + /// Weakref to the object we are displaying our effects on + var/datum/weakref/holder_ref + +/datum/light_middleman/New(atom/parent, unique_string) + . = ..() + if(!IS_OVERLAY_LIGHT_SYSTEM(parent.light_system)) + stack_trace("Attempted to create a light middleman with a parent [parent.type] that does not use overlay lighting! This will not work.") + if(isturf(parent)) + stack_trace("Warning, becuase overlay lights are basically never used on turfs, since they don't move,\ + vis contents replacement has not yet been implemented for them (see changeturf for why this is needed)!") + src.parent = parent + primary_intercept = new() + cone_intercept = new() + var/static/uuid = 0 + uuid = WRAP_UID(uuid + 1) + primary_intercept.render_target = "*[unique_string]_[uuid]_target" + cone_intercept.render_target = "[primary_intercept.render_target]_cone" // made to mirror how overlay lights work + +/datum/light_middleman/Destroy(force) + stop_overriding_light() + QDEL_NULL(primary_intercept) + QDEL_NULL(cone_intercept) + parent = null + light_holder = null + return ..() + +/datum/light_middleman/proc/being_overriding_light(unique_string) + if(overriding) + return + overriding = TRUE + // We register here because our later set render source will always trigger a refresh and thus let us capture appearances properly + // Assuming there's an overlay light on the other side + RegisterSignal(parent, COMSIG_ATOM_OVERLAY_LIGHT_APPLIED, PROC_REF(light_applied)) + RegisterSignal(parent, COMSIG_ATOM_OVERLAY_LIGHT_REMOVED, PROC_REF(light_removed)) + parent.set_light_render_source(primary_intercept.render_target) + +/datum/light_middleman/proc/stop_overriding_light() + if(!overriding) + return + overriding = FALSE + UnregisterSignal(parent, COMSIG_ATOM_OVERLAY_LIGHT_APPLIED) + UnregisterSignal(parent, COMSIG_ATOM_OVERLAY_LIGHT_REMOVED) + var/atom/movable/old_holder = holder_ref?.resolve() + if(old_holder) + old_holder.vis_contents -= primary_intercept + old_holder.vis_contents -= cone_intercept + holder_ref = null + parent.set_light_render_source("") + +/datum/light_middleman/proc/light_applied(datum/source, image/visible_mask, image/cone, atom/movable/light_holder) + SIGNAL_HANDLER + var/atom/movable/old_holder = holder_ref?.resolve() + // If we were somewhere before, clean us out + if(old_holder) + old_holder.vis_contents -= primary_intercept + old_holder.vis_contents -= cone_intercept + holder_ref = null + + // how we make sure we're in the client's view + light_holder.vis_contents += primary_intercept + // Avoids unneeded effects clientside + if(IS_OVERLAY_CONE_LIGHT_SYSTEM(parent.light_system)) + light_holder.vis_contents += cone_intercept + + old_holder = WEAKREF(light_holder) + + var/old_target = primary_intercept.render_target + var/old_cone_target = cone_intercept.render_target + // This will halt any animations we have ongoing so if you care about that you've gotta react to it properly + primary_intercept.appearance = visible_mask + cone_intercept.appearance = cone + // set ourselves up to render back onto the visible mask + primary_intercept.render_source = "" + primary_intercept.render_target = old_target + cone_intercept.render_source = "" + cone_intercept.render_target = old_cone_target + // Dir is important I'm told + primary_intercept.vis_flags |= VIS_INHERIT_DIR + cone_intercept.vis_flags |= VIS_INHERIT_DIR + // Will double apply, here we go gang + primary_intercept.transform = null + cone_intercept.transform = null + primary_intercept.color = null + cone_intercept.color = null + primary_intercept.alpha = 255 + cone_intercept.alpha = 255 + // Sometimes can be BLEND_SUBTRACT, we don't want that + primary_intercept.blend_mode = BLEND_ADD + cone_intercept.blend_mode = BLEND_ADD + /// Allows users to hook into a refresh so they can remake their modifications to our intercepts + SEND_SIGNAL(src, COMSIG_LIGHT_MIDDLEMAN_UPDATED) + +/datum/light_middleman/proc/light_removed(datum/source, atom/movable/light_holder) + SIGNAL_HANDLER + light_holder.vis_contents -= primary_intercept + light_holder.vis_contents -= cone_intercept + holder_ref = null + +/// Just... cause it's better then not having a bespoke type +/obj/effect/abstract/light_middleman + +// Procs for reuse on multiple types +/proc/fire_flicker_middleman(datum/light_middleman/middleman) + var/obj/effect/abstract/main_light = middleman.primary_intercept + // Just in case a subtype is wildin + var/obj/effect/abstract/cone_light = middleman.cone_intercept + + /// Applies a nice random flicker to flares and their subtypes which will I hope sell the fire effect better + var/list/random_times = list() + for(var/i in 1 to 17) + // Makes a nice upside down U distribution + var/random_down = LERP(-0.075 SECONDS, 0.075 SECONDS, ANCHORED_INVERSE_CAUCHY(0.55)) + var/random_bottom = LERP(-0.05 SECONDS, 0.05 SECONDS, ANCHORED_INVERSE_CAUCHY(0.55)) + var/random_up = LERP(-0.075 SECONDS, 0.075 SECONDS, ANCHORED_INVERSE_CAUCHY(0.55)) + // We want a potentially quite long "top end" so the flicker can be an actual flicker instead of a heartbeat (that's the goal at least) + var/random_top = LERP(-0.225 SECONDS, 0.225 SECONDS, ANCHORED_INVERSE_CAUCHY(0.55)) + random_times += list(list( + 0.125 SECONDS + random_down, + 0 SECONDS + random_bottom, + 0.125 SECONDS + random_up, + 0.275 SECONDS + random_top, + )) + // Going to loop our alpha high and low semi quickly to mimik a flickering fire/flare + var/list/first_time = random_times[1] + animate(main_light, alpha = 235, time = first_time[1], easing = CUBIC_EASING|EASE_OUT, loop = -1) + animate(alpha = 235, time = first_time[2]) + animate(alpha = 255, time = first_time[3], easing = CUBIC_EASING|EASE_OUT) + animate(alpha = 255, time = first_time[4]) + for(var/list/time in random_times - first_time) + animate(alpha = 235, time = time[1], easing = CUBIC_EASING|EASE_OUT) + animate(alpha = 235, time = first_time[2]) + animate(alpha = 255, time = first_time[3], easing = CUBIC_EASING|EASE_OUT) + animate(alpha = 255, time = first_time[4]) + + // I'd really love to do both these in the same loop but parallel animations are the devil from the bible + // I don't think any of these are directional but just in case + animate(cone_light, alpha = 235, time = first_time[1], easing = CUBIC_EASING|EASE_OUT, loop = -1) + animate(alpha = 235, time = first_time[2]) + animate(alpha = 255, time = first_time[3], easing = CUBIC_EASING|EASE_OUT) + animate(alpha = 255, time = first_time[4]) + for(var/list/time in random_times - first_time) + animate(alpha = 235, time = time[1], easing = CUBIC_EASING|EASE_OUT) + animate(alpha = 235, time = first_time[2]) + animate(alpha = 255, time = first_time[3], easing = CUBIC_EASING|EASE_OUT) + animate(alpha = 255, time = first_time[4]) diff --git a/code/modules/lighting/lighting_atom.dm b/code/modules/lighting/lighting_atom.dm index b671de56cfc..fef97f72942 100644 --- a/code/modules/lighting/lighting_atom.dm +++ b/code/modules/lighting/lighting_atom.dm @@ -139,6 +139,43 @@ SEND_SIGNAL(src, COMSIG_ATOM_UPDATE_LIGHT_COLOR, .) return . +/// Setter for whether or not this atom's light is on. +/atom/proc/set_light_on(new_value) + if(new_value == light_on || light_flags & LIGHT_FROZEN) + return + if(SEND_SIGNAL(src, COMSIG_ATOM_SET_LIGHT_ON, new_value) & COMPONENT_BLOCK_LIGHT_UPDATE) + return + . = light_on + light_on = new_value + SEND_SIGNAL(src, COMSIG_ATOM_UPDATE_LIGHT_ON, .) + return . + +/// Setter for the light flags of this atom. +/atom/proc/set_light_flags(new_value) + if(new_value == light_flags || (light_flags & LIGHT_FROZEN && new_value & LIGHT_FROZEN)) + return + if(SEND_SIGNAL(src, COMSIG_ATOM_SET_LIGHT_FLAGS, new_value) & COMPONENT_BLOCK_LIGHT_UPDATE) + return + . = light_flags + light_flags = new_value + SEND_SIGNAL(src, COMSIG_ATOM_UPDATE_LIGHT_FLAGS, .) + return . + +// procs that only apply to OVERLAY_LIGHT + +/// Setter for an optional render_source to apply to this atom's light overlay +/atom/proc/set_light_render_source(new_source) + if(new_source == light_flags || light_flags & LIGHT_FROZEN) + return + if(SEND_SIGNAL(src, COMSIG_ATOM_SET_LIGHT_RENDER_SOURCE, new_source) & COMPONENT_BLOCK_LIGHT_UPDATE) + return + . = light_render_source + light_render_source = new_source + SEND_SIGNAL(src, COMSIG_ATOM_SET_LIGHT_RENDER_SOURCE, .) + return . + +// procs that only apply to COMPLEX_LIGHT + /// Setter for the light angle of this atom /atom/proc/set_light_angle(new_value) if(new_value == light_angle || light_flags & LIGHT_FROZEN) @@ -162,17 +199,6 @@ SEND_SIGNAL(src, COMSIG_ATOM_UPDATE_LIGHT_DIR, .) return . -/// Setter for whether or not this atom's light is on. -/atom/proc/set_light_on(new_value) - if(new_value == light_on || light_flags & LIGHT_FROZEN) - return - if(SEND_SIGNAL(src, COMSIG_ATOM_SET_LIGHT_ON, new_value) & COMPONENT_BLOCK_LIGHT_UPDATE) - return - . = light_on - light_on = new_value - SEND_SIGNAL(src, COMSIG_ATOM_UPDATE_LIGHT_ON, .) - return . - /// Setter for the height of our light /atom/proc/set_light_height(new_value) if(new_value == light_height || light_flags & LIGHT_FROZEN) @@ -184,16 +210,6 @@ SEND_SIGNAL(src, COMSIG_ATOM_UPDATE_LIGHT_HEIGHT, .) return . -/// Setter for the light flags of this atom. -/atom/proc/set_light_flags(new_value) - if(new_value == light_flags || (light_flags & LIGHT_FROZEN && new_value & LIGHT_FROZEN)) - return - if(SEND_SIGNAL(src, COMSIG_ATOM_SET_LIGHT_FLAGS, new_value) & COMPONENT_BLOCK_LIGHT_UPDATE) - return - . = light_flags - light_flags = new_value - SEND_SIGNAL(src, COMSIG_ATOM_UPDATE_LIGHT_FLAGS, .) - return . /atom/proc/get_light_offset() return list(0, 0) diff --git a/code/modules/mining/mine_items.dm b/code/modules/mining/mine_items.dm index ea0345f78d0..b23f7b9ba9a 100644 --- a/code/modules/mining/mine_items.dm +++ b/code/modules/mining/mine_items.dm @@ -411,7 +411,7 @@ return setDir(movedir) - var/datum/move_loop/loop = GLOB.move_manager.move(src, dir, delay = calculate_delay(), subsystem = SSconveyors, flags = MOVEMENT_LOOP_START_FAST|MOVEMENT_LOOP_IGNORE_PRIORITY) + var/datum/move_loop/loop = GLOB.move_manager.move(src, dir, delay = calculate_delay(), subsystem = SSconveyors, flags = MOVEMENT_LOOP_START_INSTANT|MOVEMENT_LOOP_IGNORE_PRIORITY) RegisterSignal(loop, COMSIG_MOVELOOP_PREPROCESS_CHECK, PROC_REF(check_rail)) RegisterSignal(loop, COMSIG_MOVELOOP_POSTPROCESS, PROC_REF(decay_momentum)) diff --git a/code/modules/vehicles/mecha/equipment/tools/other_tools.dm b/code/modules/vehicles/mecha/equipment/tools/other_tools.dm index 3a878ad6609..6b4e4e77ffe 100644 --- a/code/modules/vehicles/mecha/equipment/tools/other_tools.dm +++ b/code/modules/vehicles/mecha/equipment/tools/other_tools.dm @@ -127,7 +127,7 @@ /obj/item/mecha_parts/mecha_equipment/gravcatapult/proc/do_scatter(atom/movable/scatter, atom/movable/target) var/dist = 5 - get_dist(scatter, target) var/delay = 2 - GLOB.move_manager.move_away(scatter, target, delay = delay, timeout = delay * dist, flags = MOVEMENT_LOOP_START_FAST, priority = MOVEMENT_ABOVE_SPACE_PRIORITY) + GLOB.move_manager.move_away(scatter, target, delay = delay, timeout = delay * dist, flags = MOVEMENT_LOOP_START_INSTANT, priority = MOVEMENT_ABOVE_SPACE_PRIORITY) /obj/item/mecha_parts/mecha_equipment/gravcatapult/get_snowflake_data() return list( diff --git a/tgstation.dme b/tgstation.dme index 67e316b9900..b28377361c5 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -352,6 +352,7 @@ #include "code\__DEFINES\dcs\signals\signals_leash.dm" #include "code\__DEFINES\dcs\signals\signals_lift.dm" #include "code\__DEFINES\dcs\signals\signals_light_eater.dm" +#include "code\__DEFINES\dcs\signals\signals_light_intercept.dm" #include "code\__DEFINES\dcs\signals\signals_lockable_storage.dm" #include "code\__DEFINES\dcs\signals\signals_market.dm" #include "code\__DEFINES\dcs\signals\signals_material_container.dm" @@ -4826,6 +4827,7 @@ #include "code\modules\library\skill_learning\job_skillchips\research_director.dm" #include "code\modules\library\skill_learning\job_skillchips\roboticist.dm" #include "code\modules\library\skill_learning\job_skillchips\station_engineer.dm" +#include "code\modules\lighting\light_middleman.dm" #include "code\modules\lighting\lighting_area.dm" #include "code\modules\lighting\lighting_atom.dm" #include "code\modules\lighting\lighting_corner.dm"