From 68184a2e507fa923c6f35bd1df8535cc68b0d8fa Mon Sep 17 00:00:00 2001 From: LemonInTheDark <58055496+LemonInTheDark@users.noreply.github.com> Date: Thu, 20 Jul 2023 12:00:52 -0700 Subject: [PATCH] Stabilizes code that flicks overlays to view/all clients (#76937) ## About The Pull Request Rather then using images and displaying them with client.images, we can instead simply make an object, give it the passed in image/MA's appearance, and then vis_contents it where we want. If you want to animate things, you can just use the atom we return from the proc call. This ends up costing about 25% of the best case scenario (one guy online) It will save more time with more users, but it also allows us to avoid the hypersuffering that is passing GLOB.clients into the flick proc. So I think I'm happy enough with this. For context, here's average per call cost for flick_overlay_view() right now. It winds between 5e-5 and 1e-4. With these changes we should pretty consistently hit the low end of this, because none of our work really varies all that much. ![flick_avg](https://github.com/tgstation/tgstation/assets/58055496/3483e022-9cc5-490a-be5e-eb79f4e2110b) (I was using sswardrobe for this, but it ends up being a lot slower so like, why yaknow) ``` /atom/movable/flick_visual New: 3.65625ms Provide: 7.4375ms Qdel: 9.4375ms Stash: 9.46875ms ``` ## Why It's Good For The Game Using our tools should not make your code eat cpu time for no reason. Hearers is expensive, iterating clients is expensive, let's not be expensive. --- code/__HELPERS/game.dm | 43 ++++++++++++++---- code/controllers/subsystem/wardrobe.dm | 2 +- code/game/atoms.dm | 2 +- code/game/atoms_movable.dm | 2 +- code/game/machinery/scan_gate.dm | 4 +- code/game/objects/items.dm | 22 ++++----- .../objects/items/devices/laserpointer.dm | 12 ++--- .../crates_lockers/closets/cardboardbox.dm | 19 ++++---- .../objects/structures/gym/weight_machine.dm | 6 +-- code/game/turfs/closed/minerals.dm | 2 +- .../antagonists/heretic/magic/fire_blast.dm | 8 ++-- code/modules/hydroponics/hydroponics.dm | 4 +- code/modules/mob/living/living_defense.dm | 10 ++-- .../simple_animal/guardian/types/protector.dm | 2 +- .../simple_animal/hostile/blobbernaut.dm | 8 ++-- code/modules/reagents/reagent_containers.dm | 12 ++--- icons/turf/smoothrocks.dmi | Bin 3876 -> 3893 bytes 17 files changed, 91 insertions(+), 67 deletions(-) diff --git a/code/__HELPERS/game.dm b/code/__HELPERS/game.dm index b1b359bbb74..79979534211 100644 --- a/code/__HELPERS/game.dm +++ b/code/__HELPERS/game.dm @@ -139,8 +139,7 @@ for(var/client/remove_from in hide_from) remove_from.images -= image_to_remove - -///Add an image to a list of clients and calls a proc to remove it after a duration +/// Add an image to a list of clients and calls a proc to remove it after a duration /proc/flick_overlay_global(image/image_to_show, list/show_to, duration) if(!show_to || !length(show_to) || !image_to_show) return @@ -148,7 +147,7 @@ add_to.images += image_to_show addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(remove_image_from_clients), image_to_show, show_to), duration, TIMER_CLIENT_TIME) -/// Flicks a certain overlay onto an atom, handling icon_state strings +///Flicks a certain overlay onto an atom, handling icon_state strings /atom/proc/flick_overlay(image_to_show, list/show_to, duration, layer) var/image/passed_image = \ istext(image_to_show) \ @@ -157,13 +156,37 @@ flick_overlay_global(passed_image, show_to, duration) -/// flicks an overlay to anyone who can view this atom -/atom/proc/flick_overlay_view(image_to_show, duration) - var/list/viewing = list() - for(var/mob/viewer as anything in viewers(src)) - if(viewer.client) - viewing += viewer.client - flick_overlay(image_to_show, viewing, duration) +/** + * Helper atom that copies an appearance and exists for a period +*/ +/atom/movable/flick_visual + +/// Takes the passed in MA/icon_state, mirrors it onto ourselves, and displays that in world for duration seconds +/// Returns the displayed object, you can animate it and all, but you don't own it, we'll delete it after the duration +/atom/proc/flick_overlay_view(mutable_appearance/display, duration) + if(!display) + return null + + var/mutable_appearance/passed_appearance = \ + istext(display) \ + ? mutable_appearance(icon, display, layer) \ + : display + + // If you don't give it a layer, we assume you want it to layer on top of this atom + // Because this is vis_contents, we need to set the layer manually (you can just set it as you want on return if this is a problem) + if(passed_appearance.layer == FLOAT_LAYER) + passed_appearance.layer = layer + 0.1 + // This is faster then pooling. I promise + var/atom/movable/flick_visual/visual = new() + visual.appearance = passed_appearance + // I hate /area + var/atom/movable/lies_to_children = src + lies_to_children.vis_contents += visual + QDEL_IN_CLIENT_TIME(visual, duration) + return visual + +/area/flick_overlay_view(mutable_appearance/display, duration) + return ///Get active players who are playing in the round /proc/get_active_player_count(alive_check = FALSE, afk_check = FALSE, human_check = FALSE) diff --git a/code/controllers/subsystem/wardrobe.dm b/code/controllers/subsystem/wardrobe.dm index 58f81f86ad4..ac435cc9a6d 100644 --- a/code/controllers/subsystem/wardrobe.dm +++ b/code/controllers/subsystem/wardrobe.dm @@ -310,7 +310,7 @@ SUBSYSTEM_DEF(wardrobe) initial_callbacks[/obj/item/organ] = play_with play_with = new /list(WARDROBE_CALLBACK_REMOVE) - play_with[WARDROBE_CALLBACK_REMOVE] = CALLBACK(null, TYPE_PROC_REF(/obj/item/storage/box/survival,wardrobe_removal)) + play_with[WARDROBE_CALLBACK_REMOVE] = CALLBACK(null, TYPE_PROC_REF(/obj/item/storage/box/survival, wardrobe_removal)) initial_callbacks[/obj/item/storage/box/survival] = play_with /datum/controller/subsystem/wardrobe/proc/load_outfits() diff --git a/code/game/atoms.dm b/code/game/atoms.dm index 10b61d1051f..9379e45e5e5 100644 --- a/code/game/atoms.dm +++ b/code/game/atoms.dm @@ -60,7 +60,7 @@ ///overlays managed by [update_overlays][/atom/proc/update_overlays] to prevent removing overlays that weren't added by the same proc. Single items are stored on their own, not in a list. var/list/managed_overlays - /// Lazylist of all images (hopefully attached to us) to update when we change z levels + /// Lazylist of all images (or atoms, I'm sorry) (hopefully attached to us) to update when we change z levels /// You will need to manage adding/removing from this yourself, but I'll do the updating for you var/list/image/update_on_z diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm index 5b0f0b00e91..da14776f916 100644 --- a/code/game/atoms_movable.dm +++ b/code/game/atoms_movable.dm @@ -1166,7 +1166,7 @@ if(update_on_z) // I so much wish this could be somewhere else. alas, no. - for(var/image/update in update_on_z) + for(var/image/update as anything in update_on_z) SET_PLANE(update, PLANE_TO_TRUE(update.plane), new_turf) if(update_overlays_on_z) // This EVEN more so diff --git a/code/game/machinery/scan_gate.dm b/code/game/machinery/scan_gate.dm index 8fd41bcc9d9..49d5fab21f2 100644 --- a/code/game/machinery/scan_gate.dm +++ b/code/game/machinery/scan_gate.dm @@ -199,8 +199,8 @@ if(next_beep <= world.time) next_beep = world.time + (2 SECONDS) playsound(src, 'sound/machines/scanbuzz.ogg', 100, FALSE) - var/image/alarm_image = image(icon, src, "alarm_light", layer+1) - flick_overlay_view(alarm_image, 2 SECONDS) + var/mutable_appearance/alarm_display = mutable_appearance(icon, "alarm_light") + flick_overlay_view(alarm_display, 2 SECONDS) set_scanline("alarm", 2 SECONDS) /obj/machinery/scanner_gate/can_interact(mob/user) diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm index d594f3c538f..76dedcd323c 100644 --- a/code/game/objects/items.dm +++ b/code/game/objects/items.dm @@ -1348,7 +1348,7 @@ if(!istype(loc, /turf)) return source = loc - var/image/pickup_animation = image(icon = src, loc = source, layer = layer + 0.1) + var/image/pickup_animation = image(icon = src) SET_PLANE(pickup_animation, GAME_PLANE, source) pickup_animation.transform.Scale(0.75) pickup_animation.appearance_flags = APPEARANCE_UI_IGNORE_ALPHA @@ -1369,13 +1369,13 @@ to_y += 10 pickup_animation.pixel_x += 6 * (prob(50) ? 1 : -1) //6 to the right or left, helps break up the straight upward move - flick_overlay_global(pickup_animation, GLOB.clients, 4) - var/matrix/animation_matrix = new(pickup_animation.transform) + var/atom/movable/flick_visual/pickup = source.flick_overlay_view(pickup_animation, 0.4 SECONDS) + var/matrix/animation_matrix = new(pickup.transform) animation_matrix.Turn(pick(-30, 30)) animation_matrix.Scale(0.65) - animate(pickup_animation, alpha = 175, pixel_x = to_x, pixel_y = to_y, time = 3, transform = animation_matrix, easing = CUBIC_EASING) - animate(alpha = 0, transform = matrix().Scale(0.7), time = 1) + animate(pickup, alpha = 175, pixel_x = to_x, pixel_y = to_y, time = 0.3 SECONDS, transform = animation_matrix, easing = CUBIC_EASING) + animate(alpha = 0, transform = matrix().Scale(0.7), time = 0.1 SECONDS) /obj/item/proc/do_drop_animation(atom/moving_from) if(!istype(loc, /turf)) @@ -1422,9 +1422,9 @@ /atom/movable/proc/do_item_attack_animation(atom/attacked_atom, visual_effect_icon, obj/item/used_item) var/image/attack_image if(visual_effect_icon) - attack_image = image('icons/effects/effects.dmi', attacked_atom, visual_effect_icon, attacked_atom.layer + 0.1) + attack_image = image(icon = 'icons/effects/effects.dmi', icon_state = visual_effect_icon) else if(used_item) - attack_image = image(icon = used_item, loc = attacked_atom, layer = attacked_atom.layer + 0.1) + attack_image = image(icon = used_item) attack_image.plane = attacked_atom.plane + 1 // Scale the icon. @@ -1451,12 +1451,12 @@ if(!attack_image) return - flick_overlay_global(attack_image, GLOB.clients, 10) + var/atom/movable/flick_visual/attack = attacked_atom.flick_overlay_view(attack_image, 1 SECONDS) var/matrix/copy_transform = new(transform) // And animate the attack! - animate(attack_image, alpha = 175, transform = copy_transform.Scale(0.75), pixel_x = 0, pixel_y = 0, pixel_z = 0, time = 3) - animate(time = 1) - animate(alpha = 0, time = 3, easing = CIRCULAR_EASING|EASE_OUT) + animate(attack, alpha = 175, transform = copy_transform.Scale(0.75), pixel_x = 0, pixel_y = 0, pixel_z = 0, time = 0.3 SECONDS) + animate(time = 0.1 SECONDS) + animate(alpha = 0, time = 0.3 SECONDS, easing = CIRCULAR_EASING|EASE_OUT) /// Common proc used by painting tools like spraycans and palettes that can access the entire 24 bits color space. /obj/item/proc/pick_painting_tool_color(mob/user, default_color) diff --git a/code/game/objects/items/devices/laserpointer.dm b/code/game/objects/items/devices/laserpointer.dm index 380a7c82b56..45000bf4353 100644 --- a/code/game/objects/items/devices/laserpointer.dm +++ b/code/game/objects/items/devices/laserpointer.dm @@ -165,16 +165,16 @@ //laser pointer image icon_state = "pointer_[pointer_icon_state]" - var/image/I = image('icons/obj/weapons/guns/projectiles.dmi',targloc,pointer_icon_state,10) + var/mutable_appearance/laser = mutable_appearance('icons/obj/weapons/guns/projectiles.dmi', pointer_icon_state, 10) var/list/modifiers = params2list(params) if(modifiers) if(LAZYACCESS(modifiers, ICON_X)) - I.pixel_x = (text2num(LAZYACCESS(modifiers, ICON_X)) - 16) + laser.pixel_x = (text2num(LAZYACCESS(modifiers, ICON_X)) - 16) if(LAZYACCESS(modifiers, ICON_Y)) - I.pixel_y = (text2num(LAZYACCESS(modifiers, ICON_Y)) - 16) + laser.pixel_y = (text2num(LAZYACCESS(modifiers, ICON_Y)) - 16) else - I.pixel_x = target.pixel_x + rand(-5,5) - I.pixel_y = target.pixel_y + rand(-5,5) + laser.pixel_x = target.pixel_x + rand(-5,5) + laser.pixel_y = target.pixel_y + rand(-5,5) if(outmsg) to_chat(user, outmsg) @@ -190,7 +190,7 @@ to_chat(user, span_warning("[src]'s battery is overused, it needs time to recharge!")) recharge_locked = TRUE - targloc.flick_overlay_view(I, 10) + targloc.flick_overlay_view(laser, 1 SECONDS) icon_state = "pointer" /obj/item/laser_pointer/process(seconds_per_tick) diff --git a/code/game/objects/structures/crates_lockers/closets/cardboardbox.dm b/code/game/objects/structures/crates_lockers/closets/cardboardbox.dm index f504e89bee8..2771fe2bd76 100644 --- a/code/game/objects/structures/crates_lockers/closets/cardboardbox.dm +++ b/code/game/objects/structures/crates_lockers/closets/cardboardbox.dm @@ -81,18 +81,19 @@ /// Does the MGS ! animation /atom/proc/do_alert_animation() - var/image/alert_image = image('icons/obj/storage/closet.dmi', src, "cardboard_special", layer+1) - SET_PLANE_EXPLICIT(alert_image, ABOVE_LIGHTING_PLANE, src) - flick_overlay_view(alert_image, 0.8 SECONDS) - alert_image.alpha = 0 - animate(alert_image, pixel_z = 32, alpha = 255, time = 0.5 SECONDS, easing = ELASTIC_EASING) + var/mutable_appearance/alert = mutable_appearance('icons/obj/storage/closet.dmi', "cardboard_special") + SET_PLANE_EXPLICIT(alert, ABOVE_LIGHTING_PLANE, src) + var/atom/movable/flick_visual/exclamation = flick_overlay_view(alert, 1 SECONDS) + exclamation.alpha = 0 + animate(exclamation, pixel_z = 32, alpha = 255, time = 0.5 SECONDS, easing = ELASTIC_EASING) // We use this list to update plane values on parent z change, which is why we need the timer too // I'm sorry :( - LAZYADD(update_on_z, alert_image) - addtimer(CALLBACK(src, PROC_REF(forget_alert_image), alert_image), 0.8 SECONDS) + LAZYADD(update_on_z, exclamation) + // Intentionally less time then the flick so we don't get weird shit + addtimer(CALLBACK(src, PROC_REF(forget_alert), exclamation), 0.8 SECONDS, TIMER_CLIENT_TIME) -/atom/proc/forget_alert_image(image/alert_image) - LAZYREMOVE(update_on_z, alert_image) +/atom/proc/forget_alert(atom/movable/flick_visual/exclamation) + LAZYREMOVE(update_on_z, exclamation) /obj/structure/closet/cardboard/metal name = "large metal box" diff --git a/code/game/objects/structures/gym/weight_machine.dm b/code/game/objects/structures/gym/weight_machine.dm index ef156f1b645..3f6361a494d 100644 --- a/code/game/objects/structures/gym/weight_machine.dm +++ b/code/game/objects/structures/gym/weight_machine.dm @@ -105,9 +105,9 @@ if(!has_buckled_mobs()) end_workout() return FALSE - var/image/workout_icon = new(icon, src, "[base_icon_state]-o", ABOVE_MOB_LAYER) - workout_icon.plane = GAME_PLANE_UPPER - flick_overlay_view(workout_icon, 8) + var/mutable_appearance/workout = mutable_appearance(icon, "[base_icon_state]-o", ABOVE_MOB_LAYER) + SET_PLANE_EXPLICIT(workout, GAME_PLANE_UPPER, src) + flick_overlay_view(workout, 0.8 SECONDS) flick("[base_icon_state]-u", src) var/mob/living/user = buckled_mobs[1] animate(user, pixel_y = pixel_shift_y, time = 4) diff --git a/code/game/turfs/closed/minerals.dm b/code/game/turfs/closed/minerals.dm index 6be28e9c0b3..0b4561bf842 100644 --- a/code/game/turfs/closed/minerals.dm +++ b/code/game/turfs/closed/minerals.dm @@ -704,7 +704,7 @@ /turf/closed/mineral/gibtonite/proc/countdown(notify_admins = FALSE) set waitfor = FALSE while(istype(src, /turf/closed/mineral/gibtonite) && stage == GIBTONITE_ACTIVE && det_time > 0 && mineralAmt >= 1) - flick_overlay_view(image('icons/turf/smoothrocks.dmi', src, "rock_Gibtonite_active"), 5) //makes the animation pulse one time per tick + flick_overlay_view(mutable_appearance('icons/turf/smoothrocks.dmi', "rock_Gibtonite_active", ON_EDGED_TURF_LAYER + 0.1), 0.5 SECONDS) //makes the animation pulse one time per tick det_time-- sleep(0.5 SECONDS) if(istype(src, /turf/closed/mineral/gibtonite)) diff --git a/code/modules/antagonists/heretic/magic/fire_blast.dm b/code/modules/antagonists/heretic/magic/fire_blast.dm index 8900ea2350a..d3d3e0b2df0 100644 --- a/code/modules/antagonists/heretic/magic/fire_blast.dm +++ b/code/modules/antagonists/heretic/magic/fire_blast.dm @@ -129,10 +129,10 @@ /datum/status_effect/fire_blasted/on_apply() if(owner.on_fire && animate_duration > 0 SECONDS) - var/image/warning_sign = image(icon = 'icons/effects/effects.dmi', icon_state = "blessed", layer = BELOW_MOB_LAYER, loc = owner) - owner.flick_overlay_view(warning_sign, initial(duration)) - warning_sign.alpha = 50 - animate(warning_sign, alpha = 255, time = animate_duration) + var/mutable_appearance/warning_sign = mutable_appearance('icons/effects/effects.dmi', "blessed", BELOW_MOB_LAYER) + var/atom/movable/flick_visual/warning = owner.flick_overlay_view(warning_sign, initial(duration)) + warning.alpha = 50 + animate(warning, alpha = 255, time = animate_duration) return TRUE diff --git a/code/modules/hydroponics/hydroponics.dm b/code/modules/hydroponics/hydroponics.dm index f746a75bdfc..cee8b4e8cc9 100644 --- a/code/modules/hydroponics/hydroponics.dm +++ b/code/modules/hydroponics/hydroponics.dm @@ -861,9 +861,9 @@ // Beakers, bottles, buckets, etc. if(reagent_source.is_drainable()) playsound(loc, 'sound/effects/slosh.ogg', 25, TRUE) - var/image/splash_animation = image('icons/effects/effects.dmi', src, "splash_hydroponics") + var/mutable_appearance/splash_animation = mutable_appearance('icons/effects/effects.dmi', "splash_hydroponics") splash_animation.color = mix_color_from_reagents(reagent_source.reagents.reagent_list) - flick_overlay_global(splash_animation, GLOB.clients, 1.1 SECONDS) + flick_overlay_view(splash_animation, 1.1 SECONDS) if(visi_msg) visible_message(span_notice("[visi_msg].")) diff --git a/code/modules/mob/living/living_defense.dm b/code/modules/mob/living/living_defense.dm index 26538109f2a..d7b929fe30a 100644 --- a/code/modules/mob/living/living_defense.dm +++ b/code/modules/mob/living/living_defense.dm @@ -506,13 +506,13 @@ */ /mob/living/proc/do_slap_animation(atom/slapped) do_attack_animation(slapped, no_effect=TRUE) - var/image/gloveimg = image('icons/effects/effects.dmi', slapped, "slapglove", slapped.layer + 0.1) - gloveimg.pixel_y = 10 // should line up with head - gloveimg.pixel_x = 10 - flick_overlay_global(gloveimg, GLOB.clients, 10) + var/mutable_appearance/glove_appearance = mutable_appearance('icons/effects/effects.dmi', "slapglove") + glove_appearance.pixel_y = 10 // should line up with head + glove_appearance.pixel_x = 10 + var/atom/movable/flick_visual/glove = slapped.flick_overlay_view(glove_appearance, 1 SECONDS) // And animate the attack! - animate(gloveimg, alpha = 175, transform = matrix() * 0.75, pixel_x = 0, pixel_y = 10, pixel_z = 0, time = 3) + animate(glove, alpha = 175, transform = matrix() * 0.75, pixel_x = 0, pixel_y = 10, pixel_z = 0, time = 3) animate(time = 1) animate(alpha = 0, time = 3, easing = CIRCULAR_EASING|EASE_OUT) diff --git a/code/modules/mob/living/simple_animal/guardian/types/protector.dm b/code/modules/mob/living/simple_animal/guardian/types/protector.dm index 584041752a8..4809c3dc1c6 100644 --- a/code/modules/mob/living/simple_animal/guardian/types/protector.dm +++ b/code/modules/mob/living/simple_animal/guardian/types/protector.dm @@ -35,7 +35,7 @@ /mob/living/simple_animal/hostile/guardian/protector/adjustHealth(amount, updating_health = TRUE, forced = FALSE) . = ..() if(. > 0 && toggle) - var/image/flash_overlay = new('icons/effects/effects.dmi', src, "shield-flash", layer+0.01, dir = pick(GLOB.cardinals)) + var/image/flash_overlay = new('icons/effects/effects.dmi', src, "shield-flash", dir = pick(GLOB.cardinals)) flash_overlay.color = guardian_color flick_overlay_view(flash_overlay, 0.5 SECONDS) diff --git a/code/modules/mob/living/simple_animal/hostile/blobbernaut.dm b/code/modules/mob/living/simple_animal/hostile/blobbernaut.dm index 2162ff5d68b..dc1d038795f 100644 --- a/code/modules/mob/living/simple_animal/hostile/blobbernaut.dm +++ b/code/modules/mob/living/simple_animal/hostile/blobbernaut.dm @@ -71,13 +71,13 @@ return FALSE adjustHealth(maxHealth * BLOBMOB_BLOBBERNAUT_HEALTH_DECAY * damagesources * seconds_per_tick) //take 2.5% of max health as damage when not near the blob or if the naut has no factory, 5% if both - var/image/image = new('icons/mob/nonhuman-player/blob.dmi', src, "nautdamage", MOB_LAYER+0.01) - image.appearance_flags = RESET_COLOR + var/mutable_appearance/healing = mutable_appearance('icons/mob/nonhuman-player/blob.dmi', "nautdamage", MOB_LAYER+0.01) + healing.appearance_flags = RESET_COLOR if(overmind) - image.color = overmind.blobstrain.complementary_color + healing.color = overmind.blobstrain.complementary_color - flick_overlay_view(image, 8) + flick_overlay_view(healing, 0.8 SECONDS) /mob/living/simple_animal/hostile/blob/blobbernaut/AttackingTarget() . = ..() diff --git a/code/modules/reagents/reagent_containers.dm b/code/modules/reagents/reagent_containers.dm index 38126f8feb1..ee90e9a6bbb 100644 --- a/code/modules/reagents/reagent_containers.dm +++ b/code/modules/reagents/reagent_containers.dm @@ -140,11 +140,11 @@ playsound(target, 'sound/effects/slosh.ogg', 25, TRUE) - var/image/splash_animation = image('icons/effects/effects.dmi', target, "splash") + var/mutable_appearance/splash_animation = mutable_appearance('icons/effects/effects.dmi', "splash") if(isturf(target)) - splash_animation = image('icons/effects/effects.dmi', target, "splash_floor") + splash_animation.icon_state = "splash_floor" splash_animation.color = mix_color_from_reagents(reagents.reagent_list) - flick_overlay_global(splash_animation, GLOB.clients, 1.0 SECONDS) + target.flick_overlay_view(splash_animation, 1 SECONDS) for(var/datum/reagent/reagent as anything in reagents.reagent_list) reagent_text += "[reagent] ([num2text(reagent.volume)])," @@ -239,11 +239,11 @@ playsound(target, 'sound/effects/slosh.ogg', 25, TRUE) - var/image/splash_animation = image('icons/effects/effects.dmi', target, "splash") + var/mutable_appearance/splash_animation = mutable_appearance('icons/effects/effects.dmi', "splash") if(isturf(target)) - splash_animation = image('icons/effects/effects.dmi', target, "splash_floor") + splash_animation.icon_state = "splash_floor" splash_animation.color = mix_color_from_reagents(reagents.reagent_list) - flick_overlay_global(splash_animation, GLOB.clients, 1.0 SECONDS) + target.flick_overlay_view(splash_animation, 1.0 SECONDS) reagents.clear_reagents() diff --git a/icons/turf/smoothrocks.dmi b/icons/turf/smoothrocks.dmi index 0948aaaaa3b8d2d68b267125a0f337296ea59f1f..9a60937a2195a57d07c7d1b05dffb27c2f5a8a11 100644 GIT binary patch literal 3893 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 F_g|fg;2i(} literal 3876 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