From 79fae4eda29d15b708532f8588686d21228e3035 Mon Sep 17 00:00:00 2001 From: SmArtKar <44720187+SmArtKar@users.noreply.github.com> Date: Sun, 8 Jun 2025 18:03:18 +0200 Subject: [PATCH] Fixes weird pipe/vent/cable rendering under glass and catwalk floors (#91454) ## About The Pull Request Changes some logic in underfloor object and atmos component rendering to make them look nicer when visible, but not accessible under floors. Also changes how catwalks get rendered to improve their visuals when opened. Before: ![image](https://github.com/user-attachments/assets/217070c6-3df7-4a25-8e83-557601f87d36) After: ![image](https://github.com/user-attachments/assets/e568176f-c116-4a30-8212-6c6b879ed847) ## Why It's Good For The Game Makes pipes look less shit. The rendering curse reigns eternal. ## Changelog :cl: fix: Fixed vents and pipes partially poking out from under glass and catwalk floors /:cl: --- code/__DEFINES/traits/declarations.dm | 3 ++ code/_globalvars/traits/_traits.dm | 1 + code/datums/components/acid.dm | 2 +- code/datums/elements/undertile.dm | 9 +++- .../effects/anomalies/anomalies_gravity.dm | 13 ++---- .../fluid_spread/effects_foam.dm | 2 +- .../fluid_spread/effects_smoke.dm | 2 +- code/game/objects/obj_defense.dm | 12 ++--- code/game/turfs/open/floor/catwalk_plating.dm | 9 +++- code/game/turfs/turf.dm | 2 +- .../components/binary_devices/dp_vent_pump.dm | 2 +- .../machinery/components/components_base.dm | 43 ++++++++++++++---- .../components/unary_devices/airlock_pump.dm | 2 +- .../unary_devices/bluespace_sender.dm | 2 +- .../unary_devices/outlet_injector.dm | 2 +- .../components/unary_devices/passive_vent.dm | 2 +- .../unary_devices/portables_connector.dm | 2 +- .../components/unary_devices/vent_pump.dm | 2 +- .../components/unary_devices/vent_scrubber.dm | 2 +- icons/turf/floors/catwalk_plating.dmi | Bin 4221 -> 3902 bytes 20 files changed, 72 insertions(+), 42 deletions(-) diff --git a/code/__DEFINES/traits/declarations.dm b/code/__DEFINES/traits/declarations.dm index 12c304782e1..bd11cc67b31 100644 --- a/code/__DEFINES/traits/declarations.dm +++ b/code/__DEFINES/traits/declarations.dm @@ -1508,4 +1508,7 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai /// Trait given to colorblind mobs #define TRAIT_COLORBLIND "colorblind" +/// Trait that signals to objects on this turf that its open (has UNDERFLOOR_INTERACTIBLE) but still covers them +#define TRAIT_UNCOVERED_TURF "uncovered_turf" + // END TRAIT DEFINES diff --git a/code/_globalvars/traits/_traits.dm b/code/_globalvars/traits/_traits.dm index 1e796d960d9..c9733faab87 100644 --- a/code/_globalvars/traits/_traits.dm +++ b/code/_globalvars/traits/_traits.dm @@ -772,6 +772,7 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_SPINNING_WEB_TURF" = TRAIT_SPINNING_WEB_TURF, "TRAIT_TURF_IGNORE_SLIPPERY" = TRAIT_TURF_IGNORE_SLIPPERY, "TRAIT_TURF_IGNORE_SLOWDOWN" = TRAIT_TURF_IGNORE_SLOWDOWN, + "TRAIT_UNCOVERED_TURF" = TRAIT_UNCOVERED_TURF, ), )) diff --git a/code/datums/components/acid.dm b/code/datums/components/acid.dm index 20762c4c945..3aa3a004262 100644 --- a/code/datums/components/acid.dm +++ b/code/datums/components/acid.dm @@ -159,7 +159,7 @@ GLOBAL_DATUM_INIT(acid_overlay, /mutable_appearance, mutable_appearance('icons/e var/applied_targets = 0 for(var/atom/movable/target_movable as anything in target_turf) // Don't apply acid to things under the turf - if(target_turf.underfloor_accessibility < UNDERFLOOR_INTERACTABLE && HAS_TRAIT(target_movable, TRAIT_T_RAY_VISIBLE)) + if(HAS_TRAIT(target_movable, TRAIT_UNDERFLOOR)) continue // Ignore mobs if turf_acid_ignores_mobs is TRUE if(turf_acid_ignores_mobs && ismob(target_movable)) diff --git a/code/datums/elements/undertile.dm b/code/datums/elements/undertile.dm index 1f79c308fa7..7c1eb85d52a 100644 --- a/code/datums/elements/undertile.dm +++ b/code/datums/elements/undertile.dm @@ -74,8 +74,13 @@ ADD_TRAIT(source, invisibility_trait, ELEMENT_TRAIT(type)) else - SET_PLANE_IMPLICIT(source, initial(source.plane)) - source.layer = initial(source.layer) + if(!HAS_TRAIT(source.loc, TRAIT_UNCOVERED_TURF)) + SET_PLANE_IMPLICIT(source, initial(source.plane)) + source.layer = initial(source.layer) + else + SET_PLANE_IMPLICIT(source, FLOOR_PLANE) + source.layer = BELOW_CATWALK_LAYER + REMOVE_TRAIT(source, TRAIT_UNDERFLOOR, REF(src)) if(invisibility_trait) diff --git a/code/game/objects/effects/anomalies/anomalies_gravity.dm b/code/game/objects/effects/anomalies/anomalies_gravity.dm index 8d90666b821..f2b97862f1b 100644 --- a/code/game/objects/effects/anomalies/anomalies_gravity.dm +++ b/code/game/objects/effects/anomalies/anomalies_gravity.dm @@ -52,14 +52,11 @@ if(!M.mob_negates_gravity()) step_towards(M,src) for(var/obj/O in range(0,src)) - if(!O.anchored) - if(isturf(O.loc)) - var/turf/T = O.loc - if(T.underfloor_accessibility < UNDERFLOOR_INTERACTABLE && HAS_TRAIT(O, TRAIT_T_RAY_VISIBLE)) - continue - var/mob/living/target = locate() in view(4,src) - if(target && !target.stat) - O.throw_at(target, 5, 10) + if(O.anchored || HAS_TRAIT(O, TRAIT_UNDERFLOOR)) + continue + var/mob/living/target = locate() in view(4,src) + if(target && !target.stat) + O.throw_at(target, 5, 10) //anomaly quickly contracts then slowly expands its ring animate(warp, time = seconds_per_tick*3, transform = matrix().Scale(0.5,0.5)) diff --git a/code/game/objects/effects/effect_system/fluid_spread/effects_foam.dm b/code/game/objects/effects/effect_system/fluid_spread/effects_foam.dm index c377b4bc9ce..786672c21d1 100644 --- a/code/game/objects/effects/effect_system/fluid_spread/effects_foam.dm +++ b/code/game/objects/effects/effect_system/fluid_spread/effects_foam.dm @@ -98,7 +98,7 @@ continue if(object.invisibility >= INVISIBILITY_ABSTRACT) // Don't foam landmarks please continue - if(turf_location.underfloor_accessibility < UNDERFLOOR_INTERACTABLE && HAS_TRAIT(object, TRAIT_T_RAY_VISIBLE)) + if(HAS_TRAIT(object, TRAIT_UNDERFLOOR)) continue if (HAS_TRAIT(loc, TRAIT_ELEVATED_TURF) && !HAS_TRAIT(object, TRAIT_ELEVATING_OBJECT)) continue // Do expose tables, don't expose items on tables diff --git a/code/game/objects/effects/effect_system/fluid_spread/effects_smoke.dm b/code/game/objects/effects/effect_system/fluid_spread/effects_smoke.dm index 0ad4df67645..20cabff91be 100644 --- a/code/game/objects/effects/effect_system/fluid_spread/effects_smoke.dm +++ b/code/game/objects/effects/effect_system/fluid_spread/effects_smoke.dm @@ -376,7 +376,7 @@ continue if(thing.invisibility >= INVISIBILITY_ABSTRACT) // Don't smoke landmarks please continue - if(location.underfloor_accessibility < UNDERFLOOR_INTERACTABLE && HAS_TRAIT(thing, TRAIT_T_RAY_VISIBLE)) + if(HAS_TRAIT(thing, TRAIT_UNDERFLOOR)) continue reagents.expose(thing, SMOKE_MACHINE, fraction) diff --git a/code/game/objects/obj_defense.dm b/code/game/objects/obj_defense.dm index fd58fe5fb0d..1d343314531 100644 --- a/code/game/objects/obj_defense.dm +++ b/code/game/objects/obj_defense.dm @@ -61,12 +61,8 @@ return TRUE /obj/blob_act(obj/structure/blob/B) - if (!..()) + if (!..() || HAS_TRAIT(src, TRAIT_UNDERFLOOR)) return - if(isturf(loc)) - var/turf/T = loc - if(T.underfloor_accessibility < UNDERFLOOR_INTERACTABLE && HAS_TRAIT(src, TRAIT_T_RAY_VISIBLE)) - return take_damage(400, BRUTE, MELEE, 0, get_dir(src, B)) /obj/attack_alien(mob/living/carbon/alien/adult/user, list/modifiers) @@ -127,10 +123,8 @@ ///Called when the obj is exposed to fire. /obj/fire_act(exposed_temperature, exposed_volume) - if(isturf(loc)) - var/turf/our_turf = loc - if(our_turf.underfloor_accessibility < UNDERFLOOR_INTERACTABLE && HAS_TRAIT(src, TRAIT_T_RAY_VISIBLE)) - return + if(HAS_TRAIT(src, TRAIT_UNDERFLOOR)) + return if(exposed_temperature && !(resistance_flags & FIRE_PROOF)) take_damage(clamp(0.02 * exposed_temperature, 0, 20), BURN, FIRE, 0) if(QDELETED(src)) // take_damage() can send our obj to an early grave, let's stop here if that happens diff --git a/code/game/turfs/open/floor/catwalk_plating.dm b/code/game/turfs/open/floor/catwalk_plating.dm index b1f81be75d3..79d616fd02f 100644 --- a/code/game/turfs/open/floor/catwalk_plating.dm +++ b/code/game/turfs/open/floor/catwalk_plating.dm @@ -41,16 +41,23 @@ underfloor_accessibility = UNDERFLOOR_INTERACTABLE layer = LOW_FLOOR_LAYER icon_state = "[catwalk_type]_below" + ADD_TRAIT(src, TRAIT_UNCOVERED_TURF, INNATE_TRAIT) else underfloor_accessibility = UNDERFLOOR_VISIBLE - layer = CATWALK_LAYER icon_state = "[catwalk_type]_above" + layer = initial(layer) + REMOVE_TRAIT(src, TRAIT_UNCOVERED_TURF, INNATE_TRAIT) levelupdate() user.balloon_alert(user, "[!covered ? "cover removed" : "cover added"]") tool.play_tool_sound(src) update_appearance() +/turf/open/floor/catwalk_floor/update_overlays() + . = ..() + if (!covered) + . += mutable_appearance(icon, "[catwalk_type]_overlay", CATWALK_LAYER, src, FLOOR_PLANE, appearance_flags = KEEP_APART) + /turf/open/floor/catwalk_floor/crowbar_act(mob/user, obj/item/crowbar) if(covered) user.balloon_alert(user, "remove cover first!") diff --git a/code/game/turfs/turf.dm b/code/game/turfs/turf.dm index 56ca43ea3e2..f4a1010dd32 100644 --- a/code/game/turfs/turf.dm +++ b/code/game/turfs/turf.dm @@ -539,7 +539,7 @@ GLOBAL_LIST_EMPTY(station_turfs) /turf/singularity_act() if(underfloor_accessibility < UNDERFLOOR_INTERACTABLE) for(var/obj/on_top in contents) //this is for deleting things like wires contained in the turf - if(HAS_TRAIT(on_top, TRAIT_T_RAY_VISIBLE)) + if(HAS_TRAIT(on_top, TRAIT_UNDERFLOOR)) on_top.singularity_act() ScrapeAway(flags = CHANGETURF_INHERIT_AIR) return(2) diff --git a/code/modules/atmospherics/machinery/components/binary_devices/dp_vent_pump.dm b/code/modules/atmospherics/machinery/components/binary_devices/dp_vent_pump.dm index 58e1f619a73..338be57fbbb 100644 --- a/code/modules/atmospherics/machinery/components/binary_devices/dp_vent_pump.dm +++ b/code/modules/atmospherics/machinery/components/binary_devices/dp_vent_pump.dm @@ -27,7 +27,7 @@ /obj/machinery/atmospherics/components/binary/dp_vent_pump/update_icon_nopipes() cut_overlays() - if(showpipe) + if(underfloor_state) var/image/cap = get_pipe_image(icon, "dpvent_cap", dir, pipe_color, piping_layer = piping_layer) cap.appearance_flags |= RESET_COLOR|KEEP_APART add_overlay(cap) diff --git a/code/modules/atmospherics/machinery/components/components_base.dm b/code/modules/atmospherics/machinery/components/components_base.dm index 40abb89921d..a764b29e3f1 100644 --- a/code/modules/atmospherics/machinery/components/components_base.dm +++ b/code/modules/atmospherics/machinery/components/components_base.dm @@ -6,8 +6,8 @@ layer = GAS_PUMP_LAYER ///Is the component welded? var/welded = FALSE - ///Should the component should show the pipe underneath it? - var/showpipe = TRUE + ///Current underfloor_accessibility state, determines if the component should show the pipe underneath it and what plane it renders on. + var/underfloor_state = UNDERFLOOR_INTERACTABLE ///When the component is on a non default layer should we shift everything? Or just the underlay pipe var/shift_underlay_only = TRUE ///Stores the parent pipeline, used in components @@ -57,11 +57,11 @@ return ..() /** - * Called in on_hide(), set the showpipe var to true or false depending on the situation, calls update_icon() + * Called in on_hide(), set the underfloor_state var to true or false depending on the situation, calls update_icon() */ /obj/machinery/atmospherics/components/proc/hide_pipe(underfloor_accessibility) - showpipe = !!underfloor_accessibility - if(showpipe) + underfloor_state = underfloor_accessibility + if(underfloor_state) REMOVE_TRAIT(src, TRAIT_UNDERFLOOR, REF(src)) else ADD_TRAIT(src, TRAIT_UNDERFLOOR, REF(src)) @@ -73,16 +73,17 @@ underlays.Cut() color = null - SET_PLANE_IMPLICIT(src, showpipe ? GAME_PLANE : FLOOR_PLANE) - // Layer is handled in update_layer() + var/uncovered_turf = HAS_TRAIT(loc, TRAIT_UNCOVERED_TURF) + SET_PLANE_IMPLICIT(src, (underfloor_state == UNDERFLOOR_INTERACTABLE && !uncovered_turf) ? GAME_PLANE : FLOOR_PLANE) - if(!showpipe) + // Layer is handled in update_layer() + if(!underfloor_state) return ..() + if(pipe_flags & PIPING_DISTRO_AND_WASTE_LAYERS) return ..() var/connected = 0 //Direction bitset - var/underlay_pipe_layer = shift_underlay_only ? piping_layer : 3 for(var/i in 1 to device_type) //adds intact pieces @@ -92,6 +93,9 @@ var/node_dir = get_dir(src, node) var/mutable_appearance/pipe_appearance = mutable_appearance('icons/obj/pipes_n_cables/pipe_underlays.dmi', "intact_[node_dir]_[underlay_pipe_layer]", appearance_flags = RESET_COLOR|KEEP_APART) pipe_appearance.color = (node.pipe_color == ATMOS_COLOR_OMNI || istype(node, /obj/machinery/atmospherics/pipe/color_adapter)) ? pipe_color : node.pipe_color + if (underfloor_state == UNDERFLOOR_VISIBLE || uncovered_turf) + pipe_appearance.layer = BELOW_CATWALK_LAYER + get_pipe_layer_offset() + SET_PLANE_EXPLICIT(pipe_appearance, FLOOR_PLANE, src) underlays += pipe_appearance connected |= node_dir @@ -99,12 +103,22 @@ if((initialize_directions & direction) && !(connected & direction)) var/mutable_appearance/pipe_appearance = mutable_appearance('icons/obj/pipes_n_cables/pipe_underlays.dmi', "exposed_[direction]_[underlay_pipe_layer]", appearance_flags = RESET_COLOR|KEEP_APART) pipe_appearance.color = pipe_color + if (underfloor_state == UNDERFLOOR_VISIBLE || uncovered_turf) + pipe_appearance.layer = BELOW_CATWALK_LAYER + get_pipe_layer_offset() + SET_PLANE_EXPLICIT(pipe_appearance, FLOOR_PLANE, src) underlays += pipe_appearance if(!shift_underlay_only) PIPING_LAYER_SHIFT(src, piping_layer) return ..() +/obj/machinery/atmospherics/components/get_pipe_image(iconfile, iconstate, direction, color, piping_layer, trinary) + var/mutable_appearance/pipe_appearance = ..() + if (underfloor_state == UNDERFLOOR_VISIBLE || HAS_TRAIT(loc, TRAIT_UNCOVERED_TURF)) + pipe_appearance.layer = BELOW_CATWALK_LAYER + get_pipe_layer_offset() + SET_PLANE_EXPLICIT(pipe_appearance, FLOOR_PLANE, src) + return pipe_appearance + // Pipenet stuff; housekeeping /obj/machinery/atmospherics/components/nullify_node(i) @@ -342,7 +356,16 @@ connect_nodes() /obj/machinery/atmospherics/components/update_layer() - layer = (showpipe ? initial(layer) : BELOW_CATWALK_LAYER) + (piping_layer - PIPING_LAYER_DEFAULT) * PIPING_LAYER_LCHANGE + (GLOB.pipe_colors_ordered[pipe_color] * 0.001) + if (!underfloor_state) + layer = BELOW_CATWALK_LAYER + else if (PLANE_TO_TRUE(plane) == FLOOR_PLANE) + layer = ABOVE_OPEN_TURF_LAYER + else + layer = initial(layer) + layer += get_pipe_layer_offset() + +/obj/machinery/atmospherics/components/proc/get_pipe_layer_offset() + return (piping_layer - PIPING_LAYER_DEFAULT) * PIPING_LAYER_LCHANGE + (GLOB.pipe_colors_ordered[pipe_color] * 0.001) /** * Handles air relocation to the pipenet/environment diff --git a/code/modules/atmospherics/machinery/components/unary_devices/airlock_pump.dm b/code/modules/atmospherics/machinery/components/unary_devices/airlock_pump.dm index ada130d9ea0..1cc4c9867a3 100644 --- a/code/modules/atmospherics/machinery/components/unary_devices/airlock_pump.dm +++ b/code/modules/atmospherics/machinery/components/unary_devices/airlock_pump.dm @@ -89,7 +89,7 @@ /obj/machinery/atmospherics/components/unary/airlock_pump/update_overlays() . = ..() - if(!showpipe) + if(!underfloor_state) return var/mutable_appearance/distro_pipe_appearance = get_pipe_image(icon, "pipe_exposed", dir, COLOR_BLUE, piping_layer = 4) diff --git a/code/modules/atmospherics/machinery/components/unary_devices/bluespace_sender.dm b/code/modules/atmospherics/machinery/components/unary_devices/bluespace_sender.dm index 06b34d684ca..1d5000354f7 100644 --- a/code/modules/atmospherics/machinery/components/unary_devices/bluespace_sender.dm +++ b/code/modules/atmospherics/machinery/components/unary_devices/bluespace_sender.dm @@ -91,7 +91,7 @@ GLOBAL_LIST_EMPTY_TYPED(bluespace_senders, /obj/machinery/atmospherics/component /obj/machinery/atmospherics/components/unary/bluespace_sender/update_overlays() . = ..() . += get_pipe_image(icon, "pipe", dir, , piping_layer) - if(showpipe) + if(underfloor_state) . += get_pipe_image(icon, "pipe", initialize_directions) /obj/machinery/atmospherics/components/unary/bluespace_sender/process_atmos() diff --git a/code/modules/atmospherics/machinery/components/unary_devices/outlet_injector.dm b/code/modules/atmospherics/machinery/components/unary_devices/outlet_injector.dm index cdd01efa189..6a402a2ba0b 100644 --- a/code/modules/atmospherics/machinery/components/unary_devices/outlet_injector.dm +++ b/code/modules/atmospherics/machinery/components/unary_devices/outlet_injector.dm @@ -75,7 +75,7 @@ /obj/machinery/atmospherics/components/unary/outlet_injector/update_icon_nopipes() cut_overlays() - if(showpipe) + if(underfloor_state) // everything is already shifted so don't shift the cap var/image/cap = get_pipe_image(icon, "inje_cap", initialize_directions, pipe_color) cap.appearance_flags |= RESET_COLOR|KEEP_APART diff --git a/code/modules/atmospherics/machinery/components/unary_devices/passive_vent.dm b/code/modules/atmospherics/machinery/components/unary_devices/passive_vent.dm index a68876b797f..fc3622b892f 100644 --- a/code/modules/atmospherics/machinery/components/unary_devices/passive_vent.dm +++ b/code/modules/atmospherics/machinery/components/unary_devices/passive_vent.dm @@ -17,7 +17,7 @@ /obj/machinery/atmospherics/components/unary/passive_vent/update_icon_nopipes() cut_overlays() - if(showpipe) + if(underfloor_state) var/image/cap = get_pipe_image(icon, "vent_cap", initialize_directions, pipe_color) cap.appearance_flags |= RESET_COLOR|KEEP_APART add_overlay(cap) diff --git a/code/modules/atmospherics/machinery/components/unary_devices/portables_connector.dm b/code/modules/atmospherics/machinery/components/unary_devices/portables_connector.dm index e14d010ab4b..13b76f33826 100644 --- a/code/modules/atmospherics/machinery/components/unary_devices/portables_connector.dm +++ b/code/modules/atmospherics/machinery/components/unary_devices/portables_connector.dm @@ -32,7 +32,7 @@ /obj/machinery/atmospherics/components/unary/portables_connector/update_icon_nopipes() cut_overlays() - if(showpipe) + if(underfloor_state) var/image/cap = get_pipe_image(icon, "connector_cap", initialize_directions, pipe_color) cap.appearance_flags |= RESET_COLOR|KEEP_APART add_overlay(cap) diff --git a/code/modules/atmospherics/machinery/components/unary_devices/vent_pump.dm b/code/modules/atmospherics/machinery/components/unary_devices/vent_pump.dm index b07d275dffd..cc868e4fa64 100644 --- a/code/modules/atmospherics/machinery/components/unary_devices/vent_pump.dm +++ b/code/modules/atmospherics/machinery/components/unary_devices/vent_pump.dm @@ -189,7 +189,7 @@ /obj/machinery/atmospherics/components/unary/vent_pump/update_icon_nopipes() cut_overlays() - if(showpipe) + if(underfloor_state) var/image/cap = get_pipe_image(icon, "vent_cap", initialize_directions) add_overlay(cap) else diff --git a/code/modules/atmospherics/machinery/components/unary_devices/vent_scrubber.dm b/code/modules/atmospherics/machinery/components/unary_devices/vent_scrubber.dm index 25ce6ff4e33..6a57c08457a 100644 --- a/code/modules/atmospherics/machinery/components/unary_devices/vent_scrubber.dm +++ b/code/modules/atmospherics/machinery/components/unary_devices/vent_scrubber.dm @@ -120,7 +120,7 @@ /obj/machinery/atmospherics/components/unary/vent_scrubber/update_icon_nopipes() cut_overlays() - if(showpipe) + if(underfloor_state) var/image/cap = get_pipe_image(icon, "scrub_cap", initialize_directions) add_overlay(cap) else diff --git a/icons/turf/floors/catwalk_plating.dmi b/icons/turf/floors/catwalk_plating.dmi index 800d0c8dfffcdb408472963441180bcd44c083a9..898612b0fccd638b36471c341dd96e0facb3727c 100644 GIT binary patch literal 3902 zcmXX}c|26@`##5HOc+L%m}qz-B}JChBr}P!ghAF6S)<6CJ>HqqL?Rl6P?nLULKH%F zNmF(TuVfjLeb*Sq%>2gt`~GpB`#I-apZht_xv%T`+)oU_`~p%CEeHSr(%8triWjN8 z3qrtoW1O4oK3*8UbICr?z$3uzwzq$v_bopF2nCZ9yF|$9!qHvxhowI3<6fzgen6Km z!Qa0$@a2T2%Jh(_yo^)aWWVQV$!4$bJKek~)s(MF;zybAjN}Vp_O}eaUo)(}#W*eI z-B^?V&f7?AeR6j8VvJM)-$<+O4f~i^`lIU?E{hGP22 z{(fsaBW}bY{}Z$-3r{NS_EwbMQ|ob2*TQrB!};V@3co%$wYe+3@SIWWc$@mt;W6C| zC?E(uAv?p*O5+bTDFf_g2hLGEo+&p+bNG&3Rz160BJ2(Th{zfnoV^tKVT%C=Z8`J(Cd zWR({R`V#w;m5yrhKcVg+3aZ0zwwYNi&Pxn=S&z96*p{$PWV)Tvsi_Rl92{(`-)lJX zXJn+UcJtRl$mq7eOG~We@h30dzkmODeB711@p6gn$Q8E=(rsZ1l2v8n9dyu=e)*6%bN3ueBgv5tmnsXuxsqKC4S}Z~Ya)X4)S5Sm((IFAFEG z>c^=%th(z9P)XXH!w}H?^HiDB-A7w8d}BJcSqGn!p1pRSUSOAS0@_n8&vnMb2gWwm zm6xzfHNmw%Ek=s2Bz1#h7_!^IlT97XX?IQTRd14M+aGB9k(yeT%*6Yw6p-G7c$$*( zk~_b+7(7p@+n9MN<>1vgL9=+0p00YvWg{oVSpL90cQ22}mO1Ai8!75O*v*h4J_ks|1zq}`RqeX9DLbxT^3Nz!bnT2|5=J+v_V}6 zg(0F89#WI_5zhon4(?UPrvmc30CHAhh8(xMXz*-r=ob zQ@HG`vVpLv7Nx|mGz`0~6t=n=;LHE*Fto*uf4tum@MaBtthDV3w|#Tf+RiTg5V8LD zWNY=-)>cwy*46D~Y$1chyjG^gd}a3BR!kF?@X_m~X!>W2YnT2Pe)Znrvx2KaJpR)4cJ(xZ}ATEx=2_ z3mMkKE4?TwB14y(QKOj8UNdrd&AEG;`@!NfF3HSqq}Dj!A}ez_&%DGdPTH`7y((Vn zwb33a0&qvSJW~PzQOsfWKPA=d0M9x#xyHhHi_dYKGyL*EqYvKp8$X}p-WI-mbi&oC zVNtYyh*_2wn7g2}^UB87wqILYJ1DrGU&1mSeawDZ^)&BS?ejkMn3GBwmIV%vsndgGctD9ahv*)fL1V3kp=^8l4xW zV;cBcIT=rs+yRcSMJo+2JeEH>L5n(#mG{9%yEVO-gJw=hsXD^`g6su8VX1bbi zf*J$&2}xZM`1H<22)K3v@x962yRo!XqpR!wV+R4J_jk;WblyLnhP<~1{;fx~0>a#~ zHD+CAw4~;PSu00VHKkPV=*jnkVt8xdnvL|OLb{lWBaAO%=BG~oi>jqkF5pw^B4M&&NX|`QSf_|me7Tb z_G*`(X+X$%LbWFO@8J74b2xEw5T}o6SFh84AGy!fT&EJehx9wL;GXz(n##BKgTn< z3V;>UeUai73Gox8B0N#0JNNNXrMwA&>E7aek9sVPB(t1$_6z^pY_6iDr0}vVL zH-6Xv#}~c?BSlVyGhnaR=y7UjRcOpvsG=OW&T*TM&^iF267C7y$OEf_7iJaYI{AR9 z5)NJ4d!?kfSVU>9;U8A8_sYOF&7QOys3_Mm9ma@)jjHRle(|VS*Iiw|rRxf`fyNuIzmXIKq}G znqGM=q~2U(#S9Iq-;05kzo1Sn>KA4X{2~k)`n&zq5bp;WH-3x~BEYbH(2EV4X|(X6 zHme@!uZgT+G~si#eLKX^rcN_o=D|N<i1_mP&Hlj!5Wwh^6)FQP*W*;q7zZ2Nw(rv=?E_( zbG|q542Dey@X4HOA*wc>--r1Jk{L$-`v}GqK|NZkk=So6XA$KbC zat@{j3H;mgFvZBGPuzq}0|N^+-7`(vW?aKL7o|l%3KNjbX;cU{wL3$@8j-<(_Tx5H>`w*4VowO$gIHB3bp+m zNYb4~REUQH5LbFLYni>mr`TLU(iyVAG<~Ngju2V5KC*GFdAkXMy$ln#{?d`Y;=H!d z=fph+3_#LBj9q&`O*(gC>nZ0griE|B*S-cNfB(QoOx@oG_TJJ_K~ERS_Hfu?cj0u zeuM4d2tB8;&31wY*RL|`pWUbY#}YIE`gTYI$;NS88#tFKK=?g6S4XUiV#+kSQ(R8s zoWczpw@pn=W2P`oMR18z7!hr10P>Iq$NM#CSRAul122ck7lMYLonddac}!gndWVJ- zc6A5CCH9k$jR{j=WPrn*tC~~Q#U0&oiQ`G-JB*b0a7^ldnJTngi1qB(mgXVRu?E#V zT@3r-gvMgTmrT|g+`n4SWP=_KDm=-^wHCtz%h;c@Pu4X1zjiF8pTr6)AK_3z2X1B? zbRK!ATEyc5Z0igRt<7`Y-Tk2977fdr{m1fM-JUxtxM44lB>La8=}|0ouV=GC_32#i zgw@rwHz(!V?qF>@GDnRb;yMLBZ9*fNqSM;QS1&{!pn3YR7g)a`mk>3zwc{g7FE`*Cdeg ztl_Qht{bc%06bVJOq~S(9m$VB7ZPN$cA21lS3+{{Gd=`o-?7X@Mzky0M#!~@)CIiq zA)Pz`)n%RcC2>>ViI(PnV}P@CS;tWjwpIz4biXw$XBW-t6R#fO$ zLDV{`@vH*$=*i#0D63(wUCYeN7xJ1f_|C+796th>n&cx!G3v7QhN_zK(sBH)J3a1c zsp&jnMA}_4kfAt{y0xML0a-${Nu4=s3AivR!s literal 4221 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 HD&&6v?2t7g