Files
Charlie Nolan fb93e5a74a Assorted performance improvements (#25649)
* Improved efficiency of playsound.

* Improve performance of turf/Enter

* Improve performace of thrown objects.

* Improved performance of /atom/movable/CanPass.

* Improved pipeline destruction performance.

* Apply suggestions from code review

Co-authored-by: Luc <89928798+lewcc@users.noreply.github.com>
Co-authored-by: 1080pCat <96908085+1080pCat@users.noreply.github.com>
Signed-off-by: Charlie Nolan <funnyman3595@gmail.com>

---------

Signed-off-by: Charlie Nolan <funnyman3595@gmail.com>
Co-authored-by: FunnyMan3595 (Charlie Nolan) <funnyman@google.com>
Co-authored-by: Luc <89928798+lewcc@users.noreply.github.com>
Co-authored-by: 1080pCat <96908085+1080pCat@users.noreply.github.com>
2024-07-16 10:12:13 +00:00

660 lines
20 KiB
Plaintext

/turf
icon = 'icons/turf/floors.dmi'
level = 1
luminosity = 1
var/intact = TRUE
var/turf/baseturf = /turf/space
var/slowdown = 0 //negative for faster, positive for slower
/// used to check if pipes should be visible under the turf or not
var/transparent_floor = FALSE
/// Set if the turf should appear on a different layer while in-game and map editing, otherwise use normal layer.
var/real_layer = TURF_LAYER
layer = MAP_EDITOR_TURF_LAYER
///Icon-smoothing variable to map a diagonal wall corner with a fixed underlay.
var/list/fixed_underlay = null
///Properties for open tiles (/floor)
/// All the gas vars, on the turf, are meant to be utilized for initializing a gas datum and setting its first gas values; the turf vars are never further modified at runtime; it is never directly used for calculations by the atmospherics system.
var/oxygen = 0
var/carbon_dioxide = 0
var/nitrogen = 0
var/toxins = 0
var/sleeping_agent = 0
var/agent_b = 0
//Properties for airtight tiles (/wall)
var/thermal_conductivity = 0.05
var/heat_capacity = 1
//Properties for both
var/temperature = T20C
var/blocks_air = FALSE
flags = 0
var/image/obscured //camerachunks
var/changing_turf = FALSE
var/list/blueprint_data //for the station blueprints, images of objects eg: pipes
/// How pathing algorithm will check if this turf is passable by itself (not including content checks). By default it's just density check.
/// WARNING: Currently to use a density shortcircuiting this does not support dense turfs with special allow through function
var/pathing_pass_method = TURF_PATHING_PASS_DENSITY
/*
Atmos Vars
*/
/// The pressure difference between the turf and it's neighbors. Affects movables by pulling them in the path of least resistance
var/pressure_difference = 0
/// The direction movables should travel when affected by pressure. Set to the biggest difference in atmos by turf neighbors
var/pressure_direction = 0
/// makes turfs less picky about where they transfer gas. Largely just used in the SM
var/atmos_superconductivity = 0
/*
Lighting Vars
*/
/// Handles if the lighting should be dynamic. Generally lighting is dynamic if it's not in space
var/dynamic_lighting = TRUE
/// If you're curious why these are TMP vars, I don't know! TMP only affects savefiles so this does nothing :) - GDN
/// Is the lighting on this turf inited
var/tmp/lighting_corners_initialised = FALSE
/// List of light sources affecting this turf.
var/tmp/list/datum/light_source/affecting_lights
/// The lighting Object affecting us
var/tmp/atom/movable/lighting_object/lighting_object
/// A list of our lighting corners.
var/tmp/list/datum/lighting_corner/corners
/// Not to be confused with opacity, this will be TRUE if there's any opaque atom on the tile.
var/tmp/has_opaque_atom = FALSE
/// The general behavior of atmos on this tile.
var/atmos_mode = ATMOS_MODE_SEALED
/// The external environment that this tile is exposed to for ATMOS_MODE_EXPOSED_TO_ENVIRONMENT
var/atmos_environment
var/datum/gas_mixture/bound_to_turf/bound_air
/turf/Initialize(mapload)
SHOULD_CALL_PARENT(FALSE)
if(initialized)
stack_trace("Warning: [src]([type]) initialized multiple times!")
initialized = TRUE
if(layer == MAP_EDITOR_TURF_LAYER)
layer = real_layer
// by default, vis_contents is inherited from the turf that was here before
vis_contents.Cut()
levelupdate()
if(length(smoothing_groups))
sortTim(smoothing_groups) //In case it's not properly ordered, let's avoid duplicate entries with the same values.
SET_BITFLAG_LIST(smoothing_groups)
if(length(canSmoothWith))
sortTim(canSmoothWith)
if(canSmoothWith[length(canSmoothWith)] > MAX_S_TURF) //If the last element is higher than the maximum turf-only value, then it must scan turf contents for smoothing targets.
smoothing_flags |= SMOOTH_OBJ
SET_BITFLAG_LIST(canSmoothWith)
if(smoothing_flags & (SMOOTH_CORNERS|SMOOTH_BITMASK))
QUEUE_SMOOTH(src)
visibilityChanged()
for(var/atom/movable/AM in src)
Entered(AM)
var/area/A = loc
if(!IS_DYNAMIC_LIGHTING(src) && IS_DYNAMIC_LIGHTING(A))
add_overlay(/obj/effect/fullbright)
if(light_power && light_range)
update_light()
if(opacity)
has_opaque_atom = TRUE
initialize_milla()
return INITIALIZE_HINT_NORMAL
/turf/Destroy(force)
. = QDEL_HINT_IWILLGC
if(!changing_turf)
stack_trace("Incorrect turf deletion")
changing_turf = FALSE
if(force)
..()
//this will completely wipe turf state
var/turf/B = new world.turf(src)
for(var/A in B.contents)
qdel(A)
return
REMOVE_FROM_SMOOTH_QUEUE(src)
visibilityChanged()
QDEL_LIST_CONTENTS(blueprint_data)
initialized = FALSE
bound_air = null
..()
/turf/attack_hand(mob/user as mob)
user.Move_Pulled(src)
/turf/attack_robot(mob/user)
user.Move_Pulled(src)
/turf/attack_animal(mob/user)
user.Move_Pulled(src)
/turf/attack_alien(mob/living/carbon/alien/user)
user.Move_Pulled(src)
/turf/attack_larva(mob/user)
user.Move_Pulled(src)
/turf/attack_slime(mob/user)
user.Move_Pulled(src)
/turf/ex_act(severity)
return FALSE
/turf/rpd_act(mob/user, obj/item/rpd/our_rpd) //This is the default turf behaviour for the RPD; override it as required
if(our_rpd.mode == RPD_ATMOS_MODE)
our_rpd.create_atmos_pipe(user, src)
else if(our_rpd.mode == RPD_DISPOSALS_MODE)
for(var/obj/machinery/door/airlock/A in src)
if(A.density)
to_chat(user, "<span class='warning'>That type of pipe won't fit under [A]!</span>")
return
our_rpd.create_disposals_pipe(user, src)
else if(our_rpd.mode == RPD_TRANSIT_MODE)
our_rpd.create_transit_tube(user, src)
else if(our_rpd.mode == RPD_ROTATE_MODE)
our_rpd.rotate_all_pipes(user, src)
else if(our_rpd.mode == RPD_FLIP_MODE)
our_rpd.flip_all_pipes(user, src)
else if(our_rpd.mode == RPD_DELETE_MODE)
our_rpd.delete_all_pipes(user, src)
/turf/bullet_act(obj/item/projectile/Proj)
if(istype(Proj, /obj/item/projectile/bullet/gyro))
explosion(src, -1, 0, 2)
..()
return FALSE
/turf/Enter(atom/movable/mover as mob|obj, atom/forget)
if(!mover)
return TRUE
// First, make sure it can leave its square
if(isturf(mover.loc))
// Nothing but border objects stop you from leaving a tile, only one loop is needed
// This as anything looks odd, since we check istype shortly after, but it's so we can save an istype check for items, which are far more common than other objects.
for(var/obj/obstacle as anything in mover.loc)
if(isitem(obstacle) || !istype(obstacle) || obstacle == mover || obstacle == forget)
continue
if(!obstacle.CheckExit(mover, src))
mover.Bump(obstacle, TRUE)
return FALSE
var/list/large_dense = list()
// Next, check for border obstacles on this turf
// Everyting inside a turf is an atom/movable.
for(var/atom/movable/border_obstacle as anything in src)
if(isitem(border_obstacle) || border_obstacle == forget || isnull(border_obstacle))
continue
if(border_obstacle.flags & ON_BORDER)
if(!border_obstacle.CanPass(mover, mover.loc, 1))
mover.Bump(border_obstacle, TRUE)
return FALSE
else
large_dense += border_obstacle
//Then, check the turf itself
if(!src.CanPass(mover, src))
mover.Bump(src, TRUE)
return FALSE
// Finally, check objects/mobs that block entry and are not on the border
var/atom/movable/tompost_bump
var/top_layer = FALSE
for(var/atom/movable/obstacle as anything in large_dense)
if(obstacle.layer <= top_layer)
continue
if(!obstacle.CanPass(mover, mover.loc, 1))
tompost_bump = obstacle
top_layer = obstacle.layer
if(tompost_bump)
mover.Bump(tompost_bump, TRUE)
return FALSE
return TRUE //Nothing found to block so return success!
/turf/Entered(atom/movable/M, atom/OL, ignoreRest = FALSE)
..()
if(ismob(M))
var/mob/O = M
if(!O.lastarea)
O.lastarea = get_area(O.loc)
// If an opaque movable atom moves around we need to potentially update visibility.
if(M.opacity)
has_opaque_atom = TRUE // Make sure to do this before reconsider_lights(), incase we're on instant updates. Guaranteed to be on in this case.
reconsider_lights()
/turf/proc/levelupdate()
for(var/obj/O in src)
if(O.level == 1 && O.initialized) // Only do this if the object has initialized
O.hide(src.intact)
// override for space turfs, since they should never hide anything
/turf/space/levelupdate()
for(var/obj/O in src)
if(O.level == 1 && O.initialized) // Only do this if the object has initialized
O.hide(FALSE)
// Removes all signs of lattice on the pos of the turf -Donkieyo
/turf/proc/RemoveLattice()
var/obj/structure/lattice/L = locate(/obj/structure/lattice, src)
if(L && !(L.resistance_flags & INDESTRUCTIBLE))
qdel(L)
/turf/proc/dismantle_wall(devastated = FALSE, explode = FALSE)
return
/turf/proc/TerraformTurf(path, defer_change = FALSE, keep_icon = TRUE, ignore_air = FALSE)
return ChangeTurf(path, defer_change, keep_icon, ignore_air)
//Creates a new turf
/turf/proc/ChangeTurf(path, defer_change = FALSE, keep_icon = TRUE, ignore_air = FALSE, copy_existing_baseturf = TRUE)
if(!path)
return
if(!GLOB.use_preloader && path == type) // Don't no-op if the map loader requires it to be reconstructed
return src
set_light(0)
var/old_opacity = opacity
var/old_dynamic_lighting = dynamic_lighting
var/old_affecting_lights = affecting_lights
var/old_lighting_object = lighting_object
var/old_blueprint_data = blueprint_data
var/old_obscured = obscured
var/old_corners = corners
BeforeChange()
SEND_SIGNAL(src, COMSIG_TURF_CHANGE, path, defer_change, keep_icon, ignore_air, copy_existing_baseturf)
var/old_baseturf = baseturf
changing_turf = TRUE
qdel(src) //Just get the side effects and call Destroy
var/turf/W = new path(src)
if(copy_existing_baseturf)
W.baseturf = old_baseturf
if(!defer_change)
W.AfterChange(ignore_air)
W.blueprint_data = old_blueprint_data
recalc_atom_opacity()
if(SSlighting.initialized)
recalc_atom_opacity()
lighting_object = old_lighting_object
affecting_lights = old_affecting_lights
corners = old_corners
if(old_opacity != opacity || dynamic_lighting != old_dynamic_lighting)
reconsider_lights()
if(dynamic_lighting != old_dynamic_lighting)
if(IS_DYNAMIC_LIGHTING(src))
lighting_build_overlay()
else
lighting_clear_overlay()
for(var/turf/space/S in RANGE_TURFS(1, src)) //RANGE_TURFS is in code\__HELPERS\game.dm
S.update_starlight()
obscured = old_obscured
return W
/turf/proc/BeforeChange()
return
/turf/proc/is_safe()
return FALSE
// I'm including `ignore_air` because BYOND lacks positional-only arguments
/turf/proc/AfterChange(ignore_air = FALSE, keep_cabling = FALSE) //called after a turf has been replaced in ChangeTurf()
levelupdate()
initialize_milla()
recalculate_atmos_connectivity()
//update firedoor adjacency
var/list/turfs_to_check = get_adjacent_open_turfs(src) | src
for(var/turf/T in turfs_to_check)
for(var/obj/machinery/door/firedoor/FD in T)
FD.CalculateAffectingAreas()
// Check for weeds and either update, create or delete wall weeds
turfs_to_check = AdjacentTurfs(open_only = TRUE, cardinal_only = FALSE)
for(var/turf/T in turfs_to_check)
for(var/obj/structure/alien/weeds/W in T)
W.check_surroundings()
if(!keep_cabling && !can_have_cabling())
for(var/obj/structure/cable/C in contents)
qdel(C)
/turf/simulated/AfterChange(ignore_air = FALSE, keep_cabling = FALSE)
..()
RemoveLattice()
if(!ignore_air)
var/datum/milla_safe/turf_assimilate_air/milla = new()
milla.invoke_async(src)
/datum/milla_safe/turf_assimilate_air
/datum/milla_safe/turf_assimilate_air/on_run(turf/self)
if(isnull(self))
return
var/datum/gas_mixture/merged = new()
var/turf_count = 0
for(var/turf/T in self.GetAtmosAdjacentTurfs())
if(isspaceturf(T))
turf_count += 1
continue
if(T.blocks_air)
continue
merged.merge(get_turf_air(T))
turf_count += 1
if(turf_count > 0)
// Average the contents of the turfs.
merged.set_oxygen(merged.oxygen() / turf_count)
merged.set_nitrogen(merged.nitrogen() / turf_count)
merged.set_carbon_dioxide(merged.carbon_dioxide() / turf_count)
merged.set_toxins(merged.toxins() / turf_count)
merged.set_sleeping_agent(merged.sleeping_agent() / turf_count)
merged.set_agent_b(merged.agent_b() / turf_count)
get_turf_air(self).copy_from(merged)
/turf/proc/ReplaceWithLattice()
ChangeTurf(baseturf, keep_icon = FALSE)
new /obj/structure/lattice(locate(x, y, z))
/turf/proc/remove_plating(mob/user)
return
/turf/proc/kill_creatures(mob/U = null)//Will kill people/creatures and damage mechs./N
//Useful to batch-add creatures to the list.
for(var/mob/living/M in src)
if(M == U)
continue//Will not harm U. Since null != M, can be excluded to kill everyone.
INVOKE_ASYNC(M, TYPE_PROC_REF(/mob, gib))
for(var/obj/mecha/M in src)//Mecha are not gibbed but are damaged.
INVOKE_ASYNC(M, TYPE_PROC_REF(/obj/mecha, take_damage), 100, "brute")
/turf/proc/Bless()
flags |= BLESSED_TILE
/turf/proc/clean(floor_only)
for(var/obj/effect/decal/cleanable/C in src)
var/obj/effect/decal/cleanable/blood/B = C
if(istype(B) && B.off_floor)
floor_only = FALSE
else
qdel(C)
color = initial(color)
if(floor_only)
clean_blood()
for(var/mob/living/simple_animal/slime/M in src)
M.adjustToxLoss(rand(5, 10))
// Defined here to avoid runtimes
/turf/proc/MakeDry(wet_setting = TURF_WET_WATER)
return
/turf/proc/burn_down()
return
/// Returns the adjacent turfs. Can check for density or cardinal directions only instead of all 8, or just dense turfs entirely. dense_only takes precedence over open_only.
/turf/proc/AdjacentTurfs(open_only = FALSE, cardinal_only = FALSE, dense_only = FALSE)
var/list/L = list()
var/turf/T
var/list/directions = cardinal_only ? GLOB.cardinal : GLOB.alldirs
for(var/dir in directions)
T = get_step(src, dir)
if(!istype(T))
continue
if(dense_only && !T.density)
continue
if((open_only && T.density) && !dense_only)
continue
L.Add(T)
return L
/turf/proc/Distance(turf/T)
return get_dist(src, T)
/turf/acid_act(acidpwr, acid_volume)
. = TRUE
var/acid_type = /obj/effect/acid
if(acidpwr >= 200) //alien acid power
acid_type = /obj/effect/acid/alien
var/has_acid_effect = FALSE
for(var/obj/O in src)
if(intact && O.level == 1) //hidden under the floor
continue
if(istype(O, acid_type))
var/obj/effect/acid/A = O
A.acid_level = min(A.level + acid_volume * acidpwr, 12000)//capping acid level to limit power of the acid
has_acid_effect = 1
continue
O.acid_act(acidpwr, acid_volume)
if(!has_acid_effect)
new acid_type(src, acidpwr, acid_volume)
/turf/proc/acid_melt()
return
/turf/handle_fall()
if(has_gravity(src))
playsound(src, "bodyfall", 50, TRUE)
/turf/singularity_act()
if(intact)
for(var/obj/O in contents) //this is for deleting things like wires contained in the turf
if(O.level != 1)
continue
if(O.invisibility == INVISIBILITY_MAXIMUM)
O.singularity_act()
ChangeTurf(baseturf)
return 2
/turf/proc/visibilityChanged()
if(SSticker)
GLOB.cameranet.updateVisibility(src)
/turf/attackby(obj/item/I, mob/user, params)
if(can_lay_cable())
if(istype(I, /obj/item/stack/cable_coil))
var/obj/item/stack/cable_coil/C = I
for(var/obj/structure/cable/LC in src)
if(LC.d1 == 0 || LC.d2 == 0)
LC.attackby(C, user)
return
C.place_turf(src, user)
return TRUE
else if(istype(I, /obj/item/rcl))
var/obj/item/rcl/R = I
if(R.loaded)
for(var/obj/structure/cable/LC in src)
if(LC.d1 == 0 || LC.d2 == 0)
LC.attackby(R, user)
return
R.loaded.place_turf(src, user)
R.is_empty(user)
return FALSE
/turf/proc/can_have_cabling()
return TRUE
/turf/proc/can_lay_cable()
return can_have_cabling() && !intact
/*
* # power_list()
* returns a list power machinery on the turf and cables on the turf that have a direction equal to the one supplied in params and are currently connected to a powernet
*
* Arguments:
* source - the atom that is calling this proc
* direction - the direction that a cable must have in order to be returned in this proc i.e. d1 or d2 must equal direction
* cable_only - if TRUE, power_list will only return cables, if FALSE it will also return power machinery
*/
/turf/proc/power_list(atom/source, direction, cable_only = FALSE)
. = list()
for(var/obj/AM in src)
if(AM == source)
continue //we don't want to return source
if(istype(AM, /obj/structure/cable))
var/obj/structure/cable/C = AM
if(C.d1 == direction || C.d2 == direction)
. += C // one of the cables ends matches the supplied direction, add it to connnections
if(cable_only || direction)
continue
if(istype(AM, /obj/machinery/power) && !istype(AM, /obj/machinery/power/apc))
. += AM
/turf/proc/get_smooth_underlay_icon(mutable_appearance/underlay_appearance, turf/asking_turf, adjacency_dir)
underlay_appearance.icon = icon
underlay_appearance.icon_state = icon_state
underlay_appearance.dir = adjacency_dir
return TRUE
/turf/proc/add_blueprints(atom/movable/AM)
var/image/I = new
I.plane = GAME_PLANE
I.layer = OBJ_LAYER
I.appearance = AM.appearance
I.appearance_flags = RESET_COLOR|RESET_ALPHA|RESET_TRANSFORM
I.loc = src
I.dir = AM.dir
I.alpha = 128
LAZYADD(blueprint_data, I)
/turf/proc/add_blueprints_preround(atom/movable/AM)
if(!SSticker || SSticker.current_state != GAME_STATE_PLAYING)
add_blueprints(AM)
/turf/proc/empty(turf_type = /turf/space)
// Remove all atoms except observers, landmarks, docking ports, and (un)`simulated` atoms (lighting overlays)
var/turf/T0 = src
for(var/X in T0.GetAllContents())
var/atom/A = X
if(!A.simulated)
continue
if(istype(A, /mob/dead))
continue
if(istype(A, /obj/effect/landmark))
continue
if(istype(A, /obj/docking_port))
continue
qdel(A, force = TRUE)
T0.ChangeTurf(turf_type)
/turf/AllowDrop()
return TRUE
// Makes an image of up to 20 things on a turf + the turf
/turf/proc/photograph(limit = 20)
var/image/I = new()
I.add_overlay(src)
for(var/V in contents)
var/atom/A = V
if(A.invisibility)
continue
I.add_overlay(A)
if(limit)
limit--
else
return I
return I
/turf/hit_by_thrown_mob(mob/living/C, datum/thrownthing/throwingdatum, damage, mob_hurt, self_hurt)
if(mob_hurt || !density)
return
playsound(src, 'sound/weapons/punch1.ogg', 35, 1)
C.visible_message("<span class='danger'>[C] slams into [src]!</span>", "<span class='userdanger'>You slam into [src]!</span>")
if(issilicon(C))
C.adjustBruteLoss(damage)
C.Weaken(3 SECONDS)
else
C.take_organ_damage(damage)
C.KnockDown(3 SECONDS)
/turf/proc/initialize_milla()
var/datum/milla_safe/initialize_turf/milla = new()
milla.invoke_async(src)
/datum/milla_safe/initialize_turf
/datum/milla_safe/initialize_turf/on_run(turf/T)
if(!isnull(T))
set_tile_atmos(T, atmos_mode = T.atmos_mode, environment_id = SSmapping.environments[T.atmos_environment], innate_heat_capacity = T.heat_capacity, temperature = T.temperature)
/// Do not call this directly. Use get_readonly_air or implement /datum/milla_safe.
/turf/proc/private_unsafe_get_air()
RETURN_TYPE(/datum/gas_mixture)
if(isnull(bound_air))
SSair.bind_turf(src)
return bound_air
/// Gets a read-only version of this tile's air. Do not use if you intend to modify the air later, implement /datum/milla_safe instead.
/turf/proc/get_readonly_air()
RETURN_TYPE(/datum/gas_mixture)
// This is one of two intended places to call this otherwise-unsafe proc.
var/datum/gas_mixture/bound_to_turf/air = private_unsafe_get_air()
if(air.lastread < SSair.times_fired)
var/list/milla_tile = new/list(MILLA_TILE_SIZE)
get_tile_atmos(src, milla_tile)
air.copy_from_milla(milla_tile)
air.lastread = SSair.times_fired
air.readonly = null
air.dirty = FALSE
air.synchronized = FALSE
return air.get_readonly()
/// Blindly releases air to this tile. Do not use if you care what the tile previously held, implement /datum/milla_safe instead.
/turf/proc/blind_release_air(datum/gas_mixture/air)
var/datum/milla_safe/turf_blind_release/milla = new()
milla.invoke_async(src, air)
/datum/milla_safe/turf_blind_release
/datum/milla_safe/turf_blind_release/on_run(turf/T, datum/gas_mixture/air)
get_turf_air(T).merge(air)
// Blindly sets the air in this tile. Do not use if you care what the tile previously held, implement /datum/milla_safe instead.
/turf/proc/blind_set_air(datum/gas_mixture/air)
var/datum/milla_safe/turf_blind_set/milla = new()
milla.invoke_async(src, air)
/datum/milla_safe/turf_blind_set
/datum/milla_safe/turf_blind_set/on_run(turf/T, datum/gas_mixture/air)
get_turf_air(T).copy_from(air)
/turf/return_analyzable_air()
return get_readonly_air()