Files
Paradise/code/controllers/subsystem/non_firing/SSpathfinder.dm
T
24845b238a The Divine Flock - DaedalusDock Flockmind Port (#31984)
* Initial commit - FLOCKMIND - Probably has like a billion things to fix

* Do after conversions

* Config

* Moved the files, icon fixes

* Tick everything, language work, event, spawn landmark, role prefs, beginning mob port

* Spans and some other fixes. Also the tickening

* More tickening

* More fixes. Lots of fixes.

* More Fixes

* A whole lot more. Also flock TGUI.

* Fixes fixes fixes fixes fixes

* FIXES

* More fixes - PR ready, still needs a fuckton of testing

* Fixes

* fix incomplete upstream merge

* fix FlockPanel + sort button name

* TGUI review

* Fixes tealprint list

* Fixes

* More fixes

* Incapacitator Fix

* Filenames

* Linters

* Interceptor range buff

* Reagent counts

* Linters

* Fabricator vendor fix

* Keybinds and HUD - Flockdrones, Fixes Vendor Conversion, Cube Materials

* Reworks reagent, adds flock grilles, fixes compute node overlays

* Intent-based flockdrone parts

* Intent based drone parts

* Radial control panel for controlling drones manually, phasing through windows/grilles

* Movement fixes

* Radio talk power, stare fix

* Flock health HUD

* Fixes flock lights, linters

* Unit tests

* Adds countdown to relay

* Relay improvements

* Small fix

* Logic Schmogic

* Relay overlay and looping sound effect

* Ignore air when converting turfs

* Cage fixes and improvements

* Improved flock bolt

* Turret conversions

* Flock bolts taze simple or basic mobs

* Sentience type

* Fixe

* Linter

* tgui review stage 2

* Concentrated Repair Burst

* Improves radio detection

* Removes extra space

* Adds healing visual effect

* Cube tech levels

* Ghooost

* Excess

* Flock doors, chairs, lattices. Centralizes conversion code.  Crafting with Gnesis

* Update code/modules/antagonists/flockmind/ai_behaviors/flock_wander.dm

Co-authored-by: Kapu1178 <75460809+Kapu1178@users.noreply.github.com>
Signed-off-by: PollardTheDragon <144391971+PollardTheDragon@users.noreply.github.com>

* Fixes the fix

* Astar movement detection

* Fix, extraneous code, language stuff

* Language fixes and wander fix

* Fixes

* Another fix

* Lints

* Another linter

* Language improvement

* More language improvements

* Time requirement and appearing in orbit menu as an antag

* Cube glow

* TGUI

* Minicache

* Linters

* Grammar

* Material ID fix

* Lid fix

* Reagent turf reaction

* Reagent fix

* Butcher results

* Conversion rates

* Flock stare fix

* Fixes stare behavior

* Staring

* Flock mob blood

* Flock mobs gibs and blood. Also some runtime fixes

* Flock mobs now resist out of grabs, buckles, lockers, and more

* Fixes flock orbit, fixes a runtime I think,

* Target mechs, damage mechs, other bug fixes

* Cage fix

* Cage resist change

* Some mind changes, gatecrash buff

* Drones now shoot mechs, stare improvement

* Cut down on spam a little

* Nest fix

* No more resist spam

* Fixed drone death control

* Resist statement

* Makes the relay alarm scarier

* Fixes dead flock camera mobs having no ghost sprite, something with ghosting

* Enhanced flockphasing

* Improved flockmob pathing

* Added required turf restriction to relay

* Increased needed bandwidth for relay construction

* Nerfed drone substrate rate

* Added new status tab items for relay progress

* Another relay cost adjustment

* Improves drone AI responsiveness

* Computer frames now become flock computers

* Improves target finding for conversion, building, and replicating

* Reduced flock event pop requirements

* Adjusts flock protection on structures. Adjusts overlays.

* Relay unlock tweak

* Fixwes flock being able to gib mech'd AIs with one button

* Map conflict

* Flock can no longer be outed by merely existing

* Fied bug causing drones to shoot themselves

* Prevents mobs from attacking while in a cage

* Converter tool can now open closets and crates.

* Adds descriptions to flockdrone tools.

* More informatic blurbs

* Adds xenobiology organs

* Organ lint

* TGUI merge

* bundle and mm

---------

Signed-off-by: PollardTheDragon <144391971+PollardTheDragon@users.noreply.github.com>
Co-authored-by: Toastical <20125180+Toastical@users.noreply.github.com>
Co-authored-by: Kapu1178 <75460809+Kapu1178@users.noreply.github.com>
Co-authored-by: Burzah <116982774+Burzah@users.noreply.github.com>
2026-07-08 21:31:28 +00:00

287 lines
11 KiB
Plaintext

/// Queues and manages JPS pathfinding steps
SUBSYSTEM_DEF(pathfinder)
name = "Pathfinder"
init_order = INIT_ORDER_PATH
priority = FIRE_PRIORITY_PATHFINDING
wait = 0.5
/// List of pathfind datums we are currently trying to process
var/list/datum/pathfind/active_pathing = list()
/// List of pathfind datums being ACTIVELY processed. exists to make subsystem stats readable
var/list/datum/pathfind/currentrun = list()
/// List of uncheccked source_to_map entries
var/list/currentmaps = list()
/// Assoc list of target turf -> list(/datum/path_map) centered on the turf
var/list/source_to_maps = list()
var/static/space_type_cache
/datum/controller/subsystem/pathfinder/Initialize()
space_type_cache = typecacheof(/turf/space)
/datum/controller/subsystem/pathfinder/stat_entry(msg)
msg = "P:[length(active_pathing)]"
return ..()
// This is another one of those subsystems (hey lighting) in which one "Run" means fully processing a queue
// We'll use a copy for this just to be nice to people reading the mc panel
/datum/controller/subsystem/pathfinder/fire(resumed)
if(!resumed)
src.currentrun = active_pathing.Copy()
src.currentmaps = deepCopyList(source_to_maps)
// Dies of sonic speed from caching datum var reads
var/list/currentrun = src.currentrun
while(length(currentrun))
var/datum/pathfind/path = currentrun[length(currentrun)]
if(!path.search_step()) // Something's wrong
path.early_exit()
currentrun.len--
continue
if(MC_TICK_CHECK)
return
path.finished()
// Next please
currentrun.len--
// Go over our existing pathmaps, clear out the ones we aren't using
var/list/currentmaps = src.currentmaps
var/oldest_time = world.time - MAP_REUSE_SLOWEST
while(length(currentmaps))
var/turf/source = currentmaps[length(currentmaps)]
var/list/datum/path_map/owned_maps = currentmaps[source]
for(var/datum/path_map/map as anything in owned_maps)
if(map.creation_time < oldest_time && !map.building)
source_to_maps[source] -= map
owned_maps.len--
if(MC_TICK_CHECK)
return
if(!length(source_to_maps[source]))
source_to_maps -= source
currentmaps.len--
/// Initiates a pathfind. Returns true if we're good, FALSE if something's failed
/datum/controller/subsystem/pathfinder/proc/pathfind(atom/movable/pathfind_caller, atom/end, max_distance = 30, mintargetdist, access = list(), simulated_only = TRUE, turf/exclude, skip_first = TRUE, diagonal_handling = DIAGONAL_REMOVE_CLUNKY, list/datum/callback/on_finish)
var/datum/pathfind/jps/path = new()
path.setup(pathfind_caller, access, max_distance, simulated_only, exclude, on_finish, end, mintargetdist, skip_first, diagonal_handling)
if(path.start())
active_pathing += path
return TRUE
return FALSE
/// Initiates a swarmed pathfind. Returns TRUE if we're good, FALSE if something's failed
/// If a valid pathmap exists for the TARGET turf we'll use that, otherwise we have to build a new one
/datum/controller/subsystem/pathfinder/proc/swarmed_pathfind(atom/movable/pathfind_caller, atom/end, max_distance = 30, mintargetdist = 0, age = MAP_REUSE_INSTANT, access = list(), simulated_only = TRUE, turf/exclude, skip_first = TRUE, list/datum/callback/on_finish)
var/turf/target = get_turf(end)
var/datum/can_pass_info/pass_info = new(pathfind_caller, access)
// If there's a map we can use already, use it
var/datum/path_map/valid_map = get_valid_map(pass_info, target, simulated_only, exclude, age, include_building = TRUE)
if(valid_map && valid_map.expand(max_distance))
path_map_passalong(on_finish, get_turf(pathfind_caller), mintargetdist, skip_first, valid_map)
return TRUE
// Otherwise we're gonna make a new one, and turn it into a path for the callbacks passed into us
var/list/datum/callback/pass_in = list()
pass_in += CALLBACK(GLOBAL_PROC, /proc/path_map_passalong, on_finish, get_turf(pathfind_caller), mintargetdist, skip_first)
// And to allow subsequent calls to reuse the same map, we'll put a placeholder in the cache, and fill it up when the pathing finishes
var/datum/path_map/empty = new()
empty.pass_info = new(pathfind_caller, access)
empty.start = target
empty.pass_space = simulated_only
empty.avoid = exclude
empty.building = TRUE
path_map_cache(target, empty)
pass_in += CALLBACK(src, PROC_REF(path_map_fill), target, empty)
if(!SSpathfinder.can_pass_build_map(pass_info, target, max_distance, simulated_only, exclude, pass_in))
return FALSE
return TRUE
/// We generate a path for the passed in callbacks, and then pipe it over
/proc/path_map_passalong(list/datum/callback/return_callbacks, turf/target, mintargetdist = 0, skip_first = TRUE, datum/path_map/hand_back)
var/list/requested_path
if(istype(hand_back, /datum/path_map))
requested_path = hand_back.get_path_from(target, skip_first, mintargetdist)
for(var/datum/callback/return_callback as anything in return_callbacks)
return_callback.Invoke(requested_path)
/// Caches the passed in path_map, allowing for reuse in future
/datum/controller/subsystem/pathfinder/proc/path_map_cache(turf/target, datum/path_map/hand_back)
// Cache our path_map
if(!target || !hand_back)
return
source_to_maps[target] += list(hand_back)
/datum/controller/subsystem/pathfinder/proc/path_map_fill(turf/target, datum/path_map/fill_into, datum/path_map/hand_back)
fill_into.building = FALSE
if(!fill_into.compare_against(hand_back))
source_to_maps[target] -= fill_into
return
fill_into.copy_from(hand_back)
fill_into.creation_time = hand_back.creation_time
// If we aren't in the source list anymore don't go trying to clear it out yeah?
if(!source_to_maps[target] || !(fill_into in source_to_maps[target]))
return
// Let's remove anything we're better than
for(var/datum/path_map/same_target as anything in source_to_maps[target])
if(fill_into == same_target || !same_target.compare_against(hand_back))
continue
// If it's still being made it'll be fresher then us
if(same_target.building)
continue
// We assume that we are fresher, and that's all we care about
// If it's being expanded it'll get updated when that finishes, then clear when all the refs drop
source_to_maps[target] -= same_target
/// Initiates a SSSP run. Returns true if we're good, FALSE if something's failed
/datum/controller/subsystem/pathfinder/proc/build_map(atom/movable/pathfind_caller, turf/source, max_distance = 30, access = list(), simulated_only = TRUE, turf/exclude, list/datum/callback/on_finish)
var/datum/pathfind/sssp/path = new()
path.setup(pathfind_caller, access, source, max_distance, simulated_only, exclude, on_finish)
if(path.start())
active_pathing += path
return TRUE
return FALSE
/// Initiates a SSSP run from a pass_info datum. Returns true if we're good, FALSE if something's failed
/datum/controller/subsystem/pathfinder/proc/can_pass_build_map(datum/can_pass_info/pass_info, turf/source, max_distance = 30, simulated_only = TRUE, turf/exclude, list/datum/callback/on_finish)
var/datum/pathfind/sssp/path = new()
path.setup_from_canpass(pass_info, source, max_distance, simulated_only, exclude, on_finish)
if(path.start())
active_pathing += path
return TRUE
return FALSE
/// Begins to handle a pathfinding run based off the input /datum/pathfind datum
/// You should not use this, it exists to allow for shenanigans. You do not know how to do shenanigans
/datum/controller/subsystem/pathfinder/proc/run_pathfind(datum/pathfind/run)
active_pathing += run
return TRUE
/// Takes a set of pathfind info, returns the first valid pathmap that would work if one exists
/// Optionally takes a max age to accept (defaults to 0 seconds) and a minimum acceptable range
/// If include_building is true and we can only find a building path, ew'll use that instead. tho we will wait for it to finish first
/datum/controller/subsystem/pathfinder/proc/get_valid_map(datum/can_pass_info/pass_info, turf/target, simulated_only = TRUE, turf/exclude, age = MAP_REUSE_INSTANT, min_range = -INFINITY, include_building = FALSE)
// Walk all the maps that match our caller's turf OR our target's
// Then hold onto em. If their cache time is short we can reuse/expand them, if not we'll have to make a new one
var/oldest_time = world.time - age
/// Backup return value used if no finished pathmaps are found
var/datum/path_map/constructing
for(var/datum/path_map/shared_source as anything in source_to_maps[target])
if(!shared_source.compare_against_args(pass_info, target, simulated_only, exclude))
continue
var/max_dist = 0
if(shared_source.distances.len)
max_dist = shared_source.distances[shared_source.distances.len]
if(max_dist < min_range)
continue
if(oldest_time > shared_source.creation_time && !shared_source.building)
continue
if(shared_source.building)
if(include_building)
constructing = constructing || shared_source
continue
return shared_source
if(constructing)
UNTIL(constructing.building == FALSE)
return constructing
return null
/// Takes a set of pathfind info, returns all valid pathmaps that would work
/// Takes an optional minimum range arg
/datum/controller/subsystem/pathfinder/proc/get_valid_maps(datum/can_pass_info/pass_info, turf/target, simulated_only = TRUE, turf/exclude, age = MAP_REUSE_INSTANT, min_range = -INFINITY, include_building = FALSE)
// Walk all the maps that match our caller's turf OR our target's
// Then hold onto em. If their cache time is short we can reuse/expand them, if not we'll have to make a new one
var/list/valid_maps = list()
var/oldest_time = world.time - age
for(var/datum/path_map/shared_source as anything in source_to_maps[target])
if(shared_source.compare_against_args(pass_info, target, simulated_only, exclude))
continue
var/max_dist = shared_source.distances[shared_source.distances.len]
if(max_dist < min_range)
continue
if(oldest_time > shared_source.creation_time)
continue
if(!include_building && shared_source.building)
continue
valid_maps += shared_source
return valid_maps
/// Initiates a pathfind. Returns true if we're good, FALSE if something's failed
/datum/controller/subsystem/pathfinder/proc/ventcrawl_pathfind(atom/movable/pathfind_caller, atom/end, skip_first, list/datum/callback/on_finish)
var/datum/pathfind/ventcrawl/path = new()
path.setup(pathfind_caller, end, skip_first, on_finish)
if(path.start())
active_pathing += path
return TRUE
return FALSE
/datum/controller/subsystem/pathfinder/proc/astar_pathfind(
atom/movable/invoker,
atom/end,
max_steps = 30,
mintargetdist,
list/access,
simulated_only = TRUE,
turf/exclude,
skip_first = TRUE,
use_diagonals = TRUE,
datum/callback/on_finish,
datum/callback/heuristic,
)
var/datum/pathfind/astar/path = new(
invoker,
end,
access,
max_steps,
mintargetdist,
simulated_only,
exclude,
skip_first,
use_diagonals,
on_finish,
heuristic,
)
if(path.start())
active_pathing += path
return TRUE
return FALSE
/datum/controller/subsystem/pathfinder/proc/astar_pathfind_now(
atom/movable/invoker,
atom/end,
max_steps = 14,
mintargetdist,
list/access,
simulated_only = TRUE,
turf/exclude,
skip_first = TRUE,
use_diagonals = TRUE,
datum/callback/heuristic,
)
var/datum/pathfind/astar/path = new(
invoker,
end,
access,
max_steps,
mintargetdist,
simulated_only,
exclude,
skip_first,
use_diagonals,
null,
heuristic,
)
if(!path.start())
return FALSE
if(!path.search_step(FALSE))
path.early_exit()
return FALSE
path.finished()
return path.path