mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-22 20:48:56 +01:00
[Modular] Better area spawn algorithm (#14891)
* Better area spawn algorithm * Removed max_amount like I said I did
This commit is contained in:
@@ -204,6 +204,7 @@ GLOBAL_PROTECT(admin_verbs_debug)
|
||||
/client/proc/cmd_display_overlay_log,
|
||||
/client/proc/reload_configuration,
|
||||
/client/proc/reload_interactions, //SKYRAT EDIT ADDITION
|
||||
/client/proc/test_area_spawner, // SKYRAT EDIT ADDITION - AUTOMAPPER
|
||||
/client/proc/atmos_control,
|
||||
/client/proc/reload_cards,
|
||||
/client/proc/validate_cards,
|
||||
@@ -304,6 +305,7 @@ GLOBAL_LIST_INIT(admin_verbs_hideable, list(
|
||||
/client/proc/toggle_combo_hud,
|
||||
/client/proc/admin_open_event_spawners_menu, //SKYRAT EDIT ADDITION - EVENTS
|
||||
/client/proc/spawn_pollution, // SKYRAT EDIT ADDITION
|
||||
/client/proc/test_area_spawner, // SKYRAT EDIT ADDITION - AUTOMAPPER
|
||||
/client/proc/spawn_liquid,
|
||||
/client/proc/debug_huds
|
||||
))
|
||||
|
||||
@@ -1,120 +1,3 @@
|
||||
#define RESTRICTED_OBJECTS_LIST list(/obj/machinery/recharge_station)
|
||||
#define RESTRICTED_CARDINAL_OBJECTS_LIST list(/obj/machinery/disposal/bin, /obj/structure/table, /obj/machinery/door, /obj/structure/closet)
|
||||
|
||||
SUBSYSTEM_DEF(area_spawn)
|
||||
name = "Area Spawn"
|
||||
flags = SS_NO_FIRE
|
||||
|
||||
/datum/controller/subsystem/area_spawn/Initialize(start_timeofday)
|
||||
for(var/iterating_type in subtypesof(/datum/area_spawn))
|
||||
var/datum/area_spawn/iterating_area_spawn = new iterating_type
|
||||
iterating_area_spawn.try_spawn()
|
||||
return ..()
|
||||
|
||||
/**
|
||||
* Area spawn datums
|
||||
*
|
||||
* Use these to spawn atoms in areas instead of placing them on a map. It will select any available open and entering turf.
|
||||
*/
|
||||
/datum/area_spawn
|
||||
/// The target area for us to spawn the desired atom, the list is formatted, highest priority first.
|
||||
var/list/target_areas
|
||||
/// The atom that we want to spawn
|
||||
var/desired_atom
|
||||
/// The amount we want to spawn
|
||||
var/amount_to_spawn = 1
|
||||
/// The max amount that the world can have
|
||||
var/max_amount = 1
|
||||
/// Do we need to be adjacent to a wall? This also checks the other 3 cardinals for no density. Generally useful for objects with density and anchored.
|
||||
var/wall_hug = FALSE
|
||||
/// Map blacklist, this is used to determine what maps we should not spawn on.
|
||||
var/list/blacklisted_stations = list("Blueshift", "Runtime Station", "MultiZ Debug")
|
||||
|
||||
/**
|
||||
* Attempts to find a location using an algorithm to spawn the desired atom.
|
||||
*/
|
||||
/datum/area_spawn/proc/try_spawn()
|
||||
if(SSmapping.config.map_name in blacklisted_stations)
|
||||
return
|
||||
|
||||
// Turfs that are available
|
||||
var/list/available_turfs = list()
|
||||
// Turfs that are available and completely void of contents
|
||||
var/list/available_empty_turfs = list()
|
||||
|
||||
for(var/area_type in target_areas)
|
||||
if(LAZYLEN(available_turfs))
|
||||
break
|
||||
var/area/found_area = GLOB.areas_by_type[area_type]
|
||||
if(!found_area)
|
||||
continue
|
||||
for(var/turf/iterating_turf in found_area)
|
||||
if(iterating_turf.density)
|
||||
continue
|
||||
var/density_found = FALSE
|
||||
for(var/atom/movable/found_movable in iterating_turf)
|
||||
if(found_movable.density)
|
||||
density_found = TRUE
|
||||
break
|
||||
if(is_type_in_list(found_movable, RESTRICTED_OBJECTS_LIST))
|
||||
density_found = TRUE
|
||||
break
|
||||
if(density_found)
|
||||
continue
|
||||
// Time to check cardinals
|
||||
// The cardinals must be clear of any density, aside from wall cardinals!
|
||||
var/cardinal_density_check = TRUE
|
||||
// Some items want to be hugging a wall, such as lockers, and machines. It just looks better.
|
||||
var/wall_check = FALSE
|
||||
for(var/dir in GLOB.cardinals)
|
||||
var/turf/cardinal_test_turf = get_step(iterating_turf, dir)
|
||||
if(isclosedturf(cardinal_test_turf))
|
||||
wall_check = TRUE
|
||||
for(var/atom/movable/found_movable in cardinal_test_turf)
|
||||
if(found_movable.density)
|
||||
cardinal_density_check = FALSE
|
||||
if(wall_hug && !wall_check)
|
||||
continue
|
||||
if(!cardinal_density_check && wall_hug)
|
||||
continue
|
||||
// Finally we want to prioritise entirely empty turfs
|
||||
var/totally_empty = TRUE
|
||||
for(var/atom/movable/found_movable in iterating_turf)
|
||||
if(found_movable.layer > LOW_OBJ_LAYER)
|
||||
totally_empty = FALSE
|
||||
break
|
||||
if(totally_empty)
|
||||
available_empty_turfs += iterating_turf
|
||||
available_turfs += iterating_turf
|
||||
|
||||
if(!LAZYLEN(available_turfs))
|
||||
CRASH("[src.type] could not find any suitable turfs on map [SSmapping.config.map_name]!")
|
||||
|
||||
for(var/i in 1 to amount_to_spawn)
|
||||
if(LAZYLEN(available_empty_turfs))
|
||||
var/turf/picked_turf = pick(available_empty_turfs)
|
||||
available_empty_turfs -= picked_turf
|
||||
new desired_atom(picked_turf)
|
||||
else
|
||||
var/turf/picked_turf = pick(available_turfs)
|
||||
available_turfs -= picked_turf
|
||||
new desired_atom(picked_turf)
|
||||
|
||||
/obj/effect/turf_test
|
||||
name = "PASS"
|
||||
icon = 'modular_skyrat/modules/automapper/icons/area_test.dmi'
|
||||
icon_state = "area_test"
|
||||
color = COLOR_GREEN
|
||||
anchored = TRUE
|
||||
|
||||
/obj/effect/turf_test/fail
|
||||
name = "FAIL"
|
||||
color = COLOR_RED
|
||||
|
||||
/obj/effect/turf_test/empty
|
||||
name = "PASS(EMPTY)"
|
||||
color = COLOR_BLUE
|
||||
|
||||
// Pets
|
||||
/datum/area_spawn/markus
|
||||
target_areas = list(/area/station/cargo/sorting, /area/station/cargo/storage, /area/station/cargo/office, /area/station/cargo/qm)
|
||||
@@ -143,7 +26,7 @@ SUBSYSTEM_DEF(area_spawn)
|
||||
wall_hug = TRUE
|
||||
|
||||
/datum/area_spawn/command_drobe
|
||||
target_areas = list(/area/station/command/meeting_room, /area/station/command/bridge)
|
||||
target_areas = list(/area/station/command/meeting_room, /area/station/command/meeting_room/council, /area/station/command/bridge)
|
||||
desired_atom = /obj/machinery/vending/access/command
|
||||
wall_hug = TRUE
|
||||
|
||||
@@ -157,8 +40,8 @@ SUBSYSTEM_DEF(area_spawn)
|
||||
desired_atom = /obj/machinery/gun_vendor
|
||||
wall_hug = TRUE
|
||||
|
||||
/datum/area_spawn/lustwish_dorms
|
||||
target_areas = list(/area/station/commons/locker, /area/station/commons/dorms)
|
||||
/datum/area_spawn/lustwish_public
|
||||
target_areas = list(/area/station/commons/locker, /area/station/commons/dorms/laundry, /area/station/commons/dorms)
|
||||
desired_atom = /obj/machinery/vending/dorms
|
||||
wall_hug = TRUE
|
||||
|
||||
@@ -203,6 +86,3 @@ SUBSYSTEM_DEF(area_spawn)
|
||||
/datum/area_spawn/customs_agent_landmark
|
||||
desired_atom = /obj/effect/landmark/start/customs_agent
|
||||
target_areas = list(/area/station/security/checkpoint/supply, /area/station/cargo/storage)
|
||||
|
||||
#undef RESTRICTED_OBJECTS_LIST
|
||||
#undef RESTRICTED_CARDINAL_OBJECTS_LIST
|
||||
|
||||
@@ -0,0 +1,278 @@
|
||||
SUBSYSTEM_DEF(area_spawn)
|
||||
name = "Area Spawn"
|
||||
flags = SS_NO_FIRE
|
||||
|
||||
// Can't be on tile or a neighbor.
|
||||
// Usually things where it's important to be sure the players can walk up to them, but aren't dense.
|
||||
var/list/restricted_objects_list = list(
|
||||
/obj/machinery/recharge_station,
|
||||
/obj/machinery/disposal/bin,
|
||||
/obj/structure/table,
|
||||
/obj/machinery/door,
|
||||
/obj/structure/closet,
|
||||
/obj/structure/stairs,
|
||||
)
|
||||
|
||||
// Only Blacklist if on same tile because looks bad, etc, but doesn't need to be reached.
|
||||
var/list/restricted_overlap_objects_list = list(
|
||||
/obj/item/kirbyplants,
|
||||
)
|
||||
|
||||
// Things here in some way act as walls. This is the result of extensive tweaking.
|
||||
var/list/allowed_diagonal_objects_list = list(
|
||||
/obj/structure/grille,
|
||||
/obj/structure/window,
|
||||
/obj/machinery/door,
|
||||
)
|
||||
|
||||
var/list/datum/area_turf_info/area_turf_cache = list()
|
||||
|
||||
/datum/controller/subsystem/area_spawn/Initialize(start_timeofday)
|
||||
for(var/iterating_type in subtypesof(/datum/area_spawn))
|
||||
var/datum/area_spawn/iterating_area_spawn = new iterating_type
|
||||
iterating_area_spawn.try_spawn()
|
||||
clear_cache()
|
||||
return ..()
|
||||
|
||||
/**
|
||||
* Clear the cached tiles for optimization or debugging purposes.
|
||||
*/
|
||||
/datum/controller/subsystem/area_spawn/proc/clear_cache()
|
||||
for(var/datum/cache in area_turf_cache)
|
||||
qdel(cache)
|
||||
LAZYCLEARLIST(area_turf_cache)
|
||||
|
||||
/**
|
||||
* Process the geometry of an area and cache the candidates.
|
||||
*
|
||||
* Returns turf candidate list. "[priority]" =
|
||||
*
|
||||
* Arguments:
|
||||
* * area - the area to process
|
||||
* * wall_hug - Are we processing for wall_hug case or not?
|
||||
*/
|
||||
/datum/controller/subsystem/area_spawn/proc/get_turf_candidates(area/area, wall_hug)
|
||||
// Get area cache or make a new one.
|
||||
var/datum/area_turf_info/area_turf_info
|
||||
if(!area_turf_cache[area.type])
|
||||
area_turf_info = area_turf_cache[area.type] = new /datum/area_turf_info()
|
||||
else
|
||||
area_turf_info = area_turf_cache[area.type]
|
||||
|
||||
// Different use cases have different lists of turfs.
|
||||
// Get or create the cached list.
|
||||
var/list/turf_list
|
||||
if(wall_hug)
|
||||
if(area_turf_info.available_wall_turfs)
|
||||
return area_turf_info.available_wall_turfs
|
||||
turf_list = area_turf_info.available_wall_turfs = list()
|
||||
else
|
||||
if(area_turf_info.available_open_turfs)
|
||||
return area_turf_info.available_open_turfs
|
||||
turf_list = area_turf_info.available_open_turfs = list()
|
||||
|
||||
// Get highest priority items
|
||||
for(var/turf/iterating_turf in area)
|
||||
// Only retain turfs of the highest priority
|
||||
var/priority = process_turf(iterating_turf, wall_hug)
|
||||
if(priority > 0)
|
||||
LAZYADDASSOC(turf_list, "[priority]", list(iterating_turf))
|
||||
|
||||
// Sort the priorities descending
|
||||
return sortTim(turf_list, /proc/cmp_num_string_asc)
|
||||
|
||||
/**
|
||||
* Process a specific turf and return priority number from 0 to infinity.
|
||||
*
|
||||
* Turfs with highest priority will be picked. Priority 0 means NEVER.
|
||||
*
|
||||
* Arguments:
|
||||
* * turf - The turf to process
|
||||
* * wall_hug - Are we processing for wall_hug case or not?
|
||||
*/
|
||||
/datum/controller/subsystem/area_spawn/proc/process_turf(turf/turf, wall_hug)
|
||||
// Only spawn on actual floors
|
||||
if(!isfloorturf(turf))
|
||||
return 0
|
||||
|
||||
// Turf completely empty?
|
||||
var/totally_empty = TRUE
|
||||
for(var/atom/movable/found_movable in turf)
|
||||
// Same tile conditions for no-go
|
||||
if(
|
||||
found_movable.density \
|
||||
|| is_type_in_list(found_movable, restricted_objects_list) \
|
||||
|| is_type_in_list(found_movable, restricted_overlap_objects_list)
|
||||
)
|
||||
return 0
|
||||
if(found_movable.layer > LOW_OBJ_LAYER && found_movable.layer < ABOVE_MOB_LAYER)
|
||||
totally_empty = FALSE
|
||||
|
||||
// Number of directions that have a closed wall
|
||||
var/num_walls_found = 0
|
||||
// Found a dense object?
|
||||
var/found_dense_object = FALSE
|
||||
// Number of directions that have anything dense
|
||||
var/num_dense_found = 0
|
||||
// Number of directions that have 2 squares of open space.
|
||||
var/num_very_open_floors = 0
|
||||
for(var/dir in GLOB.cardinals)
|
||||
var/turf/neighbor_turf = get_step(turf, dir)
|
||||
if(isclosedturf(neighbor_turf))
|
||||
num_walls_found++
|
||||
num_dense_found++
|
||||
continue
|
||||
if(wall_hug)
|
||||
var/turf/long_test_turf = get_step(neighbor_turf, dir)
|
||||
if(isopenturf(long_test_turf))
|
||||
num_very_open_floors++
|
||||
for(var/atom/movable/found_movable in neighbor_turf)
|
||||
if(found_movable.density || is_type_in_list(found_movable, restricted_objects_list))
|
||||
found_dense_object = TRUE
|
||||
num_dense_found++
|
||||
break
|
||||
|
||||
// Wall hugging also, as a low priority, doesn't even want diagnal things
|
||||
var/num_diagonal_objects = 0
|
||||
if(wall_hug)
|
||||
for(var/dir in GLOB.diagonals)
|
||||
var/turf/neighbor_turf = get_step(turf, dir)
|
||||
for(var/atom/movable/found_movable in neighbor_turf)
|
||||
if(
|
||||
!is_type_in_list(found_movable, allowed_diagonal_objects_list) \
|
||||
&& (found_movable.density || is_type_in_list(found_movable, restricted_objects_list))
|
||||
)
|
||||
num_diagonal_objects++
|
||||
break
|
||||
|
||||
if(wall_hug)
|
||||
// For wall hugging, must be against wall, and not touching another dense object as it may completely block it.
|
||||
if(num_walls_found == 0 || found_dense_object || num_walls_found == 4)
|
||||
return 0
|
||||
|
||||
// #1 Priority after that: be in a totally empty square
|
||||
// #2 favor being in a cozy wall nook
|
||||
// #3 (marginally) have clear diagnals
|
||||
// #4 be in a big room/hallway so we don't pinch a room down to 1 square of passage.
|
||||
return (totally_empty ? 1000 : 0) + (400 - num_diagonal_objects * 100) + (num_walls_found * 10) + num_very_open_floors
|
||||
|
||||
// For non-wall hug
|
||||
// #1 priority is totally empty
|
||||
// #2 priority is being in the middle of the room
|
||||
return (totally_empty ? 10 : 0) + (4 - num_dense_found)
|
||||
|
||||
/**
|
||||
* Pick a turf candidate and remove from the list.
|
||||
*
|
||||
* Only picks one of the highest priority ones.
|
||||
*
|
||||
* Arguments:
|
||||
* * turf_candidates - Turf candidate list produced by
|
||||
*/
|
||||
/datum/controller/subsystem/area_spawn/proc/pick_turf_candidate(list/list/turf/turf_candidates)
|
||||
// Pick-n-take highest priority.
|
||||
var/list/turf/sublist = turf_candidates[peek(turf_candidates)]
|
||||
var/turf/winner = pick_n_take(sublist)
|
||||
|
||||
// To be safe, remove the neighbors too.
|
||||
for(var/dir in GLOB.cardinals)
|
||||
var/turf/neighbor = get_step(winner, dir)
|
||||
sublist -= neighbor
|
||||
|
||||
// Remove this priority if it's now empty.
|
||||
if(!LAZYLEN(sublist))
|
||||
pop(turf_candidates)
|
||||
|
||||
// Extremely specific, but landmarks are immediately destroyed when created so can't be detected another way.
|
||||
// This is the only landmark list that normally creates solid objects in non-maintenance spaces.
|
||||
GLOB.secequipment -= winner
|
||||
|
||||
return winner
|
||||
|
||||
/**
|
||||
* Turf into for a given area
|
||||
*/
|
||||
/datum/area_turf_info
|
||||
/// Turfs in the middle of the room
|
||||
var/list/list/turf/available_open_turfs
|
||||
/// Turfs hugging the wall
|
||||
var/list/list/turf/available_wall_turfs
|
||||
|
||||
/**
|
||||
* Area spawn datums
|
||||
*
|
||||
* Use these to spawn atoms in areas instead of placing them on a map. It will select any available open and entering turf.
|
||||
*/
|
||||
/datum/area_spawn
|
||||
/// The target area for us to spawn the desired atom, the list is formatted, highest priority first.
|
||||
var/list/target_areas
|
||||
/// The atom that we want to spawn
|
||||
var/desired_atom
|
||||
/// The amount we want to spawn
|
||||
var/amount_to_spawn = 1
|
||||
/// Do we need to be adjacent to a wall? This also checks the other 3 cardinals for no density. Generally useful for objects with density and anchored.
|
||||
var/wall_hug = FALSE
|
||||
/// Map blacklist, this is used to determine what maps we should not spawn on.
|
||||
var/list/blacklisted_stations = list("Blueshift", "Runtime Station", "MultiZ Debug")
|
||||
|
||||
/**
|
||||
* Attempts to find a location using an algorithm to spawn the desired atom.
|
||||
*/
|
||||
/datum/area_spawn/proc/try_spawn()
|
||||
if(SSmapping.config.map_name in blacklisted_stations)
|
||||
return
|
||||
|
||||
// Turfs that are available
|
||||
var/list/available_turfs
|
||||
|
||||
for(var/area_type in target_areas)
|
||||
var/area/found_area = GLOB.areas_by_type[area_type]
|
||||
if(!found_area)
|
||||
continue
|
||||
available_turfs = SSarea_spawn.get_turf_candidates(found_area, wall_hug)
|
||||
if(LAZYLEN(available_turfs))
|
||||
break
|
||||
|
||||
if(!LAZYLEN(available_turfs))
|
||||
CRASH("[src.type] could not find any suitable turfs on map [SSmapping.config.map_name]!")
|
||||
|
||||
for(var/i in 1 to amount_to_spawn)
|
||||
new desired_atom(SSarea_spawn.pick_turf_candidate(available_turfs))
|
||||
|
||||
/obj/effect/turf_test
|
||||
name = "PASS"
|
||||
icon = 'modular_skyrat/modules/automapper/icons/area_test.dmi'
|
||||
icon_state = "area_test"
|
||||
color = COLOR_RED
|
||||
anchored = TRUE
|
||||
|
||||
/obj/effect/turf_test/wall
|
||||
color = COLOR_BLUE
|
||||
maptext_y = 16
|
||||
|
||||
/**
|
||||
* Show overlay over area of priorities. Wall priority over open priority.
|
||||
*/
|
||||
/client/proc/test_area_spawner(area/area)
|
||||
set category = "Debug"
|
||||
set name = "Test Area Spawner"
|
||||
set desc = "Show area spawner placement candidates as an overlay."
|
||||
|
||||
for(var/obj/effect/turf_test/old_test in area)
|
||||
qdel(old_test)
|
||||
|
||||
SSarea_spawn.clear_cache()
|
||||
var/list/list/turf/open_candidates = SSarea_spawn.get_turf_candidates(area, FALSE)
|
||||
var/list/list/turf/wall_candidates = SSarea_spawn.get_turf_candidates(area, TRUE)
|
||||
|
||||
for(var/priority in open_candidates)
|
||||
var/list/turf/turfs = open_candidates[priority]
|
||||
for(var/turf/turf as anything in turfs)
|
||||
var/obj/overlay = new /obj/effect/turf_test(turf)
|
||||
overlay.maptext = MAPTEXT(priority)
|
||||
|
||||
for(var/priority in wall_candidates)
|
||||
var/list/turf/turfs = wall_candidates[priority]
|
||||
for(var/turf/turf as anything in turfs)
|
||||
var/obj/overlay = new /obj/effect/turf_test/wall(turf)
|
||||
overlay.maptext = MAPTEXT(priority)
|
||||
@@ -5019,6 +5019,7 @@
|
||||
#include "modular_skyrat\modules\assault_operatives\code\armaments\armament_utility.dm"
|
||||
#include "modular_skyrat\modules\assault_operatives\code\armaments\assaultops_armament_station.dm"
|
||||
#include "modular_skyrat\modules\automapper\code\area_spawn_entries.dm"
|
||||
#include "modular_skyrat\modules\automapper\code\area_spawn_subsystem.dm"
|
||||
#include "modular_skyrat\modules\automapper\code\automap_template.dm"
|
||||
#include "modular_skyrat\modules\automapper\code\automapper_subsystem.dm"
|
||||
#include "modular_skyrat\modules\autotransfer\code\autotransfer.dm"
|
||||
|
||||
Reference in New Issue
Block a user