Files
Batrachophreno d712dbfab9 Moves area blurbs permanently to status pane (#21797)
As title. Less annoying chat spam.
2026-02-04 17:01:30 +00:00

569 lines
16 KiB
Plaintext

//
// Areas
//
#define VOLUME_AMBIENCE 30
#define VOLUME_AMBIENT_HUM 18
#define VOLUME_MUSIC 30
/area
var/global/global_uid = 0
var/uid
/// Bitflag (Any of `AREA_FLAG_*`). See "code\__DEFINES\misc.dm".
var/area_flags
/// Color of this area on the holomap. Must be a hex color (as string) or null.
var/holomap_color
/// Do we have an active fire alarm?
var/fire = FALSE
var/atmosalm = 0
var/poweralm = 1
var/party = null
var/radiation_active = null
level = null
name = "Unknown"
icon = 'icons/turf/areas.dmi'
icon_state = "unknown"
layer = AREA_LAYER
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
invisibility = INVISIBILITY_LIGHTING
plane = BLACKNESS_PLANE
var/obj/machinery/power/apc/apc = null
/// The base turf type of the area, which can be used to override the z-level's base turf.
var/turf/base_turf
/// If this area has a light switch (or multiple), do the lights start on or off? FALSE = off, TRUE = on.
var/lightswitch = TRUE
var/eject = null
var/requires_power = 1
/// This gets overridden to 1 for space in area/New()
var/always_unpowered = 0
// Status vars
var/power_equip = 1
var/power_light = 1
var/power_environ = 1
// Continuous drain; don't touch these directly.
var/used_equip = 0
var/used_light = 0
var/used_environ = 0
// Used once and cleared each tick.
var/oneoff_equip = 0
var/oneoff_light = 0
var/oneoff_environ = 0
/// Boolean, if this area has gravity
var/has_gravity = TRUE
/// Boolean, if this area always has gravity
var/alwaysgravity = FALSE
/// Boolean, if this area never has gravity
var/nevergravity = FALSE
/// Contains a list of doors adjacent to this area.
var/list/all_doors = list()
var/air_doors_activated = FALSE
var/list/ambience = list()
var/list/forced_ambience = null
var/list/music = list()
/// Used to decide what kind of reverb the area makes sound have
var/sound_environment = SOUND_AREA_STANDARD_STATION
/// If TRUE, lights in area cannot be toggled with light controller.
var/no_light_control = FALSE
/// If TRUE, lights in area will be darkened by the night mode controller.
var/allow_nightmode = FALSE
var/emergency_lights = FALSE
/**
* Boolean, if the area is part of the station (aka main map)
*
* This includes any non-space area, including maintenance; it doesn't include space or shuttles (as they could be outside the station)
*/
var/station_area = FALSE
/// Only used for the Horizon, and mostly for mapping checks/naming. All except horizon_deck (integer) use '_DEFINES\ship_locations.dm' constants
var/horizon_deck = null
var/department = null
var/subdepartment = null
// 'amidships' belongs to location_ew
var/location_ew = null
var/location_ns = null
var/centcomm_area = FALSE
/// A text-based description of the area, can be used for sounds, notable things in the room, etc.
var/area_blurb
var/tmp/is_outside = OUTSIDE_NO
/// Should the turfs in this area render starlight accurate to the starlight of the local sector?
/// This is intended for use with EVA areas of ships - such as the wings of the Horizon, for instance.
/// You probably shouldn't be setting this to true on any planet-based maps, or on any indoors areas.
var/needs_starlight = FALSE
/// defaults to TRUE, false disables hostile events (like drone uprising).
var/hostile_events = TRUE
/**
* Don't move this to Initialize(). Things in here need to run before SSatoms does.
*/
/area/New()
// DMMS hook - Required for areas to work properly.
if (!GLOB.areas_by_type[type])
GLOB.areas_by_type[type] = src
GLOB.areas += src
// Atmos code needs this, so we need to make sure this is done by the time they initialize.
uid = ++global_uid
. = ..()
/area/Initialize(mapload)
icon_state = "white"
color = null
blend_mode = BLEND_MULTIPLY
if(!requires_power)
power_light = 0
power_equip = 0
power_environ = 0
if(centcomm_area)
GLOB.centcom_areas[src] = TRUE
alwaysgravity = 1
if(station_area)
GLOB.the_station_areas[src] = TRUE
if(!requires_power || !apc)
power_light = 0
power_equip = 0
power_environ = 0
if (!mapload)
power_change() // All machines set to current power level.
. = ..()
update_base_lighting()
if (mapload && turf_initializer)
for(var/turf/T in src)
turf_initializer.initialize(T)
/area/Destroy()
if(GLOB.areas_by_type[type] == src)
GLOB.areas_by_type[type] = null
//this is not initialized until get_sorted_areas() is called so we have to do a null check
if(!isnull(GLOB.sortedAreas))
GLOB.sortedAreas -= src
//just for sanity sake cause why not
if(!isnull(GLOB.areas))
GLOB.areas -= src
GLOB.centcom_areas -= src
GLOB.the_station_areas -= src
//parent cleanup
return ..()
/area/proc/is_prison()
return area_flags & AREA_FLAG_PRISON
/area/proc/is_no_crew_expected()
return area_flags & AREA_FLAG_NO_CREW_EXPECTED
/**
* Set lights in area.
*
* * state - TRUE for on, FALSE for off, NULL for initial state
*
* Returns `TRUE`
*/
/area/proc/set_lightswitch(var/state)
if(isnull(state))
state = initial(lightswitch)
lightswitch = state
var/obj/machinery/light_switch/L = locate() in src
if(L)
L.on = state
L.sync_lights()
/area/proc/get_cameras()
. = list()
for (var/thing in SSmachinery.all_cameras)
var/obj/machinery/camera/C = thing
if (!isturf(C.loc))
continue
if (C.loc.loc == src) // What the fuck is this?
. += C
/area/proc/atmosalert(danger_level, var/alarm_source)
if (danger_level == 0)
GLOB.atmosphere_alarm.clearAlarm(src, alarm_source)
else
GLOB.atmosphere_alarm.triggerAlarm(src, alarm_source, severity = danger_level)
//Check all the alarms before lowering atmosalm. Raising is perfectly fine.
for (var/obj/machinery/alarm/AA in src)
if (!(AA.stat & (NOPOWER|BROKEN)) && !AA.shorted && AA.report_danger_level)
danger_level = max(danger_level, AA.danger_level)
if(danger_level != atmosalm)
if (danger_level < 1 && atmosalm >= 1)
//closing the doors on red and opening on green provides a bit of hysteresis that will hopefully prevent fire doors from opening and closing repeatedly due to noise
air_doors_open()
else if (danger_level >= 2 && atmosalm < 2)
air_doors_close()
atmosalm = danger_level
for (var/obj/machinery/alarm/AA in src)
AA.update_icon()
return 1
return 0
/area/proc/air_doors_close()
if(!air_doors_activated)
air_doors_activated = 1
for(var/obj/machinery/door/firedoor/E in all_doors)
if(!E.blocked)
if(E.operating)
E.nextstate = FIREDOOR_CLOSED
else if(!E.density)
INVOKE_ASYNC(E, TYPE_PROC_REF(/obj/machinery/door, close))
/area/proc/air_doors_open()
if(air_doors_activated)
air_doors_activated = 0
for(var/obj/machinery/door/firedoor/E in all_doors)
if(!E.blocked)
if(E.operating)
E.nextstate = FIREDOOR_OPEN
else if(E.density)
INVOKE_ASYNC(E, TYPE_PROC_REF(/obj/machinery/door, open))
/area/proc/fire_alert()
if(!fire)
fire = 1 //used for firedoor checks
update_icon()
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
for(var/obj/machinery/door/firedoor/D in all_doors)
if(!D.blocked)
if(D.operating)
D.nextstate = FIREDOOR_CLOSED
else if(!D.density)
INVOKE_ASYNC(D, TYPE_PROC_REF(/obj/machinery/door, close))
/area/proc/fire_reset()
if (fire)
fire = 0 //used for firedoor checks
update_icon()
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
for(var/obj/machinery/door/firedoor/D in all_doors)
if(!D.blocked)
if(D.operating)
D.nextstate = FIREDOOR_OPEN
else if(D.density)
INVOKE_ASYNC(D, TYPE_PROC_REF(/obj/machinery/door, open))
/area/proc/readyalert()
if(!eject)
eject = 1
update_icon()
/area/proc/readyreset()
if(eject)
eject = 0
update_icon()
/area/proc/partyalert()
if (!party)
party = 1
update_icon()
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
/area/proc/partyreset()
if (party)
party = 0
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
update_icon()
for(var/obj/machinery/door/firedoor/D in all_doors)
if(!D.blocked)
if(D.operating)
D.nextstate = FIREDOOR_OPEN
else if(D.density)
INVOKE_ASYNC(D, TYPE_PROC_REF(/obj/machinery/door, open))
#define DO_PARTY(COLOR) animate(color = COLOR, time = 0.5 SECONDS, easing = QUAD_EASING)
/area/update_icon()
// If it doesn't require power, can still activate this proc.
if ((fire || eject || party || radiation_active) && (!requires_power||power_environ) && !istype(src, /area/space))
if(radiation_active)
color = "#30e63f"
animate(src) // stop any current animations.
animate(src, color = "#2ea138", time = 1 SECOND, loop = -1, easing = SINE_EASING)
animate(color ="#30e63f", time = 1 SECOND, easing = SINE_EASING)
else if(fire && !eject && !party) // FIRE
color = "#ff9292"
animate(src)
animate(src, color = "#ffa5b2", time = 1 SECOND, loop = -1, easing = SINE_EASING)
animate(color = "#ff9292", time = 1 SECOND, easing = SINE_EASING)
else if(!fire && eject && !party) // EJECT
color = "#ff9292"
animate(src)
animate(src, color = "#bc8a81", time = 1 SECOND, loop = -1, easing = EASE_IN|CUBIC_EASING)
animate(color = "#ff9292", time = 0.5 SECOND, easing = EASE_OUT|CUBIC_EASING)
else if(party && !fire && !eject) // PARTY
color = "#ff728e"
animate(src)
animate(src, color = "#7272ff", time = 0.5 SECONDS, loop = -1, easing = QUAD_EASING)
DO_PARTY("#72aaff")
DO_PARTY("#ffc68e")
DO_PARTY("#72c6ff")
DO_PARTY("#ff72e2")
DO_PARTY("#72ff8e")
DO_PARTY("#ffff8e")
DO_PARTY("#ff728e")
else
color = "#ffb2b2"
animate(src)
animate(src, color = "#B3DFFF", time = 0.5 SECOND, loop = -1, easing = SINE_EASING)
animate(color = "#ffb2b2", time = 0.5 SECOND, loop = -1, easing = SINE_EASING)
else
animate(src, color = "#FFFFFF", time = 0.5 SECONDS, easing = QUAD_EASING) // Stop the animation.
#undef DO_PARTY
/**
* Call back when an atom enters an area
*
* Sends signals COMSIG_AREA_ENTERED and COMSIG_ENTER_AREA (to a list of atoms)
*
* If the area has ambience, then it plays some ambience music to the ambience channel
*/
/area/Entered(atom/movable/arrived, area/old_area)
SEND_SIGNAL(src, COMSIG_AREA_ENTERED, arrived, old_area)
if(!arrived.important_recursive_contents?[RECURSIVE_CONTENTS_AREA_SENSITIVE])
return
for(var/atom/movable/recipient as anything in arrived.important_recursive_contents[RECURSIVE_CONTENTS_AREA_SENSITIVE])
SEND_SIGNAL(recipient, COMSIG_ENTER_AREA, src)
/* START aurora snowflake code */
if(!istype(arrived, /mob/living) || !ROUND_IS_STARTED)
return
var/mob/living/L = arrived
if(!L.ckey) return
if(!L.lastarea)
L.lastarea = get_area(L.loc)
var/area/newarea = get_area(L.loc)
var/area/oldarea = L.lastarea
if((oldarea.has_gravity() == FALSE) && (newarea.has_gravity() == TRUE) && (L.m_intent == M_RUN)) // Being ready when you change areas gives you a chance to avoid falling all together.
thunk(L)
L.update_floating()
L.lastarea = newarea
// Start playing ambience.
if(src.ambience.len && L && L.client && (L.client.prefs.sfx_toggles & ASFX_AMBIENCE) && !L.ear_deaf)
play_ambience(L)
else
stop_ambience(L)
// The dreaded ship ambience hum.
// Explanation for the "if" clause: If the area has ambience, the mob exists, has a client, the client has the hum ASFX toggled on, the area the mob is in is a station area,
// the mob isn't deaf, and the client doesn't already have the ambient hum playing, then start playing the ambient hum.
if(L && L.client && (L.client.prefs.sfx_toggles & ASFX_HUM) && newarea.station_area && !L.ear_deaf)
if(!L.client.ambient_hum_playing)
L.client.ambient_hum_playing = TRUE
L << sound('sound/ambience/shipambience.ogg', repeat = 1, volume = VOLUME_AMBIENT_HUM, channel = 3)
// Otherwise, stop playing the ambient hum.
else
L << sound(null, channel = 3)
if(L.client)
L.client.ambient_hum_playing = FALSE
// Start playing music, if it exists.
if(src.music.len && L && L.client && (L.client.prefs.sfx_toggles & ASFX_MUSIC))
play_music(L)
// Stop playing music.
else
stop_music(L)
/* END aurora snowflake code */
/**
* Called when an atom exits an area
*
* Sends signals COMSIG_AREA_EXITED and COMSIG_EXIT_AREA (to a list of atoms)
*/
/area/Exited(atom/movable/gone, direction)
SEND_SIGNAL(src, COMSIG_AREA_EXITED, gone, direction)
SEND_SIGNAL(gone, COMSIG_MOVABLE_EXITED_AREA, src, direction)
if(!gone.important_recursive_contents?[RECURSIVE_CONTENTS_AREA_SENSITIVE])
return
for(var/atom/movable/recipient as anything in gone.important_recursive_contents[RECURSIVE_CONTENTS_AREA_SENSITIVE])
SEND_SIGNAL(recipient, COMSIG_EXIT_AREA, src)
/**
* Plays ambiance for the provided mob.
*
* * mob/living/L - Affected mob.
*/
/area/proc/play_ambience(var/mob/living/L)
if((world.time >= L.client.ambience_last_played_time + 3 MINUTES) && prob(30))
var/picked_ambience = pick(ambience)
L << sound(picked_ambience, volume = VOLUME_AMBIENCE, channel = CHANNEL_AMBIENCE)
L.client.ambience_last_played_time = world.time
/**
* Stops ambiance for the provided mob.
*
* * mob/living/L - Affected mob.
*/
/area/proc/stop_ambience(var/mob/living/L)
L << sound(null, channel = 2)
/**
* Plays music for the provided mob.
*
* * mob/living/L - Affected mob.
*/
/area/proc/play_music(var/mob/living/L)
if(src.music.len)
var/picked_music = pick(music)
L << sound(picked_music, volume = VOLUME_MUSIC, channel = 4)
/**
* Stops music for the provided mob.
*
* * mob/living/L - Affected mob.
*/
/area/proc/stop_music(var/mob/living/L)
L << sound(null, channel = 4)
/area/proc/gravitychange(var/gravitystate = 0)
has_gravity = gravitystate
for(var/mob/M in src)
if(has_gravity())
thunk(M)
else
to_chat(M, SPAN_NOTICE("The sudden lack of gravity makes you feel weightless and float cluelessly."))
M.update_floating()
/area/proc/thunk(mob)
if(istype(get_turf(mob), /turf/space)) // Can't fall onto nothing.
return
if(istype(mob,/mob/living/carbon/human/))
var/mob/living/carbon/human/H = mob
if(H.Check_Shoegrip(FALSE))
return
if(H.m_intent == M_RUN)
H.AdjustStunned(2)
H.AdjustWeakened(2)
else
H.AdjustStunned(1)
H.AdjustWeakened(1)
to_chat(mob, SPAN_WARNING("The sudden appearance of gravity makes you fall to the floor!"))
/area/proc/prison_break()
for(var/obj/machinery/power/apc/temp_apc in src)
temp_apc.overload_lighting(70)
for(var/obj/machinery/door/airlock/temp_airlock in src)
temp_airlock.prison_open()
for(var/obj/machinery/door/window/temp_windoor in src)
temp_windoor.open()
/area/has_gravity(turf/gravity_turf)
if(alwaysgravity)
return TRUE
if(nevergravity)
return FALSE
return has_gravity
/**
* Useful proc for events.
* This returns a random area of the station which is meaningful. Ie, a room somewhere
*
* * filter_players - Default FALSE. If TRUE, skips areas without any players in them.
*
* Returns /area/
*/
/proc/random_station_area(var/filter_players = FALSE)
var/list/possible = list()
for(var/Y in GLOB.the_station_areas)
if(!Y)
continue
var/area/A = Y
if (!is_station_level(A.z))
continue
if (istype(A, /area/shuttle) || findtext(A.name, "Docked") || findtext(A.name, "Shuttle"))
continue
if (istype(A, /area/solar) || findtext(A.name, "solar"))
continue
if (istype(A, /area/constructionsite))
continue
if (istype(A, /area/horizon/rnd/xenobiology))
continue
if (istype(A, /area/horizon/maintenance/substation))
continue
if (istype(A, /area/turbolift))
continue
if (istype(A, /area/mine))
continue
if (istype(A, /area/horizon/exterior))
continue
//Although hostile mobs instadying to turrets is fun
//If there's no AI they'll just be hit with stunbeams all day and spam the attack logs.
if (istype(A, /area/turret_protected) || LAZYLEN(A.turret_controls))
continue
if(!A.hostile_events)
continue
if(filter_players)
var/should_continue = FALSE
for(var/mob/living/carbon/human/H in GLOB.human_mob_list)
if(!H.client)
continue
if(A == get_area(H))
should_continue = TRUE
break
if(should_continue)
continue
possible += A
return pick(possible)
/area/proc/random_space()
var/list/turfs = list()
for(var/turf/simulated/floor/F in src.contents)
if(turf_clear(F))
turfs += F
if (turfs.len)
return pick(turfs)
else return null
#undef VOLUME_AMBIENCE
#undef VOLUME_AMBIENT_HUM
#undef VOLUME_MUSIC