Files
fulpstation/code/__HELPERS/shuttle.dm
John Willard 7199947c08 [MDB IGNORE] [IDB IGNORE] WIP TGU (#1427)
Several months worth of updates.

---------

Co-authored-by: A miscellaneous Fern <80640114+FernandoJ8@users.noreply.github.com>
Co-authored-by: Pepsilawn <reisenrui@gmail.com>
Co-authored-by: Ray <64306407+OneAsianTortoise@users.noreply.github.com>
Co-authored-by: Cure221 <106662180+Cure221@users.noreply.github.com>
2025-11-06 08:20:20 -05:00

561 lines
23 KiB
Plaintext

GLOBAL_LIST_EMPTY(shuttle_frames_by_turf)
/datum/shuttle_frame
/// The turfs that are part of this frame
var/list/turfs = list()
/// Turfs that are changing, but shouldn't be removed from the frame immediately in case they are still valid frame turfs after the change
var/list/possibly_valid_changing_turfs = list()
/// List of turfs to start the split check with (to avoid redundant flood fill checks)
var/list/deferred_update_turfs = list()
/// Turfs to check for the existence of shuttles - used for expanding custom shuttles
var/list/shuttle_tracking_turfs = list()
/// A list of shuttles on tracking turfs, each associated with a list of which tracking turfs the shuttle overlaps
var/list/adjacent_shuttles = list()
/// Turfs that are in our list of turfs, but also have had a shuttle land on them
var/list/shuttle_covered_turfs = list()
/datum/shuttle_frame/New(list/initial_turfs)
if(initial_turfs)
add_turfs(initial_turfs)
/datum/shuttle_frame/proc/start_tracking_turf_for_shuttles(turf/to_track, dir)
if(!shuttle_tracking_turfs[to_track])
var/obj/docking_port/mobile/custom/shuttle = SSshuttle.get_containing_shuttle(to_track)
if(istype(shuttle))
if(!adjacent_shuttles[shuttle])
start_tracking_shuttle(shuttle)
adjacent_shuttles[shuttle][to_track] = TRUE
RegisterSignals(to_track, list(COMSIG_TURF_ON_SHUTTLE_MOVE, COMSIG_TURF_REMOVED_FROM_SHUTTLE), PROC_REF(shuttle_leave_react))
RegisterSignals(to_track, list(COMSIG_TURF_AFTER_SHUTTLE_MOVE, COMSIG_TURF_ADDED_TO_SHUTTLE), PROC_REF(shuttle_arrive_react))
shuttle_tracking_turfs[to_track] |= dir
/datum/shuttle_frame/proc/stop_tracking_turf_for_shuttles(turf/to_stop_tracking)
for(var/obj/docking_port/mobile/custom/shuttle as anything in adjacent_shuttles)
var/list/turfs_tracking_shuttle = adjacent_shuttles[shuttle]
if(turfs_tracking_shuttle[to_stop_tracking])
turfs_tracking_shuttle -= to_stop_tracking
if(!length(turfs_tracking_shuttle))
stop_tracking_shuttle(shuttle)
shuttle_tracking_turfs -= to_stop_tracking
UnregisterSignal(to_stop_tracking, list(COMSIG_TURF_ON_SHUTTLE_MOVE, COMSIG_TURF_REMOVED_FROM_SHUTTLE, COMSIG_TURF_AFTER_SHUTTLE_MOVE, COMSIG_TURF_ADDED_TO_SHUTTLE))
/datum/shuttle_frame/proc/shuttle_leave_react(turf/source)
SIGNAL_HANDLER
for(var/obj/docking_port/mobile/custom/shuttle as anything in adjacent_shuttles)
var/list/turfs_tracking_shuttle = adjacent_shuttles[shuttle]
if(turfs_tracking_shuttle[source])
turfs_tracking_shuttle -= source
if(!length(turfs_tracking_shuttle))
stop_tracking_shuttle(shuttle)
break
/datum/shuttle_frame/proc/shuttle_arrive_react(turf/source)
SIGNAL_HANDLER
var/obj/docking_port/mobile/custom/shuttle = SSshuttle.get_containing_shuttle(source)
if(istype(shuttle))
start_tracking_shuttle(shuttle)
adjacent_shuttles[shuttle][source] = TRUE
/datum/shuttle_frame/proc/start_tracking_shuttle(obj/docking_port/mobile/custom/shuttle)
adjacent_shuttles[shuttle] = list()
/datum/shuttle_frame/proc/stop_tracking_shuttle(obj/docking_port/mobile/custom/shuttle)
adjacent_shuttles -= shuttle
/datum/shuttle_frame/proc/add_turf(turf/new_turf)
if(GLOB.shuttle_frames_by_turf[new_turf])
stack_trace("turf already assigned to shuttle frame")
return
turfs[new_turf] = TRUE
if(SSshuttle.get_containing_shuttle(new_turf))
shuttle_covered_turfs[new_turf] = TRUE
if(shuttle_tracking_turfs[new_turf])
stop_tracking_turf_for_shuttles(new_turf)
RegisterSignal(new_turf, COMSIG_TURF_ON_SHUTTLE_MOVE, PROC_REF(shuttle_uncover_react))
RegisterSignal(new_turf, COMSIG_TURF_AFTER_SHUTTLE_MOVE, PROC_REF(shuttle_cover_react))
GLOB.shuttle_frames_by_turf[new_turf] = src
for(var/dir in GLOB.cardinals)
var/turf/neighbor = get_step(new_turf, dir)
if(turfs[neighbor])
continue
start_tracking_turf_for_shuttles(neighbor, REVERSE_DIR(dir))
/datum/shuttle_frame/proc/shuttle_cover_react(turf/source)
shuttle_covered_turfs[source] = TRUE
/datum/shuttle_frame/proc/shuttle_uncover_react(turf/source)
shuttle_covered_turfs -= source
/datum/shuttle_frame/proc/add_turfs(list/turfs)
for(var/turf in turfs)
add_turf(turf)
/datum/shuttle_frame/proc/remove_turf(turf/removed_turf)
if(possibly_valid_changing_turfs[removed_turf])
return
UnregisterSignal(removed_turf, list(COMSIG_TURF_ON_SHUTTLE_MOVE, COMSIG_TURF_AFTER_SHUTTLE_MOVE))
for(var/dir in GLOB.cardinals)
var/turf/check_turf = get_step(removed_turf, dir)
if(!turfs[check_turf])
shuttle_tracking_turfs[check_turf] &= ~REVERSE_DIR(dir)
if(!shuttle_tracking_turfs[check_turf])
stop_tracking_turf_for_shuttles(check_turf)
continue
else
shuttle_tracking_turfs[removed_turf] |= dir
deferred_update_turfs[check_turf] = TRUE
shuttle_covered_turfs -= removed_turf
turfs -= removed_turf
GLOB.shuttle_frames_by_turf -= removed_turf
if(!length(turfs))
qdel(src)
else
addtimer(CALLBACK(src, PROC_REF(auto_propagate_turf_removal)), 0, TIMER_UNIQUE | TIMER_DELETE_ME)
/datum/shuttle_frame/proc/remove_turfs(list/turfs)
for(var/turf in turfs)
remove_turf(turf)
/datum/shuttle_frame/proc/auto_propagate_turf_removal()
var/list/islands = list()
var/list/island_queues = list()
for(var/deferred_update_turf in deferred_update_turfs)
if(!turfs[deferred_update_turf])
continue
var/list/new_island = list()
new_island[deferred_update_turf] = TRUE
islands += list(list())
island_queues += list(new_island)
deferred_update_turfs.Cut()
var/islands_modified = TRUE
while(islands_modified && length(islands) > 1)
islands_modified = FALSE
for(var/island_idx in 1 to length(islands))
var/list/island = islands[island_idx]
var/list/queue = island_queues[island_idx]
if(!length(queue))
continue
var/turf/check_turf = popleft(queue)
if(!turfs[check_turf])
continue
island[check_turf] = TRUE
islands_modified = TRUE
var/list/to_enqueue = list()
for(var/dir in GLOB.cardinals)
var/turf/next_check_turf = get_step(check_turf, dir)
if(!(next_check_turf && turfs[next_check_turf]) || island[next_check_turf] || queue[next_check_turf])
continue
to_enqueue[next_check_turf] = TRUE
var/list/merge_indices = list()
for(var/other_island_idx in 1 to length(islands))
if(island_idx == other_island_idx)
continue
for(var/turf/enqueued_turf as anything in to_enqueue)
if(islands[other_island_idx][enqueued_turf])
merge_indices |= other_island_idx
to_enqueue -= enqueued_turf
for(var/merge_index in merge_indices)
island |= islands[merge_index]
queue |= island_queues[merge_index]
queue |= to_enqueue
var/total_removed = 0
for(var/merge_index in merge_indices)
var/effective_index = merge_index - total_removed
islands.Cut(effective_index, effective_index+1)
island_queues.Cut(effective_index, effective_index+1)
total_removed++
if(total_removed)
break
if(length(islands) < 2)
return
popleft(islands)
for(var/other_island in islands)
remove_turfs(other_island)
new /datum/shuttle_frame(other_island)
/proc/assign_shuttle_construction_turf_to_frame(turf/new_turf)
var/list/adjacent_frames = list()
for(var/dir in GLOB.cardinals)
var/turf/neighbor = get_step(new_turf, dir)
if(!neighbor)
continue
var/datum/shuttle_frame/frame = GLOB.shuttle_frames_by_turf[neighbor]
if(!frame)
continue
adjacent_frames |= frame
switch(length(adjacent_frames))
if(0)
new /datum/shuttle_frame(list(new_turf))
if(1)
var/datum/shuttle_frame/frame = adjacent_frames[1]
frame.add_turf(new_turf)
else
var/datum/shuttle_frame/frame = popleft(adjacent_frames)
for(var/datum/shuttle_frame/other_frame as anything in adjacent_frames)
var/list/turfs = other_frame.turfs.Copy()
other_frame.remove_turfs(turfs)
frame.add_turfs(turfs)
frame.add_turf(new_turf)
/// Helper proc that tests to ensure all whiteship templates can spawn at their docking port, and logs their sizes
/// This should be a unit test, but too much of our other code breaks during shuttle movement, so not yet, not yet.
/proc/test_whiteship_sizes()
var/obj/docking_port/stationary/port_type = /obj/docking_port/stationary/picked/whiteship
var/datum/turf_reservation/docking_yard = SSmapping.request_turf_block_reservation(
initial(port_type.width),
initial(port_type.height),
1,
)
var/turf/bottom_left = docking_yard.bottom_left_turfs[1]
var/turf/spawnpoint = locate(
bottom_left.x + initial(port_type.dwidth),
bottom_left.y + initial(port_type.dheight),
bottom_left.z,
)
var/obj/docking_port/stationary/picked/whiteship/port = new(spawnpoint)
var/list/ids = port.shuttlekeys
var/height = 0
var/width = 0
var/dheight = 0
var/dwidth = 0
var/delta_height = 0
var/delta_width = 0
for(var/id in ids)
var/datum/map_template/shuttle/our_template = SSmapping.shuttle_templates[id]
// We do a standard load here so any errors will properly runtimes
var/obj/docking_port/mobile/ship = SSshuttle.action_load(our_template, port)
if(ship)
ship.jumpToNullSpace()
ship = null
// Yes this is very hacky, but we need to both allow loading a template that's too big to be an error state
// And actually get the sizing information from every shuttle
SSshuttle.load_template(our_template)
var/obj/docking_port/mobile/theoretical_ship = SSshuttle.preview_shuttle
if(theoretical_ship)
height = max(theoretical_ship.height, height)
width = max(theoretical_ship.width, width)
dheight = max(theoretical_ship.dheight, dheight)
dwidth = max(theoretical_ship.dwidth, dwidth)
delta_height = max(theoretical_ship.height - theoretical_ship.dheight, delta_height)
delta_width = max(theoretical_ship.width - theoretical_ship.dwidth, delta_width)
theoretical_ship.jumpToNullSpace()
qdel(port, TRUE)
log_world("Whiteship sizing information. Use this to set the docking port, and the map size\n\
Max Height: [height] \n\
Max Width: [width] \n\
Max DHeight: [dheight] \n\
Max DWidth: [dwidth] \n\
The following are the safest bet for map sizing. Anything smaller then this could in the worst case not fit in the docking port\n\
Max Combined Width: [height + dheight] \n\
Max Combinded Height [width + dwidth]")
/proc/custom_shuttle_room_check(obj/docking_port/mobile/custom/shuttle, list/neighboring_areas = list(), turf/check_turf)
if(SSshuttle.get_containing_shuttle(check_turf) != shuttle)
return EXTRA_ROOM_CHECK_SKIP
var/area/check_area = check_turf.loc
if(check_area != shuttle.default_area)
neighboring_areas[check_area] = TRUE
return EXTRA_ROOM_CHECK_SKIP
var/move_mode = MOVE_AREA
move_mode = check_turf.fromShuttleMove(move_mode = move_mode)
if(move_mode & (MOVE_CONTENTS | MOVE_TURF))
return
for(var/atom/movable/movable as anything in check_turf.contents)
CHECK_TICK
if(movable.loc != check_turf)
continue
move_mode = movable.hypotheticalShuttleMove(0, move_mode, shuttle)
if(!(move_mode & (MOVE_CONTENTS | MOVE_TURF)))
return EXTRA_ROOM_CHECK_SKIP
/proc/shuttle_build_check(turf/origin, list/turfs, list/areas)
var/z = origin.z
var/using_prepassed_turfs = !!length(turfs)
if(using_prepassed_turfs && !(turfs[origin]))
. |= ORIGIN_NOT_ON_SHUTTLE
if(length(SSshuttle.custom_shuttles) >= CONFIG_GET(number/max_shuttle_count))
. |= TOO_MANY_SHUTTLES
var/max_turfs = CONFIG_GET(number/max_shuttle_size)
if(!using_prepassed_turfs)
var/datum/shuttle_frame/frame = GLOB.shuttle_frames_by_turf[origin]
if(!frame)
. |= ORIGIN_NOT_ON_SHUTTLE
else
turfs += (frame.turfs - frame.shuttle_covered_turfs)
var/turf_count = length(turfs)
if(turf_count > max_turfs)
. |= ABOVE_MAX_SHUTTLE_SIZE
. |= shuttle_area_check(turfs.Copy(), areas, z)
/proc/shuttle_expand_check(turf/origin, obj/docking_port/mobile/shuttle, list/turfs, list/areas)
var/z = origin.z
var/using_prepassed_turfs = !!length(turfs)
if(using_prepassed_turfs && !(turfs[origin]))
. |= ORIGIN_NOT_ON_SHUTTLE
var/max_turfs = CONFIG_GET(number/max_shuttle_size) - shuttle.turf_count
var/list/adjacent_shuttles
if(!using_prepassed_turfs)
var/datum/shuttle_frame/frame = GLOB.shuttle_frames_by_turf[origin]
if(!frame)
. |= ORIGIN_NOT_ON_SHUTTLE
else
turfs += (frame.turfs - frame.shuttle_covered_turfs)
adjacent_shuttles = frame.adjacent_shuttles
var/turf_count = length(turfs)
if(turf_count > max_turfs)
. |= ABOVE_MAX_SHUTTLE_SIZE
if(adjacent_shuttles && !adjacent_shuttles[shuttle])
. |= FRAME_NOT_ADJACENT_TO_LINKED_SHUTTLE
. |= shuttle_area_check(turfs.Copy(), areas, z)
/*
* Check to see if the following conditions are met:
* 1. All turfs in the region are within whitelisted areas
* 2. The region does not contain the APC of a non-custom area
* 3. If the region contains the APC of a custom area, it contains the entire area
*/
/proc/shuttle_area_check(list/turfs, list/areas, z)
for(var/area/custom_area as anything in GLOB.custom_areas)
var/list/area_turfs = custom_area.get_turfs_by_zlevel(z)
var/turf_count = length(area_turfs)
if(!turf_count)
continue
var/list/turfs_not_in_frame = area_turfs - turfs
var/turfs_not_in_frame_count = length(turfs_not_in_frame)
if(turfs_not_in_frame_count == turf_count)
continue
if(turfs_not_in_frame_count)
if(custom_area.apc)
var/obj/machinery/power/apc/apc = custom_area.apc
var/list/wallmount_comps = apc.GetComponents(/datum/component/wall_mounted)
var/datum/component/wall_mounted/wallmount_comp = length(wallmount_comps) && wallmount_comps[1]
if(turfs[get_turf(apc)] || (wallmount_comp && turfs[wallmount_comp.hanging_wall_turf]))
. |= CUSTOM_AREA_NOT_COMPLETELY_CONTAINED
else if(areas)
areas[custom_area] = area_turfs - turfs_not_in_frame
turfs -= area_turfs
while(length(turfs))
var/turf/checked_turf = pick(turfs)
var/area/checked_area = checked_turf.loc
var/list/area_turfs = checked_area.get_turfs_by_zlevel(z)
if(!checked_area.allow_shuttle_docking)
. |= INTERSECTS_NON_WHITELISTED_AREA
if(checked_area.apc)
var/obj/machinery/power/apc/apc = checked_area.apc
var/list/wallmount_comps = apc.GetComponents(/datum/component/wall_mounted)
var/datum/component/wall_mounted/wallmount_comp = length(wallmount_comps) && wallmount_comps[1]
if(turfs[get_turf(apc)] || (wallmount_comp && turfs[wallmount_comp.hanging_wall_turf]))
. |= CONTAINS_APC_OF_NON_CUSTOM_AREA
turfs -= area_turfs
/proc/convert_areas_to_shuttle_areas(list/turfs, list/in_areas, list/out_areas, list/underlying_areas, area_type = /area/shuttle/custom)
for(var/area/area as anything in in_areas)
var/area/new_area = new area_type()
new_area.setup(area.name)
out_areas += new_area
var/list/area_turfs = in_areas[area]
var/datum/component/custom_area/custom_area = area.GetComponent(/datum/component/custom_area)
if(custom_area)
underlying_areas += (custom_area.previous_areas & area_turfs)
set_turfs_to_area(area_turfs, new_area)
turfs -= area_turfs
new_area.reg_in_areas_in_z()
new_area.create_area_lighting_objects()
new_area.power_change()
for(var/obj/machinery/door/firedoor/firelock as anything in area.firedoors)
firelock.CalculateAffectingAreas()
if(!area.has_contained_turfs())
qdel(area)
/proc/create_shuttle(mob/user, turf/origin, list/turfs, list/areas, shuttle_dir, port_dir = NORTH, area_type = /area/shuttle/custom, docking_port_type = /obj/docking_port/mobile/custom, obj/docking_port/stationary/dock_at, name, id, replace, custom = TRUE, force)
if(!ispath(docking_port_type, /obj/docking_port/mobile))
CRASH("docking_port_type must be /obj/docking_port/mobile or a subpath")
if(!ispath(area_type, /area/shuttle))
CRASH("area_type must be /area/shuttle or a subpath")
if(!istype(dock_at))
dock_at = new(origin)
dock_at.unregister()
dock_at.delete_after = TRUE
dock_at.shuttle_id = null
var/area/origin_area = get_area(origin)
dock_at.name = origin_area.name
dock_at.dir = port_dir
var/list/default_area_turfs = turfs.Copy()
// Convert each custom area into a shuttle area, then remove the affected turfs from the list of turfs to add to the default area
var/list/shuttle_areas = list()
var/list/underlying_areas = list()
convert_areas_to_shuttle_areas(default_area_turfs, areas, shuttle_areas, underlying_areas, area_type)
for(var/turf/turf as anything in default_area_turfs)
underlying_areas[turf] = turf.loc
// Merge the remaining frame turfs into a default shuttle area
var/list/affected_areas = list()
var/area/default_area = new area_type()
default_area.setup(name)
set_turfs_to_area(default_area_turfs, default_area, affected_areas)
default_area.reg_in_areas_in_z()
default_area.create_area_lighting_objects()
default_area.power_change()
for(var/area_name in affected_areas)
var/area/merged_area = affected_areas[area_name]
for(var/obj/machinery/door/firedoor/firelock as anything in merged_area.firedoors)
firelock.CalculateAffectingAreas()
if(!merged_area.has_contained_turfs())
qdel(merged_area)
shuttle_areas.Insert(1, default_area)
var/obj/docking_port/mobile/mobile_port = new docking_port_type(origin, shuttle_areas)
mobile_port.underlying_areas_by_turf += underlying_areas
mobile_port.name = name
mobile_port.shuttle_id = id
mobile_port.port_direction = REVERSE_DIR(shuttle_dir)
mobile_port.dir = port_dir
mobile_port.calculate_docking_port_information()
mobile_port.turf_count = length(turfs)
for(var/turf/turf as anything in turfs)
turf.stack_below_baseturf(/turf/open/floor/plating, /turf/baseturf_skipover/shuttle)
SEND_SIGNAL(turf, COMSIG_TURF_ADDED_TO_SHUTTLE, mobile_port)
if(!turf.depth_to_find_baseturf(/turf/baseturf_skipover/shuttle))
continue
var/turf/new_ceiling = get_step_multiz(turf, UP) // check if a ceiling is needed
if(new_ceiling)
// generate ceiling
if(!(istype(new_ceiling, /turf/open/floor/engine/hull/ceiling) || new_ceiling.depth_to_find_baseturf(/turf/open/floor/engine/hull/ceiling)))
if(istype(new_ceiling, /turf/open/openspace) || istype(new_ceiling, /turf/open/space/openspace))
new_ceiling.place_on_top(/turf/open/floor/engine/hull/ceiling)
else
new_ceiling.stack_ontop_of_baseturf(/turf/open/openspace, /turf/open/floor/engine/hull/ceiling)
new_ceiling.stack_ontop_of_baseturf(/turf/open/space/openspace, /turf/open/floor/engine/hull/ceiling)
mobile_port.register(replace, custom)
if(mobile_port.get_docked() != dock_at)
mobile_port.initiate_docking(dock_at, force = TRUE)
message_admins("[key_name(user)] has created a shuttle at [ADMIN_VERBOSEJMP(origin)].")
log_shuttle("[key_name(user)] has created a shuttle at [get_area(origin)].")
return mobile_port
/proc/expand_shuttle(mob/user, obj/docking_port/mobile/shuttle, list/turfs, list/areas)
var/list/default_area_turfs = turfs.Copy()
// Convert each custom area into a shuttle area, then remove the affected turfs from the list of turfs to add to the default area
var/list/shuttle_areas = list()
var/list/underlying_areas = list()
convert_areas_to_shuttle_areas(default_area_turfs, areas, shuttle_areas, underlying_areas, shuttle.area_type)
for(var/turf/turf as anything in default_area_turfs)
underlying_areas[turf] = turf.loc
var/list/affected_areas = list()
var/area/default_area = shuttle.shuttle_areas[1]
set_turfs_to_area(default_area_turfs, default_area, affected_areas)
default_area.power_change()
for(var/area_name in affected_areas)
var/area/merged_area = affected_areas[area_name]
for(var/obj/machinery/door/firedoor/firelock as anything in merged_area.firedoors)
firelock.CalculateAffectingAreas()
if(!merged_area.has_contained_turfs())
qdel(merged_area)
for(var/area/shuttle_area as anything in shuttle_areas)
shuttle.shuttle_areas[shuttle_area] = TRUE
var/list/bounds = shuttle.return_coords()
var/x0 = bounds[1]
var/y0 = bounds[2]
var/x1 = bounds[3]
var/y1 = bounds[4]
var/bounds_need_recalculation
for(var/turf/turf as anything in turfs)
turf.stack_below_baseturf(/turf/open/floor/plating, /turf/baseturf_skipover/shuttle)
SEND_SIGNAL(turf, COMSIG_TURF_ADDED_TO_SHUTTLE, shuttle)
if(turf.depth_to_find_baseturf(/turf/baseturf_skipover/shuttle))
var/turf/new_ceiling = get_step_multiz(turf, UP) // check if a ceiling is needed
if(new_ceiling)
// generate ceiling
if(!(istype(new_ceiling, /turf/open/floor/engine/hull/ceiling) || new_ceiling.depth_to_find_baseturf(/turf/open/floor/engine/hull/ceiling)))
if(istype(new_ceiling, /turf/open/openspace) || istype(new_ceiling, /turf/open/space/openspace))
new_ceiling.place_on_top(/turf/open/floor/engine/hull/ceiling)
else
new_ceiling.stack_ontop_of_baseturf(/turf/open/openspace, /turf/open/floor/engine/hull/ceiling)
new_ceiling.stack_ontop_of_baseturf(/turf/open/space/openspace, /turf/open/floor/engine/hull/ceiling)
if(bounds_need_recalculation)
continue
if(!(ISINRANGE(turf.x, x0, x1) && ISINRANGE(turf.y, y0, y1)))
bounds_need_recalculation = TRUE
shuttle.turf_count += length(turfs)
shuttle.underlying_areas_by_turf += underlying_areas
SEND_SIGNAL(shuttle, COMSIG_SHUTTLE_EXPANDED, turfs)
if(bounds_need_recalculation)
QDEL_NULL(shuttle.assigned_transit)
shuttle.calculate_docking_port_information()
shuttle.initiate_docking(shuttle.get_docked(), force = TRUE)
message_admins("[key_name(user)] has expanded [shuttle] at [ADMIN_VERBOSEJMP(user)].")
log_shuttle("[key_name(user)] expanded [shuttle] at [get_area(user)].")
/proc/clear_empty_shuttle_turfs(obj/docking_port/mobile/shuttle)
var/shuttle_z = shuttle.z
var/bounds_need_recalculation
var/docking_port_needs_relocated
var/list/bounds = shuttle.return_coords()
var/x0 = bounds[1]
var/y0 = bounds[2]
var/x1 = bounds[3]
var/y1 = bounds[4]
for(var/area/area as anything in shuttle.shuttle_areas)
var/list/turfs = area.get_turfs_by_zlevel(shuttle_z)
turfs = turfs.Copy()
for(var/turf/turf as anything in turfs)
var/move_mode = turf.fromShuttleMove(move_mode = MOVE_AREA)
if(move_mode & (MOVE_TURF | MOVE_CONTENTS))
continue
for(var/atom/movable/movable as anything in turf.contents)
//CHECK_TICK
if(movable.loc != turf)
continue
if(movable == shuttle)
continue
move_mode = movable.hypotheticalShuttleMove(0, move_mode, shuttle)
if(move_mode & (MOVE_TURF | MOVE_CONTENTS))
continue
if(shuttle.loc == turf)
docking_port_needs_relocated = TRUE
bounds_need_recalculation = TRUE
var/area/new_area = shuttle.underlying_areas_by_turf[turf]
if(!istype(new_area))
new_area = GLOB.areas_by_type[SHUTTLE_DEFAULT_UNDERLYING_AREA]
if(!istype(new_area))
new_area = new SHUTTLE_DEFAULT_UNDERLYING_AREA(null)
shuttle.underlying_areas_by_turf -= turf
shuttle.turf_count--
turfs -= turf
turf.change_area(area, new_area)
SEND_SIGNAL(turf, COMSIG_TURF_REMOVED_FROM_SHUTTLE, shuttle)
if(bounds_need_recalculation)
continue
if(turf.x == x0 || turf.x == x1 || turf.y == y0 || turf.y == y1)
bounds_need_recalculation = TRUE
if(!length(turfs))
var/obj/docking_port/mobile/custom/as_custom = shuttle
if(!(istype(as_custom) && area == as_custom.default_area))
shuttle.shuttle_areas -= area
qdel(area)
if(!shuttle.turf_count)
qdel(shuttle)
return
if(docking_port_needs_relocated)
shuttle.forceMove(pick(shuttle.underlying_areas_by_turf))
if(bounds_need_recalculation)
QDEL_NULL(shuttle.assigned_transit)
shuttle.calculate_docking_port_information()