Files
Bubberstation/code/__HELPERS/areas.dm
tonty 14d2514aa9 [MDB IGNORE] Refactors away /area/station/ai_monitored and its subtypes (with bonus neat repathing) (#93704)
## About The Pull Request

/area/station/ai_monitored's behaviour was isolated into a component,
`/datum/component/monitored_area`, which itself uses
`/datum/motion_group`s to query cameras. Functionally, it (should) work
identically to the old implementation. I'm sure that behaviour could
have been further cleaned up, camera code is quite dreadful, but it's
better to focus on isolating the behaviour first. Baby steps.

Areas that want to opt into monitoring can set `var/motion_monitored` to
TRUE (this approach was taken to make subtyping easier).

The following non-AI areas were changed:
- /area/station/ai_monitored/security/armory ->
/area/station/security/armory
- /area/station/ai_monitored/command/nuke_storage ->
/area/station/command/vault
- /area/station/ai_monitored/command/storage/eva ->
/area/station/command/eva

All other `/area/station/ai_monitored` subtypes were repathed into
`/area/station/ai` and cleaned up in a way that I thought made logical
sense. It is **much** more readable now. For example:

- /area/station/ai_monitored/turret_protected/aisat ->
/area/station/ai/satellite
- /area/station/ai_monitored/command/storage/satellite ->
/area/station/ai/satellite/maintenance/storage
- /area/station/ai_monitored/turret_protected/ai ->
/area/station/ai/satellite/chamber
2025-11-03 11:27:14 -06:00

362 lines
13 KiB
Plaintext

#define BP_MAX_ROOM_SIZE 300
#define EXTRA_ROOM_CHECK_SKIP 1
#define EXTRA_ROOM_CHECK_FAIL 2
GLOBAL_LIST_INIT(typecache_powerfailure_safe_areas, typecacheof(list(
/area/station/engineering/main,
/area/station/engineering/supermatter,
/area/station/engineering/atmospherics_engine,
/area/station/ai/satellite/chamber,
/area/ruin/comms_agent //fixes icemoon comms station being affected
)))
// Gets an atmos isolated contained space
// Returns an associative list of turf|dirs pairs
// The dirs are connected turfs in the same space
// break_if_found is a typecache of turf/area types to return false if found
// extra_check is an optional callback to invoke on each turf checked, and can specify whether to skip processing the turf or return false
// Please keep this proc type agnostic. If you need to restrict it do it elsewhere or add an arg.
/proc/detect_room(turf/origin, list/break_if_found = list(), max_size=INFINITY, datum/callback/extra_check)
if(origin.blocks_air)
return list(origin)
. = list()
var/list/checked_turfs = list()
var/list/found_turfs = list(origin)
while(length(found_turfs))
var/turf/sourceT = found_turfs[1]
found_turfs.Cut(1, 2)
var/dir_flags = checked_turfs[sourceT]
for(var/dir in GLOB.alldirs)
if(length(.) > max_size)
return
if(dir_flags & dir) // This means we've checked this dir before, probably from the other turf
continue
var/turf/checkT = get_step(sourceT, dir)
if(!checkT)
continue
checked_turfs[sourceT] |= dir
checked_turfs[checkT] |= REVERSE_DIR(dir)
switch(extra_check?.Invoke(checkT))
if(EXTRA_ROOM_CHECK_SKIP)
continue
if(EXTRA_ROOM_CHECK_FAIL)
return FALSE
.[sourceT] |= dir
.[checkT] |= REVERSE_DIR(dir)
if(break_if_found[checkT.type] || break_if_found[checkT.loc.type])
return FALSE
var/static/list/cardinal_cache = list("[NORTH]"=TRUE, "[EAST]"=TRUE, "[SOUTH]"=TRUE, "[WEST]"=TRUE)
if(!cardinal_cache["[dir]"] || !TURFS_CAN_SHARE(sourceT, checkT))
continue
found_turfs += checkT // Since checkT is connected, add it to the list to be processed
/**
* Create an atmos zone (Think ZAS), similiar to [proc/detect_room] but it ignores walls and turfs which are non-[atmos_can_pass]
*
* Arguments
* source - the turf which to find all connected atmos turfs
* range - the max range to check
*
* Returns a list of turfs, which is an area of isolated atmos
*/
/proc/create_atmos_zone(turf/source, range = INFINITY)
var/counter = 1 // a counter which increment each loop
var/loops = 0
if(source.blocks_air)
return
var/list/connected_turfs = list(source)
. = connected_turfs
while(length(connected_turfs))
var/list/turf/adjacent_turfs = list(
get_step(connected_turfs[counter], NORTH),
get_step(connected_turfs[counter], SOUTH),
get_step(connected_turfs[counter], EAST),
get_step(connected_turfs[counter], WEST)
)// get a tile in each cardinal direction at once and add that to the list
for(var/turf/valid_turf in adjacent_turfs)//loop through the list and check for atmos adjacency
var/turf/reference_turf = connected_turfs[counter]
if(valid_turf in connected_turfs)//if the turf is already added, skip
loops += 1
continue
if(length(connected_turfs) >= range)
return
if(TURFS_CAN_SHARE(reference_turf, valid_turf))
loops = 0
connected_turfs |= valid_turf//add that to the original list
if(loops >= 7)//if the loop has gone 7 consecutive times with no new turfs added, return the result. Number is arbitrary, subject to change
return
counter += 1 //increment by one so the next loop will start at the next position in the list
/proc/set_turfs_to_area(list/turf/turfs, area/new_area, list/area/affected_areas = list())
for(var/turf/the_turf as anything in turfs)
set_turf_to_area(the_turf, new_area, affected_areas)
/proc/set_turf_to_area(turf/the_turf, area/new_area, list/area/affected_areas = list())
var/area/old_area = the_turf.loc
//keep rack of all areas affected by turf changes
affected_areas[old_area.name] = old_area
//move the turf to its new area and unregister it from the old one
the_turf.change_area(old_area, new_area)
//inform atoms on the turf that their area has changed
for(var/atom/stuff as anything in the_turf)
//unregister the stuff from its old area
SEND_SIGNAL(stuff, COMSIG_EXIT_AREA, old_area)
//register the stuff to its new area. special exception for apc as its not registered to this signal
if(istype(stuff, /obj/machinery/power/apc))
var/obj/machinery/power/apc/area_apc = stuff
area_apc.assign_to_area()
else
SEND_SIGNAL(stuff, COMSIG_ENTER_AREA, new_area)
/proc/create_area(mob/creator, new_area_type = /area)
// Passed into the above proc as list/break_if_found
var/static/list/area_or_turf_fail_types = typecacheof(list(
/turf/open/space,
/area/shuttle,
))
// Ignore these areas and dont let people expand them. They can expand into them though
var/static/list/blacklisted_areas = typecacheof(list(
/area/space,
/area/station/asteroid,
))
var/error = ""
var/list/turfs = detect_room(get_turf(creator), area_or_turf_fail_types, BP_MAX_ROOM_SIZE*2)
var/turf_count = length(turfs)
if(!turf_count)
error = "The new area must be completely airtight and not a part of a shuttle."
else if(turf_count > BP_MAX_ROOM_SIZE)
error = "The room you're in is too big. It is [turf_count >= BP_MAX_ROOM_SIZE *2 ? "more than 100" : ((turf_count / BP_MAX_ROOM_SIZE)-1)*100]% larger than allowed."
if(error)
to_chat(creator, span_warning(error))
return
var/list/apc_map = list()
var/list/areas = list("New Area" = new_area_type)
for(var/i in 1 to turf_count)
var/turf/the_turf = turfs[i]
var/area/place = get_area(the_turf)
if(blacklisted_areas[place.type])
continue
if(!place.requires_power || (place.area_flags & NOTELEPORT) || (place.area_flags & HIDDEN_AREA))
continue // No expanding powerless rooms etc
if(!TURF_SHARES(the_turf)) // No expanding areas of walls/something blocking this turf because that defeats the whole point of them used to separate areas
continue
if(!isnull(place.apc))
apc_map[place.name] = place.apc
if(length(apc_map) > 1) // When merging 2 or more areas make sure we arent merging their apc into 1 area
to_chat(creator, span_warning("Multiple APC's detected in the vicinity. only 1 is allowed."))
return
areas[place.name] = place
var/area_choice = tgui_input_list(creator, "Choose an area to expand or make a new area", "Area Expansion", areas)
if(isnull(area_choice))
to_chat(creator, span_warning("No choice selected. The area remains undefined."))
return
area_choice = areas[area_choice]
var/area/newA
var/area/oldA = get_area(get_turf(creator))
if(!isarea(area_choice))
var/str = tgui_input_text(creator, "New area name", "Blueprint Editing", max_length = MAX_NAME_LEN)
if(!str)
return
newA = new area_choice
newA.AddComponent(/datum/component/custom_area)
newA.setup(str)
newA.default_gravity = oldA.default_gravity
GLOB.custom_areas[newA] = TRUE
require_area_resort() //new area registered. resort the names
else
newA = area_choice
//we haven't done anything. let's get outta here
if(newA == oldA)
to_chat(creator, span_warning("Selected choice is same as the area your standing in. No area changes were requested."))
return
/**
* A list of all machinery tied to an area along with the area itself. key=area name,value=list(area,list of machinery)
* we use this to keep track of what areas are affected by the blueprints & what machinery of these areas needs to be reconfigured accordingly
*/
var/list/area/affected_areas = list()
set_turfs_to_area(turfs, newA, affected_areas)
newA.reg_in_areas_in_z()
if(!isarea(area_choice) && newA.static_lighting)
newA.create_area_lighting_objects()
//convert map to list
var/list/area/area_list = list()
for(var/area_name in affected_areas)
area_list += affected_areas[area_name]
SEND_GLOBAL_SIGNAL(COMSIG_AREA_CREATED, newA, area_list, creator)
to_chat(creator, span_notice("You have created a new area, named [newA.name]. It is now weather proof, and constructing an APC will allow it to be powered."))
creator.log_message("created a new area: [AREACOORD(creator)] (previously \"[oldA.name]\")", LOG_GAME)
//purge old areas that had all their turfs merged into the new one i.e. old empty areas. also recompute fire doors
for(var/i in 1 to length(area_list))
var/area/merged_area = area_list[i]
//recompute fire doors affecting areas
for(var/obj/machinery/door/firedoor/FD as anything in merged_area.firedoors)
FD.CalculateAffectingAreas()
//no more turfs in this area. Time to clean up
if(!merged_area.has_contained_turfs())
qdel(merged_area)
return TRUE
#undef BP_MAX_ROOM_SIZE
/proc/require_area_resort()
GLOB.sortedAreas = null
/// Returns a sorted version of GLOB.areas, by name
/proc/get_sorted_areas()
if(!GLOB.sortedAreas)
GLOB.sortedAreas = sortTim(GLOB.areas.Copy(), /proc/cmp_name_asc)
return GLOB.sortedAreas
//Takes: Area type as a text string from a variable.
//Returns: Instance for the area in the world.
/proc/get_area_instance_from_text(areatext)
if(istext(areatext))
areatext = text2path(areatext)
return GLOB.areas_by_type[areatext]
//Takes: Area type as text string or as typepath OR an instance of the area.
//Returns: A list of all areas of that type in the world.
/proc/get_areas(areatype, subtypes=TRUE)
if(istext(areatype))
areatype = text2path(areatype)
else if(isarea(areatype))
var/area/areatemp = areatype
areatype = areatemp.type
else if(!ispath(areatype))
return null
var/list/areas = list()
if(subtypes)
var/list/cache = typecacheof(areatype)
for(var/area/area_to_check as anything in GLOB.areas)
if(cache[area_to_check.type])
areas += area_to_check
else
for(var/area/area_to_check as anything in GLOB.areas)
if(area_to_check.type == areatype)
areas += area_to_check
return areas
/// Iterates over all turfs in the target area and returns the first non-dense one
/proc/get_first_open_turf_in_area(area/target)
if(!target)
return
for(var/turf/turf in target)
if(!turf.density)
return turf
//Takes: Area type as text string or as typepath OR an instance of the area.
//Returns: A list of all turfs in areas of that type of that type in the world.
/proc/get_area_turfs(areatype, target_z = 0, subtypes=FALSE)
if(istext(areatype))
areatype = text2path(areatype)
else if(isarea(areatype))
var/area/areatemp = areatype
areatype = areatemp.type
else if(!ispath(areatype))
return null
// Pull out the areas
var/list/areas_to_pull = list()
if(subtypes)
var/list/cache = typecacheof(areatype)
for(var/area/area_to_check as anything in GLOB.areas)
if(!cache[area_to_check.type])
continue
areas_to_pull += area_to_check
else
for(var/area/area_to_check as anything in GLOB.areas)
if(area_to_check.type != areatype)
continue
areas_to_pull += area_to_check
// Now their turfs
var/list/turfs = list()
for(var/area/pull_from as anything in areas_to_pull)
if (target_z == 0)
for (var/list/zlevel_turfs as anything in pull_from.get_zlevel_turf_lists())
turfs += zlevel_turfs
else
turfs += pull_from.get_turfs_by_zlevel(target_z)
return turfs
///Takes: list of area types
///Returns: all mobs that are in an area type
/proc/mobs_in_area_type(list/area/checked_areas)
var/list/mobs_in_area = list()
for(var/mob/living/mob as anything in GLOB.mob_living_list)
if(QDELETED(mob))
continue
for(var/area in checked_areas)
if(istype(get_area(mob), area))
mobs_in_area += mob
break
return mobs_in_area
/**
* rename_area
* Renames an area to the given new name, updating all machines' names and firedoors
* to properly ensure alarms and machines are named correctly at all times.
* Args:
* - area_to_rename: The area that's being renamed.
* - new_name: The name we're changing said area to.
*/
/proc/rename_area(area/area_to_rename, new_name)
var/prevname = "[area_to_rename.name]"
set_area_machinery_title(area_to_rename, new_name, prevname)
area_to_rename.name = new_name
require_area_resort() //area renamed so resort the names
if(LAZYLEN(area_to_rename.firedoors))
for(var/obj/machinery/door/firedoor/area_firedoors as anything in area_to_rename.firedoors)
area_firedoors.CalculateAffectingAreas()
area_to_rename.update_areasize()
return TRUE
/**
* Renames all machines in a defined area from the old title to the new title.
* Used when renaming an area to ensure that all machiens are labeled the new area's machine.
* Args:
* - area_renaming: The area being renamed, which we'll check turfs from to rename machines in.
* - title: The new name of the area that we're swapping into.
* - oldtitle: The old name of the area that we're replacing text from.
*/
/proc/set_area_machinery_title(area/area_renaming, title, oldtitle)
if(!oldtitle) // or replacetext goes to infinite loop
return
//stuff tied to the area to rename
var/static/list/to_rename = typecacheof(list(
/obj/machinery/airalarm,
/obj/machinery/atmospherics/components/unary/vent_scrubber,
/obj/machinery/atmospherics/components/unary/vent_pump,
/obj/machinery/door,
/obj/machinery/firealarm,
/obj/machinery/light_switch,
/obj/machinery/power/apc,
/obj/machinery/camera,
))
for(var/list/zlevel_turfs as anything in area_renaming.get_zlevel_turf_lists())
for(var/turf/area_turf as anything in zlevel_turfs)
for(var/obj/machine as anything in typecache_filter_list(area_turf.contents, to_rename))
machine.name = replacetext(machine.name, oldtitle, title)