mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2025-12-10 01:34:01 +00:00
* fixes contents not being removed from the spatial grid when deleted during movement between 2 grid cells (#75658) ## About The Pull Request fixes the flaky test reports for cockroaches being stuck in the spatial grid (which mothblocks seems to have closed all of) cockroaches get deleted when they die, so theres a spurious unit test failure where if a cockroach is on a tile in grid cell A and moves to a lava tile in grid cell B, they will get killed when lava.Entered() is called, then deleted, and when /atom/movable/Destroy() is called we try to take them out of grid cell B (because their loc is the lava tile) but they were never added to that cell yet because their movement never finished, so that doesnt do anything. THEN moveToNullspace() is called, that movement finishes before the first movement, and then in Moved(old_loc = lava turf) we try to remove it from grid cell B which again doesnt work, and then the first movements Moved(old_loc = original turf) is called where we can actually remove them from the correct grid cell, except we cant because in exit_cell() we subtract `old_target.important_recursive_contents[channel]` from the cells content lists, and since the target is deleted by this point it doesnt have important_recursive_contents. so the fix here is changing this so it subtracts `old_target.important_recursive_contents?[type] || old_target` instead, which works if the target is deleted. also fixes some Entered() overrides that dont call parent and improves documentation on spatial grid defines ## Why It's Good For The Game fixes it without needing the change_loc() setter * fixes contents not being removed from the spatial grid when deleted during movement between 2 grid cells --------- Co-authored-by: Kylerace <kylerlumpkin1@gmail.com>
434 lines
16 KiB
Plaintext
434 lines
16 KiB
Plaintext
/*
|
|
* LAVA
|
|
* PLASMA LAVA
|
|
* MAFIA PLASMA LAVA
|
|
*/
|
|
|
|
/turf/open/lava
|
|
name = "lava"
|
|
icon_state = "lava"
|
|
desc = "Looks painful to step in. Don't mine down."
|
|
gender = PLURAL //"That's some lava."
|
|
baseturfs = /turf/open/lava //lava all the way down
|
|
slowdown = 2
|
|
|
|
light_range = 2
|
|
light_power = 0.75
|
|
light_color = LIGHT_COLOR_LAVA
|
|
light_on = FALSE
|
|
bullet_bounce_sound = 'sound/items/welder2.ogg'
|
|
|
|
footstep = FOOTSTEP_LAVA
|
|
barefootstep = FOOTSTEP_LAVA
|
|
clawfootstep = FOOTSTEP_LAVA
|
|
heavyfootstep = FOOTSTEP_LAVA
|
|
/// How much fire damage we deal to living mobs stepping on us
|
|
var/lava_damage = 20
|
|
/// How many firestacks we add to living mobs stepping on us
|
|
var/lava_firestacks = 20
|
|
/// How much temperature we expose objects with
|
|
var/temperature_damage = 10000
|
|
/// mobs with this trait won't burn.
|
|
var/immunity_trait = TRAIT_LAVA_IMMUNE
|
|
/// objects with these flags won't burn.
|
|
var/immunity_resistance_flags = LAVA_PROOF
|
|
/// the temperature that this turf will attempt to heat/cool gasses too in a heat exchanger, in kelvin
|
|
var/lava_temperature = 5000
|
|
/// The icon that covers the lava bits of our turf
|
|
var/mask_icon = 'icons/turf/floors.dmi'
|
|
/// The icon state that covers the lava bits of our turf
|
|
var/mask_state = "lava-lightmask"
|
|
|
|
/turf/open/lava/Initialize(mapload)
|
|
. = ..()
|
|
AddElement(/datum/element/lazy_fishing_spot, FISHING_SPOT_PRESET_LAVALAND_LAVA)
|
|
refresh_light()
|
|
if(!smoothing_flags)
|
|
update_appearance()
|
|
|
|
/turf/open/lava/update_overlays()
|
|
. = ..()
|
|
. += emissive_appearance(mask_icon, mask_state, src)
|
|
// We need a light overlay here because not every lava turf casts light, only the edge ones
|
|
var/mutable_appearance/light = mutable_appearance(mask_icon, mask_state, LIGHTING_PRIMARY_LAYER, src, LIGHTING_PLANE)
|
|
light.color = light_color
|
|
light.blend_mode = BLEND_ADD
|
|
. += light
|
|
// Mask away our light underlay, so things don't double stack
|
|
// This does mean if our light underlay DOESN'T look like the light we emit things will be wrong
|
|
// But that's rare, and I'm ok with that, quartering our light source count is useful
|
|
var/mutable_appearance/light_mask = mutable_appearance(mask_icon, mask_state, LIGHTING_MASK_LAYER, src, LIGHTING_PLANE)
|
|
light_mask.blend_mode = BLEND_MULTIPLY
|
|
light_mask.color = list(-1,0,0,0, 0,-1,0,0, 0,0,-1,0, 0,0,0,1, 1,1,1,0)
|
|
. += light_mask
|
|
|
|
/// Refreshes this lava turf's lighting
|
|
/turf/open/lava/proc/refresh_light()
|
|
var/border_turf = FALSE
|
|
var/list/turfs_to_check = RANGE_TURFS(1, src)
|
|
if(GET_LOWEST_STACK_OFFSET(z))
|
|
var/turf/above = SSmapping.get_turf_above(src)
|
|
if(above)
|
|
turfs_to_check += RANGE_TURFS(1, above)
|
|
var/turf/below = SSmapping.get_turf_below(src)
|
|
if(below)
|
|
turfs_to_check += RANGE_TURFS(1, below)
|
|
|
|
for(var/turf/around as anything in turfs_to_check)
|
|
if(islava(around))
|
|
continue
|
|
border_turf = TRUE
|
|
|
|
if(!border_turf)
|
|
set_light(l_on = FALSE)
|
|
return
|
|
|
|
set_light(l_on = TRUE)
|
|
|
|
/turf/open/lava/ChangeTurf(path, list/new_baseturfs, flags)
|
|
var/turf/result = ..()
|
|
|
|
if(result && !islava(result))
|
|
// We have gone from a lava turf to a non lava turf, time to let them know
|
|
var/list/turfs_to_check = RANGE_TURFS(1, result)
|
|
if(GET_LOWEST_STACK_OFFSET(z))
|
|
var/turf/above = SSmapping.get_turf_above(result)
|
|
if(above)
|
|
turfs_to_check += RANGE_TURFS(1, above)
|
|
var/turf/below = SSmapping.get_turf_below(result)
|
|
if(below)
|
|
turfs_to_check += RANGE_TURFS(1, below)
|
|
for(var/turf/open/lava/inform in turfs_to_check)
|
|
inform.set_light(l_on = TRUE)
|
|
|
|
return result
|
|
|
|
/turf/open/lava/smooth_icon()
|
|
. = ..()
|
|
mask_state = icon_state
|
|
update_appearance(~UPDATE_SMOOTHING)
|
|
|
|
/turf/open/lava/ex_act(severity, target)
|
|
return FALSE
|
|
|
|
/turf/open/lava/MakeSlippery(wet_setting, min_wet_time, wet_time_to_add, max_wet_time, permanent)
|
|
return
|
|
|
|
/turf/open/lava/Melt()
|
|
to_be_destroyed = FALSE
|
|
return src
|
|
|
|
/turf/open/lava/acid_act(acidpwr, acid_volume)
|
|
return FALSE
|
|
|
|
/turf/open/lava/MakeDry(wet_setting = TURF_WET_WATER)
|
|
return
|
|
|
|
/turf/open/lava/airless
|
|
initial_gas_mix = AIRLESS_ATMOS
|
|
|
|
/turf/open/lava/Entered(atom/movable/arrived, atom/old_loc, list/atom/old_locs)
|
|
. = ..()
|
|
if(burn_stuff(arrived))
|
|
START_PROCESSING(SSobj, src)
|
|
|
|
/turf/open/lava/Exited(atom/movable/gone, direction)
|
|
. = ..()
|
|
if(isliving(gone))
|
|
var/mob/living/leaving_mob = gone
|
|
if(!islava(leaving_mob.loc))
|
|
REMOVE_TRAIT(leaving_mob, TRAIT_PERMANENTLY_ONFIRE, TURF_TRAIT)
|
|
if(!leaving_mob.on_fire)
|
|
leaving_mob.update_fire()
|
|
|
|
/turf/open/lava/hitby(atom/movable/AM, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum)
|
|
if(burn_stuff(AM))
|
|
START_PROCESSING(SSobj, src)
|
|
|
|
/turf/open/lava/process(seconds_per_tick)
|
|
if(!burn_stuff(null, seconds_per_tick))
|
|
STOP_PROCESSING(SSobj, src)
|
|
|
|
/turf/open/lava/rcd_vals(mob/user, obj/item/construction/rcd/the_rcd)
|
|
switch(the_rcd.mode)
|
|
if(RCD_FLOORWALL)
|
|
return list("mode" = RCD_FLOORWALL, "delay" = 0, "cost" = 3)
|
|
return FALSE
|
|
|
|
/turf/open/lava/rcd_act(mob/user, obj/item/construction/rcd/the_rcd, passed_mode)
|
|
switch(passed_mode)
|
|
if(RCD_FLOORWALL)
|
|
to_chat(user, span_notice("You build a floor."))
|
|
PlaceOnTop(/turf/open/floor/plating, flags = CHANGETURF_INHERIT_AIR)
|
|
return TRUE
|
|
return FALSE
|
|
|
|
/turf/open/lava/rust_heretic_act()
|
|
return FALSE
|
|
|
|
/turf/open/lava/singularity_act()
|
|
return
|
|
|
|
/turf/open/lava/singularity_pull(S, current_size)
|
|
return
|
|
|
|
/turf/open/lava/get_smooth_underlay_icon(mutable_appearance/underlay_appearance, turf/asking_turf, adjacency_dir)
|
|
underlay_appearance.icon = 'icons/turf/floors.dmi'
|
|
underlay_appearance.icon_state = "basalt"
|
|
return TRUE
|
|
|
|
/turf/open/lava/GetHeatCapacity()
|
|
. = 700000
|
|
|
|
/turf/open/lava/GetTemperature()
|
|
. = lava_temperature
|
|
|
|
/turf/open/lava/TakeTemperature(temp)
|
|
|
|
/turf/open/lava/attackby(obj/item/C, mob/user, params)
|
|
..()
|
|
if(istype(C, /obj/item/stack/rods/lava))
|
|
var/obj/item/stack/rods/lava/R = C
|
|
var/obj/structure/lattice/lava/H = locate(/obj/structure/lattice/lava, src)
|
|
if(H)
|
|
to_chat(user, span_warning("There is already a lattice here!"))
|
|
return
|
|
if(R.use(1))
|
|
to_chat(user, span_notice("You construct a lattice."))
|
|
playsound(src, 'sound/weapons/genhit.ogg', 50, TRUE)
|
|
new /obj/structure/lattice/lava(locate(x, y, z))
|
|
else
|
|
to_chat(user, span_warning("You need one rod to build a heatproof lattice."))
|
|
return
|
|
// Light a cigarette in the lava
|
|
if(istype(C, /obj/item/clothing/mask/cigarette))
|
|
var/obj/item/clothing/mask/cigarette/ciggie = C
|
|
if(ciggie.lit)
|
|
to_chat(user, span_warning("The [ciggie.name] is already lit!"))
|
|
return TRUE
|
|
var/clumsy_modifier = HAS_TRAIT(user, TRAIT_CLUMSY) ? 2 : 1
|
|
if(prob(25 * clumsy_modifier ))
|
|
ciggie.light(span_warning("[user] expertly dips \the [ciggie.name] into [src], along with the rest of [user.p_their()] arm. What a dumbass."))
|
|
var/obj/item/bodypart/affecting = user.get_active_hand()
|
|
affecting?.receive_damage(burn = 90)
|
|
else
|
|
ciggie.light(span_rose("[user] expertly dips \the [ciggie.name] into [src], lighting it with the scorching heat of the planet. Witnessing such a feat is almost enough to make you cry."))
|
|
return TRUE
|
|
|
|
/turf/open/lava/proc/is_safe()
|
|
//if anything matching this typecache is found in the lava, we don't burn things
|
|
var/static/list/lava_safeties_typecache = typecacheof(list(/obj/structure/lattice/catwalk, /obj/structure/stone_tile, /obj/structure/lattice/lava))
|
|
var/list/found_safeties = typecache_filter_list(contents, lava_safeties_typecache)
|
|
for(var/obj/structure/stone_tile/S in found_safeties)
|
|
if(S.fallen)
|
|
LAZYREMOVE(found_safeties, S)
|
|
return LAZYLEN(found_safeties)
|
|
|
|
///Generic return value of the can_burn_stuff() proc. Does nothing.
|
|
#define LAVA_BE_IGNORING 0
|
|
/// Another. Won't burn the target but will make the turf start processing.
|
|
#define LAVA_BE_PROCESSING 1
|
|
/// Burns the target and makes the turf process (depending on the return value of do_burn()).
|
|
#define LAVA_BE_BURNING 2
|
|
|
|
///Proc that sets on fire something or everything on the turf that's not immune to lava. Returns TRUE to make the turf start processing.
|
|
/turf/open/lava/proc/burn_stuff(atom/movable/to_burn, seconds_per_tick = 1)
|
|
if(is_safe())
|
|
return FALSE
|
|
|
|
var/thing_to_check = src
|
|
if (to_burn)
|
|
thing_to_check = list(to_burn)
|
|
for(var/atom/movable/burn_target as anything in thing_to_check)
|
|
switch(can_burn_stuff(burn_target))
|
|
if(LAVA_BE_IGNORING)
|
|
continue
|
|
if(LAVA_BE_BURNING)
|
|
if(!do_burn(burn_target, seconds_per_tick))
|
|
continue
|
|
. = TRUE
|
|
|
|
/turf/open/lava/proc/can_burn_stuff(atom/movable/burn_target)
|
|
if(burn_target.movement_type & (FLYING|FLOATING)) //you're flying over it.
|
|
return LAVA_BE_IGNORING
|
|
|
|
if(isobj(burn_target))
|
|
if(burn_target.throwing) // to avoid gulag prisoners easily escaping, throwing only works for objects.
|
|
return LAVA_BE_IGNORING
|
|
var/obj/burn_obj = burn_target
|
|
if((burn_obj.resistance_flags & immunity_resistance_flags))
|
|
return LAVA_BE_PROCESSING
|
|
return LAVA_BE_BURNING
|
|
|
|
if (!isliving(burn_target))
|
|
return LAVA_BE_IGNORING
|
|
|
|
if(HAS_TRAIT(burn_target, immunity_trait))
|
|
return LAVA_BE_PROCESSING
|
|
var/mob/living/burn_living = burn_target
|
|
var/atom/movable/burn_buckled = burn_living.buckled
|
|
if(burn_buckled)
|
|
if(burn_buckled.movement_type & (FLYING|FLOATING))
|
|
return LAVA_BE_PROCESSING
|
|
if(isobj(burn_buckled))
|
|
var/obj/burn_buckled_obj = burn_buckled
|
|
if(burn_buckled_obj.resistance_flags & immunity_resistance_flags)
|
|
return LAVA_BE_PROCESSING
|
|
else if(HAS_TRAIT(burn_buckled, immunity_trait))
|
|
return LAVA_BE_PROCESSING
|
|
|
|
if(iscarbon(burn_living))
|
|
var/mob/living/carbon/burn_carbon = burn_living
|
|
var/obj/item/clothing/burn_suit = burn_carbon.get_item_by_slot(ITEM_SLOT_OCLOTHING)
|
|
var/obj/item/clothing/burn_helmet = burn_carbon.get_item_by_slot(ITEM_SLOT_HEAD)
|
|
if(burn_suit?.clothing_flags & LAVAPROTECT && burn_helmet?.clothing_flags & LAVAPROTECT)
|
|
return LAVA_BE_PROCESSING
|
|
|
|
return LAVA_BE_BURNING
|
|
|
|
#undef LAVA_BE_IGNORING
|
|
#undef LAVA_BE_PROCESSING
|
|
#undef LAVA_BE_BURNING
|
|
|
|
/turf/open/lava/proc/do_burn(atom/movable/burn_target, seconds_per_tick = 1)
|
|
. = TRUE
|
|
if(isobj(burn_target))
|
|
var/obj/burn_obj = burn_target
|
|
if(burn_obj.resistance_flags & ON_FIRE) // already on fire; skip it.
|
|
return
|
|
if(!(burn_obj.resistance_flags & FLAMMABLE))
|
|
burn_obj.resistance_flags |= FLAMMABLE //Even fireproof things burn up in lava
|
|
if(burn_obj.resistance_flags & FIRE_PROOF)
|
|
burn_obj.resistance_flags &= ~FIRE_PROOF
|
|
if(burn_obj.get_armor_rating(FIRE) > 50) //obj with 100% fire armor still get slowly burned away.
|
|
burn_obj.set_armor_rating(FIRE, 50)
|
|
burn_obj.fire_act(temperature_damage, 1000 * seconds_per_tick)
|
|
if(istype(burn_obj, /obj/structure/closet))
|
|
var/obj/structure/closet/burn_closet = burn_obj
|
|
for(var/burn_content in burn_closet.contents)
|
|
burn_stuff(burn_content)
|
|
return
|
|
|
|
var/mob/living/burn_living = burn_target
|
|
ADD_TRAIT(burn_living, TRAIT_PERMANENTLY_ONFIRE, TURF_TRAIT)
|
|
burn_living.update_fire()
|
|
|
|
burn_living.adjustFireLoss(lava_damage * seconds_per_tick)
|
|
if(!QDELETED(burn_living)) //mobs turning into object corpses could get deleted here.
|
|
burn_living.adjust_fire_stacks(lava_firestacks * seconds_per_tick)
|
|
burn_living.ignite_mob()
|
|
|
|
/turf/open/lava/smooth
|
|
name = "lava"
|
|
baseturfs = /turf/open/lava/smooth
|
|
icon = 'icons/turf/floors/lava.dmi'
|
|
mask_icon = 'icons/turf/floors/lava_mask.dmi'
|
|
icon_state = "lava-255"
|
|
mask_state = "lava-255"
|
|
base_icon_state = "lava"
|
|
smoothing_flags = SMOOTH_BITMASK | SMOOTH_BORDER
|
|
smoothing_groups = SMOOTH_GROUP_TURF_OPEN + SMOOTH_GROUP_FLOOR_LAVA
|
|
canSmoothWith = SMOOTH_GROUP_FLOOR_LAVA
|
|
underfloor_accessibility = 2 //This avoids strangeness when routing pipes / wires along catwalks over lava
|
|
|
|
/turf/open/lava/smooth/lava_land_surface
|
|
initial_gas_mix = LAVALAND_DEFAULT_ATMOS
|
|
planetary_atmos = TRUE
|
|
baseturfs = /turf/open/lava/smooth/lava_land_surface
|
|
|
|
/turf/open/lava/smooth/airless
|
|
initial_gas_mix = AIRLESS_ATMOS
|
|
|
|
/turf/open/lava/plasma
|
|
name = "liquid plasma"
|
|
desc = "A flowing stream of chilled liquid plasma. You probably shouldn't get in."
|
|
icon_state = "liquidplasma"
|
|
initial_gas_mix = "n2=82;plasma=24;TEMP=120"
|
|
baseturfs = /turf/open/lava/plasma
|
|
|
|
light_range = 3
|
|
light_power = 0.75
|
|
light_color = LIGHT_COLOR_PURPLE
|
|
immunity_trait = TRAIT_SNOWSTORM_IMMUNE
|
|
immunity_resistance_flags = FREEZE_PROOF
|
|
lava_temperature = 100
|
|
|
|
/turf/open/lava/plasma/examine(mob/user)
|
|
. = ..()
|
|
. += span_info("Some <b>liquid plasma<b> could probably be scooped up with a <b>container</b>.")
|
|
|
|
/turf/open/lava/plasma/attackby(obj/item/I, mob/user, params)
|
|
if(!I.is_open_container())
|
|
return ..()
|
|
if(!I.reagents.add_reagent(/datum/reagent/toxin/plasma, rand(5, 10)))
|
|
to_chat(user, span_warning("[I] is full."))
|
|
return
|
|
user.visible_message(span_notice("[user] scoops some plasma from the [src] with [I]."), span_notice("You scoop out some plasma from the [src] using [I]."))
|
|
|
|
/turf/open/lava/plasma/do_burn(atom/movable/burn_target, seconds_per_tick = 1)
|
|
. = TRUE
|
|
if(isobj(burn_target))
|
|
return FALSE // Does nothing against objects. Old code.
|
|
|
|
var/mob/living/burn_living = burn_target
|
|
burn_living.adjustFireLoss(2)
|
|
if(QDELETED(burn_living))
|
|
return
|
|
burn_living.adjust_fire_stacks(20) //dipping into a stream of plasma would probably make you more flammable than usual
|
|
burn_living.adjust_bodytemperature(-rand(50,65)) //its cold, man
|
|
if(!ishuman(burn_living) || SPT_PROB(65, seconds_per_tick))
|
|
return
|
|
var/mob/living/carbon/human/burn_human = burn_living
|
|
var/datum/species/burn_species = burn_human.dna.species
|
|
if(istype(burn_species, /datum/species/plasmaman) || istype(burn_species, /datum/species/android)) //ignore plasmamen/robotic species
|
|
return
|
|
|
|
var/list/plasma_parts = list()//a list of the organic parts to be turned into plasma limbs
|
|
var/list/robo_parts = list()//keep a reference of robotic parts so we know if we can turn them into a plasmaman
|
|
for(var/obj/item/bodypart/burn_limb as anything in burn_human.bodyparts)
|
|
if(IS_ORGANIC_LIMB(burn_limb) && burn_limb.limb_id != SPECIES_PLASMAMAN) //getting every organic, non-plasmaman limb (augments/androids are immune to this)
|
|
plasma_parts += burn_limb
|
|
if(!IS_ORGANIC_LIMB(burn_limb))
|
|
robo_parts += burn_limb
|
|
|
|
burn_human.adjustToxLoss(15, required_biotype = MOB_ORGANIC) // This is from plasma, so it should obey plasma biotype requirements
|
|
burn_human.adjustFireLoss(25)
|
|
if(plasma_parts.len)
|
|
var/obj/item/bodypart/burn_limb = pick(plasma_parts) //using the above-mentioned list to get a choice of limbs
|
|
burn_human.emote("scream")
|
|
var/obj/item/bodypart/plasmalimb
|
|
switch(burn_limb.body_zone) //get plasmaman limb to swap in
|
|
if(BODY_ZONE_L_ARM)
|
|
plasmalimb = new /obj/item/bodypart/arm/left/plasmaman
|
|
if(BODY_ZONE_R_ARM)
|
|
plasmalimb = new /obj/item/bodypart/arm/right/plasmaman
|
|
if(BODY_ZONE_L_LEG)
|
|
plasmalimb = new /obj/item/bodypart/leg/left/plasmaman
|
|
if(BODY_ZONE_R_LEG)
|
|
plasmalimb = new /obj/item/bodypart/leg/right/plasmaman
|
|
if(BODY_ZONE_CHEST)
|
|
plasmalimb = new /obj/item/bodypart/chest/plasmaman
|
|
if(BODY_ZONE_HEAD)
|
|
plasmalimb = new /obj/item/bodypart/head/plasmaman
|
|
burn_human.del_and_replace_bodypart(plasmalimb)
|
|
burn_human.update_body_parts()
|
|
burn_human.emote("scream")
|
|
burn_human.visible_message(span_warning("[burn_human]'s [burn_limb.plaintext_zone] melts down to the bone!"), \
|
|
span_userdanger("You scream out in pain as your [burn_limb.plaintext_zone] melts down to the bone, leaving an eerie plasma-like glow where flesh used to be!"))
|
|
if(!plasma_parts.len && !robo_parts.len) //a person with no potential organic limbs left AND no robotic limbs, time to turn them into a plasmaman
|
|
burn_human.ignite_mob()
|
|
burn_human.set_species(/datum/species/plasmaman)
|
|
burn_human.visible_message(span_warning("[burn_human] bursts into a brilliant purple flame as [burn_human.p_their()] entire body is that of a skeleton!"), \
|
|
span_userdanger("Your senses numb as all of your remaining flesh is turned into a purple slurry, sloshing off your body and leaving only your bones to show in a vibrant purple!"))
|
|
|
|
//mafia specific tame happy plasma (normal atmos, no slowdown)
|
|
/turf/open/lava/plasma/mafia
|
|
initial_gas_mix = OPENTURF_DEFAULT_ATMOS
|
|
baseturfs = /turf/open/lava/plasma/mafia
|
|
slowdown = 0
|
|
|
|
//basketball specific lava (normal atmos, no slowdown)
|
|
/turf/open/lava/smooth/basketball
|
|
initial_gas_mix = OPENTURF_DEFAULT_ATMOS
|
|
slowdown = 0
|