Files
Bubberstation/code/modules/shuttle/shuttle.dm
SkyratBot 6e677a2bbd [MIRROR] Invisibility refactor [MDB IGNORE] (#24405)
* Invisibility refactor (#78908)

This adds a tracker for sources of invisibility and a priority system. I
needed this for another thing so I'm doing this first since it touches a
lot of code. As for the bugs fixed in the changelog, it's only what I
noticed while going through everything and there's likely a few more
things fixed with this. This should be testmerged for a while, I'll
bring this out of draft when it feels safe.

🆑
admin: Invisimin can now be used on mobs that are already invisible,
whether through temporary or permanent effects.
fix: Monkeyize/Humanize mob transformations no longer permanently reveal
invisible mobs if they had effects making them invisible otherwise.
fix: Objects with the undertile element that have been made invisible
through other means are no longer revealed by being uncovered.
/🆑

* Invisibility refactor

---------

Co-authored-by: Emmett Gaines <ninjanomnom@gmail.com>
2023-10-18 00:36:09 -04:00

1226 lines
39 KiB
Plaintext

//use this define to highlight docking port bounding boxes (ONLY FOR DEBUG USE)
#ifdef TESTING
#define DOCKING_PORT_HIGHLIGHT
#endif
//NORTH default dir
/obj/docking_port
invisibility = INVISIBILITY_ABSTRACT
icon = 'icons/obj/device.dmi'
icon_state = "pinonfar"
resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF
anchored = TRUE
///Common standard is for this to point -away- from the dockingport door, ie towards the ship
dir = NORTH
/// The identifier of the port or ship.
/// This will be used in numerous other places like the console,
/// stationary ports and whatnot to tell them your ship's mobile
/// port can be used in these places, or the docking port is compatible, etc.
var/shuttle_id
/// Possible destinations
var/port_destinations
///size of covered area, perpendicular to dir. You shouldn't modify this for mobile dockingports, set automatically.
var/width = 0
///size of covered area, parallel to dir. You shouldn't modify this for mobile dockingports, set automatically.
var/height = 0
///position relative to covered area, perpendicular to dir. You shouldn't modify this for mobile dockingports, set automatically.
var/dwidth = 0
///position relative to covered area, parallel to dir. You shouldn't modify this for mobile dockingports, set automatically.
var/dheight = 0
var/area_type
///are we invisible to shuttle navigation computers?
var/hidden = FALSE
///Delete this port after ship fly off.
var/delete_after = FALSE
///are we registered in SSshuttles?
var/registered = FALSE
///register to SSshuttles
/obj/docking_port/proc/register()
if(registered)
WARNING("docking_port registered multiple times")
unregister()
registered = TRUE
return
///unregister from SSshuttles
/obj/docking_port/proc/unregister()
if(!registered)
WARNING("docking_port unregistered multiple times")
registered = FALSE
return
/obj/docking_port/proc/Check_id()
return
//these objects are indestructible
/obj/docking_port/Destroy(force)
// unless you assert that you know what you're doing. Horrible things
// may result.
if(force)
..()
return QDEL_HINT_QUEUE
else
return QDEL_HINT_LETMELIVE
/obj/docking_port/has_gravity(turf/current_turf)
return TRUE
/obj/docking_port/take_damage(damage_amount, damage_type = BRUTE, damage_flag = "", sound_effect = TRUE, attack_dir, armour_penetration = 0)
return
/obj/docking_port/singularity_pull()
return
/obj/docking_port/singularity_act()
return FALSE
/obj/docking_port/shuttleRotate()
return //we don't rotate with shuttles via this code.
///returns a list(x0,y0, x1,y1) where points 0 and 1 are bounding corners of the projected rectangle
/obj/docking_port/proc/return_coords(_x, _y, _dir)
if(_dir == null)
_dir = dir
if(_x == null)
_x = x
if(_y == null)
_y = y
//byond's sin and cos functions are inaccurate. This is faster and perfectly accurate
var/cos = 1
var/sin = 0
switch(_dir)
if(WEST)
cos = 0
sin = 1
if(SOUTH)
cos = -1
sin = 0
if(EAST)
cos = 0
sin = -1
return list(
_x + (-dwidth*cos) - (-dheight*sin),
_y + (-dwidth*sin) + (-dheight*cos),
_x + (-dwidth+width-1)*cos - (-dheight+height-1)*sin,
_y + (-dwidth+width-1)*sin + (-dheight+height-1)*cos,
)
///returns turfs within our projected rectangle in no particular order
/obj/docking_port/proc/return_turfs()
var/list/L = return_coords()
var/turf/T0 = locate(L[1],L[2],z)
var/turf/T1 = locate(L[3],L[4],z)
return block(T0,T1)
///returns turfs within our projected rectangle in a specific order.this ensures that turfs are copied over in the same order, regardless of any rotation
/obj/docking_port/proc/return_ordered_turfs(_x, _y, _z, _dir)
var/cos = 1
var/sin = 0
switch(_dir)
if(WEST)
cos = 0
sin = 1
if(SOUTH)
cos = -1
sin = 0
if(EAST)
cos = 0
sin = -1
. = list()
for(var/dx in 0 to width-1)
var/compX = dx-dwidth
for(var/dy in 0 to height-1)
var/compY = dy-dheight
// realX = _x + compX*cos - compY*sin
// realY = _y + compY*cos - compX*sin
// locate(realX, realY, _z)
var/turf/T = locate(_x + compX*cos - compY*sin, _y + compY*cos + compX*sin, _z)
.[T] = NONE
#ifdef DOCKING_PORT_HIGHLIGHT
//Debug proc used to highlight bounding area
/obj/docking_port/proc/highlight(_color = "#f00")
SetInvisibility(INVISIBILITY_NONE)
SET_PLANE_IMPLICIT(src, GHOST_PLANE)
var/list/L = return_coords()
var/turf/T0 = locate(L[1],L[2],z)
var/turf/T1 = locate(L[3],L[4],z)
for(var/turf/T in block(T0,T1))
T.color = _color
LAZYINITLIST(T.atom_colours)
T.maptext = null
if(_color)
var/turf/T = locate(L[1], L[2], z)
T.color = "#0f0"
T = locate(L[3], L[4], z)
T.color = "#00f"
#endif
//return first-found touching dockingport
/obj/docking_port/proc/get_docked()
return locate(/obj/docking_port/stationary) in loc
// Return id of the docked docking_port
/obj/docking_port/proc/getDockedId()
var/obj/docking_port/P = get_docked()
if(P)
return P.shuttle_id
// Say that A in the absolute (rectangular) bounds of this shuttle or no.
/obj/docking_port/proc/is_in_shuttle_bounds(atom/A)
var/turf/T = get_turf(A)
if(T.z != z)
return FALSE
var/list/bounds = return_coords()
var/x0 = bounds[1]
var/y0 = bounds[2]
var/x1 = bounds[3]
var/y1 = bounds[4]
if(!ISINRANGE(T.x, min(x0, x1), max(x0, x1)))
return FALSE
if(!ISINRANGE(T.y, min(y0, y1), max(y0, y1)))
return FALSE
return TRUE
/obj/docking_port/stationary
name = "dock"
var/last_dock_time
/// Map template to load when the dock is loaded
var/datum/map_template/shuttle/roundstart_template
/// Used to check if the shuttle template is enabled in the config file
var/json_key
///If true, the shuttle can always dock at this docking port, despite its area checks, or if something is already docked
var/override_can_dock_checks = FALSE
/obj/docking_port/stationary/register(replace = FALSE)
. = ..()
if(!shuttle_id)
shuttle_id = "dock"
else
port_destinations = shuttle_id
if(!name)
name = "dock"
var/counter = SSshuttle.assoc_stationary[shuttle_id]
if(!replace || !counter)
if(counter)
counter++
SSshuttle.assoc_stationary[shuttle_id] = counter
shuttle_id = "[shuttle_id]_[counter]"
name = "[name] [counter]"
else
SSshuttle.assoc_stationary[shuttle_id] = 1
if(!port_destinations)
port_destinations = shuttle_id
SSshuttle.stationary_docking_ports += src
/obj/docking_port/stationary/Initialize(mapload)
. = ..()
register()
if(!area_type)
var/area/place = get_area(src)
area_type = place?.type // We might be created in nullspace
if(mapload)
for(var/turf/T in return_turfs())
T.turf_flags |= NO_RUINS
if(SSshuttle.initialized)
INVOKE_ASYNC(SSshuttle, TYPE_PROC_REF(/datum/controller/subsystem/shuttle, setup_shuttles), list(src))
#ifdef DOCKING_PORT_HIGHLIGHT
highlight("#f00")
#endif
/obj/docking_port/stationary/unregister()
. = ..()
SSshuttle.stationary_docking_ports -= src
/obj/docking_port/stationary/Destroy(force)
if(force)
unregister()
return ..()
/obj/docking_port/stationary/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change = TRUE)
. = ..()
if(area_type) // We already have one
return
var/area/newarea = get_area(src)
area_type = newarea?.type
/obj/docking_port/stationary/proc/load_roundstart()
if(json_key)
var/sid = SSmapping.config.shuttles[json_key]
roundstart_template = SSmapping.shuttle_templates[sid]
if(!roundstart_template)
CRASH("json_key:[json_key] value \[[sid]\] resulted in a null shuttle template for [src]")
else if(roundstart_template) // passed a PATH
var/sid = "[initial(roundstart_template.port_id)]_[initial(roundstart_template.suffix)]"
roundstart_template = SSmapping.shuttle_templates[sid]
if(!roundstart_template)
CRASH("Invalid path ([sid]/[roundstart_template]) passed to docking port.")
if(roundstart_template)
SSshuttle.action_load(roundstart_template, src)
//returns first-found touching shuttleport
/obj/docking_port/stationary/get_docked()
. = locate(/obj/docking_port/mobile) in loc
/// Subtype for escape pod ports so that we can give them trait behaviour
/obj/docking_port/stationary/escape_pod
name = "escape pod loader"
height = 5
width = 3
dwidth = 1
roundstart_template = /datum/map_template/shuttle/escape_pod/default
/// Set to true if you have a snowflake escape pod dock which needs to always have the normal pod or some other one
var/enforce_specific_pod = FALSE
/obj/docking_port/stationary/escape_pod/Initialize(mapload)
. = ..()
if (enforce_specific_pod)
return
if (HAS_TRAIT(SSstation, STATION_TRAIT_SMALLER_PODS))
roundstart_template = /datum/map_template/shuttle/escape_pod/cramped
return
if (HAS_TRAIT(SSstation, STATION_TRAIT_BIGGER_PODS))
roundstart_template = /datum/map_template/shuttle/escape_pod/luxury
// should fit the syndicate infiltrator, and smaller ships like the battlecruiser corvettes and fighters
/obj/docking_port/stationary/syndicate
name = "near the station"
dheight = 1
dwidth = 12
height = 17
width = 23
shuttle_id = "syndicate_nearby"
/obj/docking_port/stationary/syndicate/northwest
name = "northwest of station"
shuttle_id = "syndicate_nw"
/obj/docking_port/stationary/syndicate/northeast
name = "northeast of station"
shuttle_id = "syndicate_ne"
/obj/docking_port/stationary/transit
name = "In Transit"
override_can_dock_checks = TRUE
/// The turf reservation returned by the transit area request
var/datum/turf_reservation/reserved_area
/// The area created during the transit area reservation
var/area/shuttle/transit/assigned_area
/// The mobile port that owns this transit port
var/obj/docking_port/mobile/owner
/obj/docking_port/stationary/transit/Initialize(mapload)
. = ..()
SSshuttle.transit_docking_ports += src
/obj/docking_port/stationary/transit/Destroy(force=FALSE)
if(force)
if(get_docked())
log_world("A transit dock was destroyed while something was docked to it.")
SSshuttle.transit_docking_ports -= src
if(owner)
if(owner.assigned_transit == src)
owner.assigned_transit = null
owner = null
if(!QDELETED(reserved_area))
qdel(reserved_area)
reserved_area = null
return ..()
/obj/docking_port/stationary/picked
///Holds a list of map name strings for the port to pick from
var/list/shuttlekeys
/obj/docking_port/stationary/picked/Initialize(mapload)
. = ..()
if(!LAZYLEN(shuttlekeys))
WARNING("Random docking port [shuttle_id] loaded with no shuttle keys")
return
var/selectedid = pick(shuttlekeys)
roundstart_template = SSmapping.shuttle_templates[selectedid]
/obj/docking_port/stationary/picked/whiteship
name = "Deep Space"
shuttle_id = "whiteship_away"
height = 45 //Width and height need to remain in sync with the size of whiteshipdock.dmm, otherwise we'll get overflow
width = 44
dheight = 18
dwidth = 18
dir = 2
shuttlekeys = list(
"whiteship_meta",
"whiteship_pubby",
"whiteship_box",
"whiteship_cere",
"whiteship_kilo",
"whiteship_donut",
"whiteship_delta",
"whiteship_tram",
"whiteship_personalshuttle",
"whiteship_obelisk",
)
/// 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]")
/obj/docking_port/mobile
name = "shuttle"
icon_state = "pinonclose"
area_type = SHUTTLE_DEFAULT_SHUTTLE_AREA_TYPE
///List of all areas our shuttle holds.
var/list/shuttle_areas = list()
///List of all currently used engines that propels us.
var/list/obj/machinery/power/shuttle_engine/engine_list = list()
///How fast the shuttle should be, taking engine thrust into account.
var/engine_coeff = 1
///How much engine power (thrust) the shuttle currently has.
var/current_engine_power = 0
///How much engine power (thrust) the shuttle starts with at mapload.
var/initial_engine_power = 0
///Speed multiplier based on station alert level
var/alert_coeff = ALERT_COEFF_BLUE
///used as a timer (if you want time left to complete move, use timeLeft proc)
var/timer
var/last_timer_length
///current shuttle mode
var/mode = SHUTTLE_IDLE
///time spent in transit (deciseconds). Should not be lower then 10 seconds without editing the animation of the hyperspace ripples.
var/callTime = 100
/// time spent "starting the engines". Also rate limits how often we try to reserve transit space if its ever full of transiting shuttles.
var/ignitionTime = 55
/// time spent after arrival before being able to begin ignition
var/rechargeTime = 0
/// time spent after transit 'landing' before actually arriving
var/prearrivalTime = 0
/// The direction the shuttle prefers to travel in, ie what direction the animation will cause it to appear to be traveling in
var/preferred_direction = NORTH
/// relative direction of the docking port from the front of the shuttle. NORTH is towards front, EAST would be starboard side, WEST port, etc.
var/port_direction = NORTH
var/obj/docking_port/stationary/destination
var/obj/docking_port/stationary/previous
var/obj/docking_port/stationary/transit/assigned_transit
var/launch_status = NOLAUNCH
var/list/ripples = list()
///Whether or not you want your ship to knock people down, and also whether it will throw them several tiles upon launching.
var/list/movement_force = list(
"KNOCKDOWN" = 3,
"THROW" = 0,
)
///if this shuttle can move docking ports other than the one it is docked at
var/can_move_docking_ports = FALSE
var/list/hidden_turfs = list()
///List of shuttle events that can run or are running
var/list/datum/shuttle_event/event_list = list()
var/admin_forced = FALSE //SKYRAT EDIT ADDITION
#define WORLDMAXX_CUTOFF (world.maxx + 1)
#define WORLDMAXY_CUTOFF (world.maxx + 1)
/**
* Calculated and populates the information used for docking and some internal vars.
* This can also be used to calculate from shuttle_areas so that you can expand/shrink shuttles!
*
* Arguments:
* * loading_from - The template that the shuttle was loaded from, if not given we iterate shuttle_areas to calculate information instead
*/
/obj/docking_port/mobile/proc/calculate_docking_port_information(datum/map_template/shuttle/loading_from)
var/port_x_offset = loading_from?.port_x_offset
var/port_y_offset = loading_from?.port_y_offset
var/width = loading_from?.width
var/height = loading_from?.height
if(!loading_from)
if(!length(shuttle_areas))
CRASH("Attempted to calculate a docking port's information without a template before it was assigned any areas!")
// no template given, use shuttle_areas to calculate width and height
var/min_x = -1
var/min_y = -1
var/max_x = WORLDMAXX_CUTOFF
var/max_y = WORLDMAXY_CUTOFF
for(var/area/area as anything in shuttle_areas)
for(var/turf/turf in area)
min_x = max(turf.x, min_x)
max_x = min(turf.x, max_x)
min_y = max(turf.y, min_y)
max_y = min(turf.y, max_y)
CHECK_TICK
if(min_x == -1 || max_x == WORLDMAXX_CUTOFF)
CRASH("Failed to locate shuttle boundaries when iterating through shuttle areas, somehow.")
if(min_y == -1 || max_y == WORLDMAXY_CUTOFF)
CRASH("Failed to locate shuttle boundaries when iterating through shuttle areas, somehow.")
width = (max_x - min_x) + 1
height = (max_y - min_y) + 1
port_x_offset = min_x - x
port_y_offset = min_y - y
if(dir in list(EAST, WEST))
src.width = height
src.height = width
else
src.width = width
src.height = height
switch(dir)
if(NORTH)
dwidth = port_x_offset - 1
dheight = port_y_offset - 1
if(EAST)
dwidth = height - port_y_offset
dheight = port_x_offset - 1
if(SOUTH)
dwidth = width - port_x_offset
dheight = height - port_y_offset
if(WEST)
dwidth = port_y_offset - 1
dheight = width - port_x_offset
#undef WORLDMAXX_CUTOFF
#undef WORLDMAXY_CUTOFF
/**
* Actions to be taken after shuttle is loaded but before it has been moved out of transit z-level to its final location
*
* Arguments:
* * replace - TRUE if this shuttle is replacing an existing one. FALSE by default.
*/
/obj/docking_port/mobile/register(replace = FALSE)
. = ..()
if(!shuttle_id)
shuttle_id = "shuttle"
if(!name)
name = "shuttle"
var/counter = SSshuttle.assoc_mobile[shuttle_id]
if(!replace || !counter)
if(counter)
counter++
SSshuttle.assoc_mobile[shuttle_id] = counter
shuttle_id = "[shuttle_id]_[counter]"
name = "[name] [counter]"
//Re link machinery to new shuttle id
linkup()
else
SSshuttle.assoc_mobile[shuttle_id] = 1
SSshuttle.mobile_docking_ports += src
/**
* Actions to be taken after shuttle is loaded and has been moved to its final location
*
* Arguments:
* * replace - TRUE if this shuttle is replacing an existing one. FALSE by default.
*/
/obj/docking_port/mobile/proc/postregister(replace = FALSE)
return
/obj/docking_port/mobile/unregister()
. = ..()
SSshuttle.mobile_docking_ports -= src
/obj/docking_port/mobile/Destroy(force)
unregister()
destination = null
previous = null
if(!QDELETED(assigned_transit))
qdel(assigned_transit, force = TRUE)
assigned_transit = null
shuttle_areas = null
remove_ripples()
return ..()
/obj/docking_port/mobile/Initialize(mapload)
. = ..()
if(!shuttle_id)
shuttle_id = "shuttle"
if(!name)
name = "shuttle"
var/counter = 1
var/tmp_id = shuttle_id
var/tmp_name = name
while(Check_id(shuttle_id))
counter++
shuttle_id = "[tmp_id]_[counter]"
name = "[tmp_name] [counter]"
var/list/all_turfs = return_ordered_turfs(x, y, z, dir)
for(var/i in 1 to all_turfs.len)
var/turf/curT = all_turfs[i]
var/area/cur_area = curT.loc
if(istype(cur_area, area_type))
shuttle_areas[cur_area] = TRUE
#ifdef DOCKING_PORT_HIGHLIGHT
highlight("#0f0")
#endif
// Called after the shuttle is loaded from template, so we make sure they know it's from mapload.
/obj/docking_port/mobile/proc/linkup(obj/docking_port/stationary/dock)
for(var/area/place as anything in shuttle_areas)
place.connect_to_shuttle(TRUE, src, dock)
for(var/atom/individual_atoms in place)
individual_atoms.connect_to_shuttle(TRUE, src, dock)
//this is a hook for custom behaviour. Maybe at some point we could add checks to see if engines are intact
/obj/docking_port/mobile/proc/canMove()
return TRUE
//this is to check if this shuttle can physically dock at dock stationary_dock
/obj/docking_port/mobile/proc/canDock(obj/docking_port/stationary/stationary_dock)
if(!istype(stationary_dock))
return SHUTTLE_NOT_A_DOCKING_PORT
if(stationary_dock.override_can_dock_checks)
return SHUTTLE_CAN_DOCK
if(dwidth > stationary_dock.dwidth)
return SHUTTLE_DWIDTH_TOO_LARGE
if(width-dwidth > stationary_dock.width-stationary_dock.dwidth)
return SHUTTLE_WIDTH_TOO_LARGE
if(dheight > stationary_dock.dheight)
return SHUTTLE_DHEIGHT_TOO_LARGE
if(height-dheight > stationary_dock.height-stationary_dock.dheight)
return SHUTTLE_HEIGHT_TOO_LARGE
//check the dock isn't occupied
var/currently_docked = stationary_dock.get_docked()
if(currently_docked)
// by someone other than us
if(currently_docked != src)
return SHUTTLE_SOMEONE_ELSE_DOCKED
else
// This isn't an error, per se, but we can't let the shuttle code
// attempt to move us where we currently are, it will get weird.
return SHUTTLE_ALREADY_DOCKED
return SHUTTLE_CAN_DOCK
/obj/docking_port/mobile/proc/check_dock(obj/docking_port/stationary/S, silent = FALSE)
var/status = canDock(S)
if(status == SHUTTLE_CAN_DOCK)
return TRUE
else
if(status != SHUTTLE_ALREADY_DOCKED && !silent) // SHUTTLE_ALREADY_DOCKED is no cause for error
message_admins("Shuttle [src] cannot dock at [S], error: [status]")
// We're already docked there, don't need to do anything.
// Triggering shuttle movement code in place is weird
return FALSE
/obj/docking_port/mobile/proc/transit_failure()
message_admins("Shuttle [src] repeatedly failed to create transit zone.")
/**
* Calls the shuttle to the destination port, respecting its ignition and call timers
*
* Arguments:
* * destination_port - Stationary docking port to move the shuttle to
*/
/obj/docking_port/mobile/proc/request(obj/docking_port/stationary/destination_port, forced = FALSE) // SKYRAT EDIT ADDITION - Forced check
if(!check_dock(destination_port) && !forced) // SKYRAT EDIT ADDITION - Forced check
testing("check_dock failed on request for [src]")
return
// SKYRAT EDIT START - Forced check
if(forced)
admin_forced = TRUE
// SKYRAT EDIT END
if(mode == SHUTTLE_IGNITING && destination == destination_port)
return
switch(mode)
if(SHUTTLE_CALL)
if(destination_port == destination)
if(timeLeft(1) < callTime * engine_coeff)
setTimer(callTime * engine_coeff)
else
destination = destination_port
setTimer(callTime * engine_coeff)
if(SHUTTLE_RECALL)
if(destination_port == destination)
setTimer(callTime * engine_coeff - timeLeft(1))
else
destination = destination_port
setTimer(callTime * engine_coeff)
mode = SHUTTLE_CALL
if(SHUTTLE_IDLE, SHUTTLE_IGNITING)
destination = destination_port
mode = SHUTTLE_IGNITING
// SKYRAT EDIT ADD START
bolt_all_doors()
play_engine_sound(src, TRUE)
// SKYRAT EDIT ADD END
setTimer(ignitionTime)
//recall the shuttle to where it was previously
/obj/docking_port/mobile/proc/cancel()
if(mode != SHUTTLE_CALL)
return
remove_ripples()
invertTimer()
mode = SHUTTLE_RECALL
/obj/docking_port/mobile/proc/enterTransit()
if((SSshuttle.lockdown && is_station_level(z)) || !canMove()) //emp went off, no escape
mode = SHUTTLE_IDLE
return
previous = null
if(!destination)
// sent to transit with no destination -> unlimited timer
timer = INFINITY
var/obj/docking_port/stationary/S0 = get_docked()
var/obj/docking_port/stationary/S1 = assigned_transit
if(S1)
if(initiate_docking(S1) != DOCKING_SUCCESS)
WARNING("shuttle \"[shuttle_id]\" could not enter transit space. Docked at [S0 ? S0.shuttle_id : "null"]. Transit dock [S1 ? S1.shuttle_id : "null"].")
else if(S0)
if(S0.delete_after)
qdel(S0, TRUE)
else
previous = S0
else
WARNING("shuttle \"[shuttle_id]\" could not enter transit space. S0=[S0 ? S0.shuttle_id : "null"] S1=[S1 ? S1.shuttle_id : "null"]")
/obj/docking_port/mobile/proc/jumpToNullSpace()
// Destroys the docking port and the shuttle contents.
// Not in a fancy way, it just ceases.
var/obj/docking_port/stationary/current_dock = get_docked()
var/underlying_area_type = SHUTTLE_DEFAULT_UNDERLYING_AREA
// If the shuttle is docked to a stationary port, restore its normal
// "empty" area and turf
if(current_dock?.area_type)
underlying_area_type = current_dock.area_type
var/list/old_turfs = return_ordered_turfs(x, y, z, dir)
var/area/underlying_area = GLOB.areas_by_type[underlying_area_type]
if(!underlying_area)
underlying_area = new underlying_area_type(null)
for(var/i in 1 to old_turfs.len)
var/turf/oldT = old_turfs[i]
if(!oldT || !istype(oldT.loc, area_type))
continue
oldT.change_area(oldT.loc, underlying_area)
oldT.empty(FALSE)
// Here we locate the bottommost shuttle boundary and remove all turfs above it
var/shuttle_tile_depth = oldT.depth_to_find_baseturf(/turf/baseturf_skipover/shuttle)
if (!isnull(shuttle_tile_depth))
oldT.ScrapeAway(shuttle_tile_depth)
qdel(src, force=TRUE)
/**
* Ghosts and marks as escaped (for greentext purposes) all mobs, then deletes the shuttle.
* Used by the Shuttle Manipulator
*/
/obj/docking_port/mobile/proc/intoTheSunset()
// Loop over mobs
for(var/turf/turfs as anything in return_turfs())
for(var/mob/living/sunset_mobs in turfs.get_all_contents())
// If they have a mind and they're not in the brig, they escaped
if(sunset_mobs.mind && !istype(get_area(sunset_mobs), /area/shuttle/escape/brig))
sunset_mobs.mind.force_escaped = TRUE
// Ghostize them and put them in nullspace stasis (for stat & possession checks)
ADD_TRAIT(sunset_mobs, TRAIT_NO_TRANSFORM, REF(src))
sunset_mobs.ghostize(FALSE)
sunset_mobs.moveToNullspace()
// Now that mobs are stowed, delete the shuttle
jumpToNullSpace()
/obj/docking_port/mobile/proc/create_ripples(obj/docking_port/stationary/S1, animate_time)
var/list/turfs = ripple_area(S1)
for(var/t in turfs)
ripples += new /obj/effect/abstract/ripple(t, animate_time)
/obj/docking_port/mobile/proc/remove_ripples()
QDEL_LIST(ripples)
/obj/docking_port/mobile/proc/ripple_area(obj/docking_port/stationary/S1)
var/list/L0 = return_ordered_turfs(x, y, z, dir)
var/list/L1 = return_ordered_turfs(S1.x, S1.y, S1.z, S1.dir)
var/list/ripple_turfs = list()
for(var/i in 1 to L0.len)
var/turf/T0 = L0[i]
var/turf/T1 = L1[i]
if(!T0 || !T1)
continue // out of bounds
if(!istype(T0.loc, area_type) || istype(T0.loc, /area/shuttle/transit))
continue // not part of the shuttle
ripple_turfs += T1
return ripple_turfs
/obj/docking_port/mobile/proc/check_poddoors()
for(var/obj/machinery/door/poddoor/shuttledock/pod as anything in SSmachines.get_machines_by_type_and_subtypes(/obj/machinery/door/poddoor/shuttledock))
pod.check()
/obj/docking_port/mobile/proc/dock_id(id)
var/port = SSshuttle.getDock(id)
if(port)
. = initiate_docking(port)
else
. = null
//used by shuttle subsystem to check timers
/obj/docking_port/mobile/proc/check()
check_effects()
//process_events() if you were to add events to non-escape shuttles, uncomment this
if(mode == SHUTTLE_IGNITING)
check_transit_zone()
if(timeLeft(1) > 0)
return
// If we can't dock or we don't have a transit slot, wait for 20 ds,
// then try again
switch(mode)
if(SHUTTLE_CALL, SHUTTLE_PREARRIVAL)
if(prearrivalTime && mode != SHUTTLE_PREARRIVAL)
mode = SHUTTLE_PREARRIVAL
setTimer(prearrivalTime)
return
var/error = initiate_docking(destination, preferred_direction)
if(error && error & (DOCKING_NULL_DESTINATION | DOCKING_NULL_SOURCE))
var/msg = "A mobile dock in transit exited initiate_docking() with an error. This is most likely a mapping problem: Error: [error], ([src]) ([previous][ADMIN_JMP(previous)] -> [destination][ADMIN_JMP(destination)])"
WARNING(msg)
message_admins(msg)
mode = SHUTTLE_IDLE
return
else if(error)
setTimer(20)
return
if(rechargeTime)
mode = SHUTTLE_RECHARGING
unbolt_all_doors() //SKYRAT EDIT ADDITION
setTimer(rechargeTime)
return
if(SHUTTLE_RECALL)
if(initiate_docking(previous) != DOCKING_SUCCESS)
setTimer(20)
return
if(SHUTTLE_IGNITING)
if(check_transit_zone() != TRANSIT_READY)
setTimer(20)
return
else
mode = SHUTTLE_CALL
setTimer(callTime * engine_coeff)
enterTransit()
return
admin_forced = FALSE //SKYRAT EDIT ADDITION
unbolt_all_doors() //SKYRAT EDIT ADDITION
mode = SHUTTLE_IDLE
timer = 0
destination = null
/obj/docking_port/mobile/proc/check_effects()
if(!ripples.len)
if((mode == SHUTTLE_CALL) || (mode == SHUTTLE_RECALL))
var/tl = timeLeft(1)
if(tl <= SHUTTLE_RIPPLE_TIME)
create_ripples(destination, tl)
play_engine_sound(src, FALSE) //SKYRAT EDIT ADDITION
play_engine_sound(destination, FALSE) //SKYRAT EDIT ADDITION
var/obj/docking_port/stationary/S0 = get_docked()
if(istype(S0, /obj/docking_port/stationary/transit) && timeLeft(1) <= PARALLAX_LOOP_TIME)
for(var/place in shuttle_areas)
var/area/shuttle/shuttle_area = place
if(shuttle_area.parallax_movedir)
parallax_slowdown()
/obj/docking_port/mobile/proc/parallax_slowdown()
for(var/place in shuttle_areas)
var/area/shuttle/shuttle_area = place
shuttle_area.parallax_movedir = FALSE
if(assigned_transit?.assigned_area)
assigned_transit.assigned_area.parallax_movedir = FALSE
var/list/L0 = return_ordered_turfs(x, y, z, dir)
for (var/thing in L0)
var/turf/T = thing
if(!T || !istype(T.loc, area_type))
continue
for (var/atom/movable/movable as anything in T)
if (movable.client_mobs_in_contents)
movable.update_parallax_contents()
/obj/docking_port/mobile/proc/check_transit_zone()
if(assigned_transit)
return TRANSIT_READY
else
SSshuttle.request_transit_dock(src)
/obj/docking_port/mobile/proc/setTimer(wait)
timer = world.time + wait
last_timer_length = wait
/obj/docking_port/mobile/proc/modTimer(multiple)
var/time_remaining = timer - world.time
if(time_remaining < 0 || !last_timer_length)
return
time_remaining *= multiple
last_timer_length *= multiple
setTimer(time_remaining)
/obj/docking_port/mobile/proc/alert_coeff_change(new_coeff)
if(isnull(new_coeff))
return
var/time_multiplier = new_coeff / alert_coeff
var/time_remaining = timer - world.time
if(time_remaining < 0 || !last_timer_length)
return
time_remaining *= time_multiplier
last_timer_length *= time_multiplier
alert_coeff = new_coeff
setTimer(time_remaining)
/obj/docking_port/mobile/proc/invertTimer()
if(!last_timer_length)
return
var/time_remaining = timer - world.time
if(time_remaining > 0)
var/time_passed = last_timer_length - time_remaining
setTimer(time_passed)
//returns timeLeft
/obj/docking_port/mobile/proc/timeLeft(divisor)
if(divisor <= 0)
divisor = 10
var/ds_remaining
if(!timer)
ds_remaining = callTime * engine_coeff
else
ds_remaining = max(0, timer - world.time)
. = round(ds_remaining / divisor, 1)
// returns 3-letter mode string, used by status screens and mob status panel
/obj/docking_port/mobile/proc/getModeStr()
switch(mode)
if(SHUTTLE_IGNITING)
return "IGN"
if(SHUTTLE_RECALL)
return "RCL"
if(SHUTTLE_CALL)
return "ETA"
if(SHUTTLE_DOCKED)
return "ETD"
if(SHUTTLE_ESCAPE)
return "ESC"
if(SHUTTLE_STRANDED)
return "ERR"
if(SHUTTLE_RECHARGING)
return "RCH"
if(SHUTTLE_PREARRIVAL)
return "LDN"
if(SHUTTLE_DISABLED)
return "DIS"
return ""
// returns 5-letter timer string, used by status screens and mob status panel
/obj/docking_port/mobile/proc/getTimerStr()
if(mode == SHUTTLE_STRANDED || mode == SHUTTLE_DISABLED)
return "--:--"
var/timeleft = timeLeft()
if(timeleft > 1 HOURS)
return "--:--"
else if(timeleft > 0)
return "[add_leading(num2text((timeleft / 60) % 60), 2, "0")]:[add_leading(num2text(timeleft % 60), 2, "0")]"
else
return "00:00"
/**
* Gets shuttle location status in a form of string for tgui interfaces
*/
/obj/docking_port/mobile/proc/get_status_text_tgui()
var/obj/docking_port/stationary/dockedAt = get_docked()
var/docked_at = dockedAt?.name || "Unknown"
if(!istype(dockedAt, /obj/docking_port/stationary/transit))
return docked_at
if(timeLeft() > 1 HOURS)
return "Hyperspace"
else
var/obj/docking_port/stationary/dst = (mode == SHUTTLE_RECALL) ? previous : destination
return "In transit to [dst?.name || "unknown location"]"
/obj/docking_port/mobile/proc/getStatusText()
var/obj/docking_port/stationary/dockedAt = get_docked()
var/docked_at = dockedAt?.name || "unknown"
if(istype(dockedAt, /obj/docking_port/stationary/transit))
if (timeLeft() > 1 HOURS)
return "hyperspace"
else
var/obj/docking_port/stationary/dst
if(mode == SHUTTLE_RECALL)
dst = previous
else
dst = destination
. = "transit towards [dst?.name || "unknown location"] ([getTimerStr()])"
else if(mode == SHUTTLE_RECHARGING)
return "[docked_at], recharging [getTimerStr()]"
else
return docked_at
/obj/docking_port/mobile/proc/getDbgStatusText()
var/obj/docking_port/stationary/dockedAt = get_docked()
. = (dockedAt?.name) ? dockedAt.name : "unknown"
if(istype(dockedAt, /obj/docking_port/stationary/transit))
var/obj/docking_port/stationary/dst
if(mode == SHUTTLE_RECALL)
dst = previous
else
dst = destination
if(dst)
. = "(transit to) [dst.name || dst.shuttle_id]"
else
. = "(transit to) nowhere"
else if(dockedAt)
. = dockedAt.name || dockedAt.shuttle_id
else
. = "unknown"
// attempts to locate /obj/machinery/computer/shuttle with matching ID inside the shuttle
/obj/docking_port/mobile/proc/get_control_console()
for(var/area/shuttle/shuttle_area as anything in shuttle_areas)
var/obj/machinery/computer/shuttle/shuttle_computer = locate(/obj/machinery/computer/shuttle) in shuttle_area
if(!shuttle_computer)
continue
if(shuttle_computer.shuttleId == shuttle_id)
return shuttle_computer
return null
/obj/docking_port/mobile/proc/hyperspace_sound(phase, list/areas)
var/selected_sound
switch(phase)
if(HYPERSPACE_WARMUP)
selected_sound = "hyperspace_begin"
if(HYPERSPACE_LAUNCH)
selected_sound = "hyperspace_progress"
if(HYPERSPACE_END)
selected_sound = "hyperspace_end"
else
CRASH("Invalid hyperspace sound phase: [phase]")
// This previously was played from each door at max volume, and was one of the worst things I had ever seen.
// Now it's instead played from the nearest engine if close, or the first engine in the list if far since it doesn't really matter.
// Or a door if for some reason the shuttle has no engine, fuck oh hi daniel fuck it
var/range = (engine_coeff * max(width, height))
var/long_range = range * 2.5
var/atom/distant_source
if(engine_list.len)
distant_source = engine_list[1]
else
for(var/our_area in areas)
distant_source = locate(/obj/machinery/door) in our_area
if(distant_source)
break
if(!distant_source)
return
for(var/mob/zlevel_mobs as anything in SSmobs.clients_by_zlevel[z])
var/dist_far = get_dist(zlevel_mobs, distant_source)
if(dist_far <= long_range && dist_far > range)
zlevel_mobs.playsound_local(distant_source, "sound/runtime/hyperspace/[selected_sound]_distance.ogg", 100)
else if(dist_far <= range)
var/source
if(!engine_list.len)
source = distant_source
else
var/closest_dist = 10000
for(var/obj/machinery/power/shuttle_engine/engines as anything in engine_list)
var/dist_near = get_dist(zlevel_mobs, engines)
if(dist_near < closest_dist)
source = engines
closest_dist = dist_near
zlevel_mobs.playsound_local(source, "sound/runtime/hyperspace/[selected_sound].ogg", 100)
// Losing all initial engines should get you 2
// Adding another set of engines at 0.5 time
/obj/docking_port/mobile/proc/alter_engines(mod)
if(!mod)
return
var/old_coeff = engine_coeff
engine_coeff = get_engine_coeff(mod)
current_engine_power = max(0, current_engine_power + mod)
if(in_flight())
var/delta_coeff = engine_coeff / old_coeff
modTimer(delta_coeff)
// Double initial engines to get to 0.5 minimum
// Lose all initial engines to get to 2
//For 0 engine shuttles like BYOS 5 engines to get to doublespeed
/obj/docking_port/mobile/proc/get_engine_coeff(engine_mod)
var/new_value = max(0, current_engine_power + engine_mod)
if(new_value == initial_engine_power)
return 1
if(new_value > initial_engine_power)
var/delta = new_value - initial_engine_power
var/change_per_engine = (1 - ENGINE_COEFF_MIN) / ENGINE_DEFAULT_MAXSPEED_ENGINES // 5 by default
if(initial_engine_power > 0)
change_per_engine = (1 - ENGINE_COEFF_MIN) / initial_engine_power // or however many it had
return clamp(1 - delta * change_per_engine,ENGINE_COEFF_MIN, ENGINE_COEFF_MAX)
if(new_value < initial_engine_power)
var/delta = initial_engine_power - new_value
var/change_per_engine = 1 //doesn't really matter should not be happening for 0 engine shuttles
if(initial_engine_power > 0)
change_per_engine = (ENGINE_COEFF_MAX - 1) / initial_engine_power //just linear drop to max delay
return clamp(1 + delta * change_per_engine, ENGINE_COEFF_MIN, ENGINE_COEFF_MAX)
/obj/docking_port/mobile/proc/in_flight()
switch(mode)
if(SHUTTLE_CALL,SHUTTLE_RECALL,SHUTTLE_PREARRIVAL)
return TRUE
if(SHUTTLE_IDLE,SHUTTLE_IGNITING)
return FALSE
return FALSE // hmm
/obj/docking_port/mobile/emergency/in_flight()
switch(mode)
if(SHUTTLE_ESCAPE)
return TRUE
if(SHUTTLE_STRANDED,SHUTTLE_ENDGAME)
return FALSE
return ..()
//Called when emergency shuttle leaves the station
/obj/docking_port/mobile/proc/on_emergency_launch()
if(launch_status == UNLAUNCHED) //Pods will not launch from the mine/planet, and other ships won't launch unless we tell them to.
launch_status = ENDGAME_LAUNCHED
enterTransit()
///Let people know shits about to go down
/obj/docking_port/mobile/proc/announce_shuttle_events()
for(var/datum/shuttle_event/event as anything in event_list)
notify_ghosts("The [name] has selected: [event.name]")
/obj/docking_port/mobile/emergency/on_emergency_launch()
return
//Called when emergency shuttle docks at centcom
/obj/docking_port/mobile/proc/on_emergency_dock()
// Mapping a new docking point for each ship mappers could potentially want docking with centcom would take up lots of space,
// just let them keep flying off "into the sunset" for their greentext.
if(launch_status == ENDGAME_LAUNCHED)
launch_status = ENDGAME_TRANSIT
/obj/docking_port/mobile/pod/on_emergency_dock()
if(launch_status == ENDGAME_LAUNCHED)
initiate_docking(SSshuttle.getDock("[shuttle_id]_away")) //Escape pods dock at centcom
mode = SHUTTLE_ENDGAME
/obj/docking_port/mobile/emergency/on_emergency_dock()
return
///Process all the shuttle events for every shuttle tick we get
/obj/docking_port/mobile/proc/process_events()
var/list/removees
for(var/datum/shuttle_event/event as anything in event_list)
if(event.event_process() == SHUTTLE_EVENT_CLEAR) //if we return SHUTTLE_EVENT_CLEAR, we clean them up
LAZYADD(removees, event)
for(var/item in removees)
event_list.Remove(item)
#ifdef TESTING
#undef DOCKING_PORT_HIGHLIGHT
#endif