Custom Shuttles Redux: Allows for the construction of custom shuttles. (#88493)

## About The Pull Request

This incredibly detailed PR adds the ability to construct custom
shuttles, which function similarly to whiteships.

To construct a custom shuttle, you need the following items:
- Shuttle frame rods
These rods can be hand-crafted by using 5 rods on 1 sheet of titanium,
or printed at a sci/engi/cargo lathe after researching the Shuttle
Engineering techweb node.
Lattices built with these rods, and catwalks/floors built on top of
these lattices, are valid for shuttle construction.
- Shuttle engines
Did you know shuttle engines have boards that weren't normally
obtainable? Well the board for one specific engine type is now available
from the sci/engi/cargo lathe after researching Shuttle Engineering. Of
course, the old options remain. You can steal engines from other
shuttles, including escape pods (it's not like engines are strictly
necessary for *those* shuttles anyways). Alternatively, the shuttle
engine supply pack is no longer locked behind the purchase of the BYOS.
- Flight Control & Navigation Console boards
These boards are printed at the sci/engi/cargo lathe after researching
the Shuttle Engineering techweb node.
If built on a custom shuttle, it will automatically link to it, unless
the shuttle already has such a console.
If built on a turf that is valid for custom shuttle construction, it
will automatically link to any shuttle constructed from or expanded with
that turf.
- Shuttle blueprints
Standard shuttle blueprints can be printed at the sci/engi/cargo lathe
after researching the Shuttle Engineering techweb node.
A cyborg upgrade granting access to a shuttle blueprint database can be
printed at the exofab after researching the aforementioned node.
Crude shuttle blueprints can be crafted by hand with a sheet of paper
and either a rainbow crayon or 10 uses of a blue crayon or spraycan.

If Science won't research the tech, you can also buy a goody pack
containing a flight control board, a docker board, two engine boards,
and a set of shuttle blueprints for 1200 credits, if you have aux base
access.

A shuttle can be constructed atop any continuous region of turfs
containing a shuttle rod lattice or a catwalk/tile built upon such.
Currently, this region cannot intersect any area other than space,
lavaland, the icemoon, or the station asteroid. Preexisting custom areas
can be included in the construction of the shuttle, but only if every
turf in the custom area is valid for shuttle construction.

In the shuttle blueprint UI, you can toggle a visualizer to display
which turfs fulfill all of the aforementioned conditions.

The following video goes through the basic process of shuttle
construction.


https://github.com/user-attachments/assets/3283422e-a201-4978-972d-67527b5df4ee

The blueprint used to construct the shuttle will be its master
blueprint. The master blueprint can be copied to other blank shuttle
blueprints (or to engiborgs with the shuttle database upgrade), and
allows the holder to perform a christening ritual on the shuttle to
rename it.
If a shuttle's master blueprint ceases to exist, a blank blueprint can
be linked to the shuttle to become the new master blueprint, or an
existing blueprint associated with that shuttle can be promoted to the
master blueprint.

Once constructed, the following options are available from the blueprint
UI to modify it:
- Create Area
Convert a continuous open area of the shuttle into a new area with the
name written in the above text input. This operates very similarly to
regular area construction.
- Rename Area
Change the name of the area you're currently in to the name written in
the above text input.
- Expand Area
Add a continuous open area of the shuttle to the neighboring area
selected from the dropdown to the left. This operates like regular area
expansion.
- Expand Shuttle
Expand a shuttle with valid frame turfs as defined above. These turfs
must be physically connected to the shuttle.
- Remove Area
    Remove an area, giving its tiles to the default shuttle area.
- Cleanup Empty Space (implemented after the above video was recorded)
Removes all completely empty turfs from the shuttle. If all the turfs in
one of the shuttle's areas were removed, that area is deleted. If
absolutely no turfs of the shuttle remain, the shuttle itself is
deleted. Due to the ability for this action to delete the shuttle, only
the master blueprint can do it.

As mentioned above, the shuttle's master blueprint can be used to
christen its associated shuttle. To do this, fill a glass drink bottle
with some amount of reagents, then hit it against one of the shuttle's
walls from outside while holding the master blueprint. You will be
prompted to enter a new name for the shuttle. The variety of things that
can happen while inputting a new name can cause the christening rite to
fail in one of several humorous ways.

### Optional (Unless specifically requested by a maintainer) Todo's
- [x] A way for shuttle circuits to be obtainable without techweb nodes
- [x] A more convenient way to carry around shuttle engines or the means
to deploy them
- [ ] A shuttle construction guide available as a reference book
- [ ] Allow boards to be linked to shuttles before construction so they
can be used outside the shuttle

## Why It's Good For The Game

Shuttles have been part of the sandbox for an incredibly long time, but
their limited accessibility has rendered them the exclusive territory of
lucky space explorers or the few antagonists who get one off the bat
(nukies and pirates). Giving players the means to construct shuttles to
their liking opens up a variety of possibilities for gimmicks for antags
and non-antags alike.

Besides the applications for antaggery and crew-sided gimmicks, this
provides side content for several departments to engage with during the
relatively-frequent periods of time where they have little else to do as
part of their intended roles.

With respect to engineering, if the station isn't actively being
damaged, the supermatter is in perfect working order, and nobody is
clamoring for machine upgrades, engineers have little else to resort to
other than construction projects. While the BSA station goal provides an
incentive for engineers to construct dedicated rooms for the cannon, it
will not necessarily be available every round. Custom shuttles not only
provide such a construction project to pursue, but provide the rare
opportunity, as well as a very good reason, to set up an independent
power network, complete with its own power source.

While atmos techs have a lot to do with gas mixing and the crystallizer,
they rarely get the opportunity to set up working life support systems
outside of repairing the ones that get blown up. Custom shuttles will
frequently start with no air, and unless the design settled upon is an
open floor plan, it will have several independent chambers that cannot
so easily be profused with a proper airmix by just opening a canister.
Furthermore, if the air in a custom shuttle gets messed up, a proper
scrubber and distro network is a significantly less tedious method of
rectifying the problem than cleaning the air manually with portable
scrubbers and pumps.

Scientists, it can be argued, with their access to RPDs through
ordnance, have similar opportunities to atmos techs, even though the act
in and of itself is not exactly part of their duties. But compared to
the other job content they could be working with after they've completed
most of their gameplay loop, custom shuttle construction is a
substantially more active endeavor. And I know how much people complain
about late-game science content just being sitting around at a console
and making gamer gear. Roboticists can have a part to play in this too.
They can put their mech RCDs to a use other than 2D topdown Fortnite,
and with the shuttle database upgrade, they can help interested cyborgs
get in on the action.

Cargo is yet another department known for having significant amounts of
downtime during a considerable number of rounds. If every other
department has gone through their initial rounds of departmental orders,
and there isn't an active need for cargo to order lots of one thing or
another, cargo techs have little to do besides mail (at least on the
days where there **is** mail to deliver). Usually, if cargo techs do, in
fact, do something as a department when not presented with more pressing
duties, they order guns and other contraband. As funny as this is,
there's not a lot of variety in how this behavior manifests. With custom
shuttles, cargo can use their free time to plan, and execute, a unique
collective expression of design sensibilities, not limited by the size
and shape constraints of the cargo bay itself.

## Changelog

🆑 Y0SH1_M4S73R (with special thanks to Vect0r, whose original PR
inspired the implementation of these changes)
add: Shuttle blueprints, the tool used to construct and modify custom
shuttles. Print a set at a science, engineering, or cargo techfab after
researching Shuttle Engineering, or craft a crude set from the crafting
menu.
add: Shuttle blueprint database upgrade for engineering cyborgs,
printable from the Exosuit Fabricator after researching Shuttle
Engineering. A version of shuttle blueprints designed for use by
cyborgs.
add: Shuttle frame rods, usable to construct custom shuttles. Hand-craft
by using 5 rods on 1 titanium sheet, or by printing them at a science,
engineering, or cargo techfab after researching Shuttle Engineering.
add: Custom shuttle flight control and navigation boards, printable from
a science, engineering, or cargo techfab after researching Shuttle
Engineering.
add: Shuttle engine boards can be printed from a science, engineering,
or cargo techfab after researching shuttle engineering.
add: The shuttle engine supply pack is no longer locked behind the
purchase of the Build Your Own Shuttle kit.
add: Shuttle Construction Starter Kit goodie pack, containing a set of
shuttle blueprints, flight control and navigation console boards, and
two engine boards, can be purchased from cargo for 1200 credits.
Requires aux base access to purchase.
refactor: Shuttles now keep track of what areas are underneath each of
their individual turfs, so that the areas left behind on movement are
consistent with what they were beforehand.
refactor: Shuttle ceilings now place themselves down as baseturfs,
instead of only appearing if the turf above is open space.
/🆑

---------

Co-authored-by: vect0r <71346830+Vect0r2@users.noreply.github.com>
Co-authored-by: Jeremiah <42397676+jlsnow301@users.noreply.github.com>
Co-authored-by: necromanceranne <40847847+necromanceranne@users.noreply.github.com>
This commit is contained in:
Y0SH1M4S73R
2025-02-23 17:18:43 +11:00
committed by GitHub
co-authored by vect0r Jeremiah necromanceranne
parent 7cb34ef9a8
commit cbc3350224
65 changed files with 2450 additions and 107 deletions
+40 -20
View File
@@ -56,7 +56,12 @@
///List of shuttle events that can run or are running
var/list/datum/shuttle_event/event_list = list()
/obj/docking_port/mobile/Initialize(mapload)
var/list/underlying_areas_by_turf = list()
///How many turfs this shuttle has. Used to check against max shuttle size when expanding expandable shuttles.
var/turf_count = 0
/obj/docking_port/mobile/Initialize(mapload, list/areas)
. = ..()
if(!shuttle_id)
@@ -71,12 +76,17 @@
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
if(areas)
for(var/area/area as anything in areas)
shuttle_areas[area] = TRUE
else
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))
turf_count++
shuttle_areas[cur_area] = TRUE
#ifdef TESTING
highlight("#0f0")
@@ -111,28 +121,28 @@
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
var/min_x = WORLDMAXX_CUTOFF
var/min_y = WORLDMAXY_CUTOFF
var/max_x = -1
var/max_y = -1
for(var/area/shuttle_area as anything in shuttle_areas)
for (var/list/zlevel_turfs as anything in shuttle_area.get_zlevel_turf_lists())
for(var/turf/turf as anything in zlevel_turfs)
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)
min_x = min(turf.x, min_x)
max_x = max(turf.x, max_x)
min_y = min(turf.y, min_y)
max_y = max(turf.y, max_y)
CHECK_TICK
if(min_x == -1 || max_x == WORLDMAXX_CUTOFF)
if(min_x == WORLDMAXX_CUTOFF || max_x == -1)
CRASH("Failed to locate shuttle boundaries when iterating through shuttle areas, somehow.")
if(min_y == -1 || max_y == WORLDMAXY_CUTOFF)
if(min_y == WORLDMAXY_CUTOFF || max_y == -1)
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
port_x_offset = x - min_x + 1
port_y_offset = y - min_y + 1
if(dir in list(EAST, WEST))
src.width = height
@@ -157,13 +167,19 @@
#undef WORLDMAXX_CUTOFF
#undef WORLDMAXY_CUTOFF
/obj/docking_port/mobile/is_in_shuttle_bounds(atom/A)
. = ..()
if(. && !shuttle_areas[get_area(A)])
return FALSE
/**
* 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.
* * custom - TRUE if this shuttle should be added to the custom shuttle list. FALSE by default.
*/
/obj/docking_port/mobile/register(replace = FALSE)
/obj/docking_port/mobile/register(replace = FALSE, custom = FALSE)
. = ..()
if(!shuttle_id)
shuttle_id = "shuttle"
@@ -185,6 +201,9 @@
SSshuttle.mobile_docking_ports += src
if(custom)
SSshuttle.custom_shuttles += src
/**
* Actions to be taken after shuttle is loaded and has been moved to its final location
*
@@ -197,6 +216,7 @@
/obj/docking_port/mobile/unregister()
. = ..()
SSshuttle.mobile_docking_ports -= src
SSshuttle.custom_shuttles -= src
@@ -17,11 +17,11 @@
var/obj/docking_port/stationary/old_dock = get_docked()
// The area that gets placed under where the shuttle moved from
var/underlying_area_type = SHUTTLE_DEFAULT_UNDERLYING_AREA
// The area that gets placed under shuttle turfs that do not have their own area to place down
var/fallback_area_type = SHUTTLE_DEFAULT_UNDERLYING_AREA
if(old_dock) //Dock overwrites
underlying_area_type = old_dock.area_type
fallback_area_type = old_dock.area_type
/**************************************************************************************************************
Both lists are associative with a turf:bitflag structure. (new_turfs bitflag space unused currently)
@@ -33,11 +33,11 @@
CHECK_TICK
/**************************************************************************************************************/
// The underlying old area is the area assumed to be under the shuttle's starting location
// The fallback area is the area for shuttle turfs that have no area underneath them
// If it no longer/has never existed it will be created
var/area/underlying_old_area = GLOB.areas_by_type[underlying_area_type]
if(!underlying_old_area)
underlying_old_area = new underlying_area_type(null)
var/area/fallback_area = GLOB.areas_by_type[fallback_area_type]
if(!fallback_area)
fallback_area = new fallback_area_type(null)
var/rotation = 0
if(new_dock.dir != dir) //Even when the dirs are the same rotation is coming out as not 0 for some reason
@@ -51,8 +51,9 @@
var/list/moved_atoms = list() //Everything not a turf that gets moved in the shuttle
var/list/areas_to_move = list() //unique assoc list of areas on turfs being moved
var/list/underlying_areas = list() //unique assoc list of areas beneath turfs being moved
. = preflight_check(old_turfs, new_turfs, areas_to_move, rotation)
. = preflight_check(old_turfs, new_turfs, areas_to_move, underlying_areas, rotation)
if(.)
remove_ripples()
return
@@ -81,11 +82,11 @@
// Moving to the new location will trample the ripples there at the exact
// same time any mobs there are trampled, to avoid any discrepancy where
// the ripples go away before it is safe.
takeoff(old_turfs, new_turfs, moved_atoms, rotation, movement_direction, old_dock, underlying_old_area)
takeoff(old_turfs, new_turfs, moved_atoms, rotation, movement_direction, old_dock, fallback_area)
CHECK_TICK
cleanup_runway(new_dock, old_turfs, new_turfs, areas_to_move, moved_atoms, rotation, movement_direction, underlying_old_area)
cleanup_runway(new_dock, old_turfs, new_turfs, areas_to_move, underlying_areas, moved_atoms, rotation, movement_direction, fallback_area)
CHECK_TICK
@@ -103,7 +104,7 @@
remove_ripples()
return DOCKING_SUCCESS
/obj/docking_port/mobile/proc/preflight_check(list/old_turfs, list/new_turfs, list/areas_to_move, rotation)
/obj/docking_port/mobile/proc/preflight_check(list/old_turfs, list/new_turfs, list/areas_to_move, list/underlying_areas, rotation)
for(var/i in 1 to length(old_turfs))
CHECK_TICK
var/turf/oldT = old_turfs[i]
@@ -126,11 +127,14 @@
move_mode = newT.toShuttleMove(oldT, move_mode, src) //turfs
if(move_mode & MOVE_AREA)
var/area/underlying_area = underlying_areas_by_turf[oldT]
if(underlying_area)
underlying_areas[underlying_area] = TRUE
areas_to_move[old_area] = TRUE
old_turfs[oldT] = move_mode
/obj/docking_port/mobile/proc/takeoff(list/old_turfs, list/new_turfs, list/moved_atoms, rotation, movement_direction, old_dock, area/underlying_old_area)
/obj/docking_port/mobile/proc/takeoff(list/old_turfs, list/new_turfs, list/moved_atoms, rotation, movement_direction, old_dock, area/fallback_area)
for(var/i in 1 to old_turfs.len)
var/turf/oldT = old_turfs[i]
var/turf/newT = new_turfs[i]
@@ -141,7 +145,7 @@
if(move_mode & MOVE_AREA)
var/area/shuttle_area = oldT.loc
shuttle_area.onShuttleMove(oldT, newT, underlying_old_area) //areas
shuttle_area.onShuttleMove(oldT, newT, src, fallback_area) //areas
if(move_mode & MOVE_CONTENTS)
for(var/k in oldT)
@@ -152,8 +156,12 @@
moved_atoms[moving_atom] = oldT
/obj/docking_port/mobile/proc/cleanup_runway(obj/docking_port/stationary/new_dock, list/old_turfs, list/new_turfs, list/areas_to_move, list/moved_atoms, rotation, movement_direction, area/underlying_old_area)
underlying_old_area.afterShuttleMove(0)
/obj/docking_port/mobile/proc/cleanup_runway(obj/docking_port/stationary/new_dock, list/old_turfs, list/new_turfs, list/areas_to_move, list/underlying_areas, list/moved_atoms, rotation, movement_direction, area/fallback_area)
fallback_area.afterShuttleMove(0)
for(var/i in 1 to underlying_areas.len)
CHECK_TICK
var/area/underlying_area = underlying_areas[i]
underlying_area.afterShuttleMove(0)
// Parallax handling
// This needs to be done before the atom after move
@@ -167,24 +175,28 @@
for(var/i in 1 to old_turfs.len)
CHECK_TICK
if(!(old_turfs[old_turfs[i]] & MOVE_TURF))
continue
var/turf/old_turf = old_turfs[i]
var/old_move_mode = old_turfs[old_turf]
var/turf/old_ceiling = get_step_multiz(old_turf, UP)
if(old_ceiling) // check if a ceiling was generated previously
// remove old ceiling
if(istype(old_ceiling, /turf/open/floor/engine/hull/ceiling))
old_ceiling.ScrapeAway()
else
old_ceiling.remove_baseturfs_from_typecache(list(/turf/open/floor/engine/hull/ceiling = TRUE))
if(!(old_move_mode & MOVE_TURF))
continue
var/turf/new_turf = new_turfs[i]
new_turf.afterShuttleMove(old_turf, rotation) //turfs
var/turf/new_ceiling = get_step_multiz(new_turf, UP) // check if a ceiling is needed
if(new_ceiling)
// generate ceiling
if(istype(new_ceiling, /turf/open/openspace)) // why is this needed? because we have 2 different typepaths for openspace
new_ceiling.ChangeTurf(/turf/open/floor/engine/hull/ceiling, list(/turf/open/openspace))
else if (istype(new_ceiling, /turf/open/space/openspace))
new_ceiling.ChangeTurf(/turf/open/floor/engine/hull/ceiling, list(/turf/open/space/openspace))
var/turf/old_ceiling = get_step_multiz(old_turf, UP)
if(old_ceiling && istype(old_ceiling, /turf/open/floor/engine/hull/ceiling)) // check if a ceiling was generated previously
// remove old ceiling
var/turf/open/floor/engine/hull/ceiling/old_shuttle_ceiling = old_ceiling
old_shuttle_ceiling.ChangeTurf(old_shuttle_ceiling.old_turf_type)
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)
for(var/i in 1 to moved_atoms.len)
CHECK_TICK
var/atom/movable/moved_object = moved_atoms[i]
@@ -195,13 +207,18 @@
// lateShuttleMove (There had better be a really good reason for additional stages beyond this)
underlying_old_area.lateShuttleMove()
fallback_area.lateShuttleMove()
for(var/i in 1 to areas_to_move.len)
CHECK_TICK
var/area/internal_area = areas_to_move[i]
internal_area.lateShuttleMove()
for(var/i in 1 to underlying_areas.len)
CHECK_TICK
var/area/underlying_area = underlying_areas[i]
underlying_area.lateShuttleMove()
for(var/i in 1 to old_turfs.len)
CHECK_TICK
if(!(old_turfs[old_turfs[i]] & MOVE_CONTENTS | MOVE_TURF))
@@ -89,6 +89,11 @@ All ShuttleMove procs go here
/////////////////////////////////////////////////////////////////////////////////////
// Return the move_move (based on the old), without any side effects.
// This is for checking what would be moved if src is on a shuttle being moved.
/atom/movable/proc/hypotheticalShuttleMove(rotation, move_mode, obj/docking_port/mobile/moving_dock)
return move_mode
// Called on every atom in shuttle turf contents before anything has been moved
// returns the new move_mode (based on the old)
// WARNING: Do not leave turf contents in beforeShuttleMove or dock() will runtime
@@ -141,16 +146,19 @@ All ShuttleMove procs go here
return MOVE_AREA
// Called on areas to move their turf between areas
/area/proc/onShuttleMove(turf/oldT, turf/newT, area/underlying_old_area)
/area/proc/onShuttleMove(turf/oldT, turf/newT, obj/docking_port/mobile/shuttle, area/fallback_area)
if(newT == oldT) // In case of in place shuttle rotation shenanigans.
return TRUE
oldT.change_area(src, underlying_old_area)
var/area/underlying_area = shuttle.underlying_areas_by_turf[oldT]
oldT.change_area(src, underlying_area || fallback_area)
shuttle.underlying_areas_by_turf -= oldT
//The old turf has now been given back to the area that turf originaly belonged to
var/area/old_dest_area = newT.loc
parallax_movedir = old_dest_area.parallax_movedir
newT.change_area(old_dest_area, src)
shuttle.underlying_areas_by_turf[newT] = old_dest_area
return TRUE
// Called on areas after everything has been moved
@@ -218,6 +226,11 @@ All ShuttleMove procs go here
break
/obj/machinery/camera/hypotheticalShuttleMove(rotation, move_mode, obj/docking_port/mobile/moving_dock)
. = ..()
if(. & MOVE_AREA)
. |= MOVE_CONTENTS
/obj/machinery/camera/beforeShuttleMove(turf/newT, rotation, move_mode, obj/docking_port/mobile/moving_dock)
. = ..()
if(. & MOVE_AREA)
@@ -323,11 +336,21 @@ All ShuttleMove procs go here
/************************************Structure move procs************************************/
/obj/structure/grille/hypotheticalShuttleMove(rotation, move_mode, obj/docking_port/mobile/moving_dock)
. = ..()
if(. & MOVE_AREA)
. |= MOVE_CONTENTS
/obj/structure/grille/beforeShuttleMove(turf/newT, rotation, move_mode, obj/docking_port/mobile/moving_dock)
. = ..()
if(. & MOVE_AREA)
. |= MOVE_CONTENTS
/obj/structure/lattice/hypotheticalShuttleMove(rotation, move_mode, obj/docking_port/mobile/moving_dock)
. = ..()
if(. & MOVE_AREA)
. |= MOVE_CONTENTS
/obj/structure/lattice/beforeShuttleMove(turf/newT, rotation, move_mode, obj/docking_port/mobile/moving_dock)
. = ..()
if(. & MOVE_AREA)
@@ -342,6 +365,11 @@ All ShuttleMove procs go here
Connect_cable(TRUE)
propagate_if_no_network()
/obj/machinery/power/shuttle_engine/hypotheticalShuttleMove(move_mode)
. = ..()
if(. & MOVE_AREA)
. |= MOVE_CONTENTS
/obj/machinery/power/shuttle_engine/beforeShuttleMove(turf/newT, rotation, move_mode, obj/docking_port/mobile/moving_dock)
. = ..()
if(. & MOVE_AREA)
@@ -365,6 +393,11 @@ All ShuttleMove procs go here
/************************************Misc move procs************************************/
/obj/docking_port/mobile/hypotheticalShuttleMove(rotation, move_mode, obj/docking_port/mobile/moving_dock)
. = ..()
if(moving_dock == src)
. |= MOVE_CONTENTS
/obj/docking_port/mobile/beforeShuttleMove(turf/newT, rotation, move_mode, obj/docking_port/mobile/moving_dock)
. = ..()
if(moving_dock == src)
@@ -7,7 +7,7 @@
return ..()
/obj/docking_port/mobile/assault_pod/initiate_docking(obj/docking_port/stationary/S1)
/obj/docking_port/mobile/assault_pod/initiate_docking(obj/docking_port/stationary/S1, force=FALSE)
. = ..()
if(!istype(S1, /obj/docking_port/stationary/transit))
playsound(get_turf(src.loc), 'sound/effects/explosion/explosion1.ogg',50,TRUE)
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,19 @@
/obj/docking_port/mobile/custom
name = "custom shuttle"
shuttle_id = "custom"
var/datum/weakref/master_blueprint
var/area/default_area
var/datum/weakref/control_console
var/datum/weakref/navigation_console
/obj/docking_port/mobile/custom/Initialize(mapload, list/areas)
. = ..()
default_area = areas[1]
/obj/docking_port/mobile/custom/canMove()
return ..() && (current_engine_power > 0)
/obj/docking_port/mobile/custom/get_engine_coeff()
var/thrust_ratio = (current_engine_power * CUSTOM_ENGINE_POWER_MULTIPLIER)/(turf_count + CUSTOM_ENGINE_POWER_TURF_COUNT_OFFSET)
var/calculated_multiplier = 2*(1-(NUM_E ** -thrust_ratio))
return clamp(1/calculated_multiplier, CUSTOM_ENGINE_COEFF_MIN, CUSTOM_ENGINE_COEFF_MAX)
@@ -0,0 +1,174 @@
GLOBAL_LIST_INIT(custom_shuttle_station_area_whitelist, list(/area/station/asteroid))
/obj/machinery/computer/shuttle/custom_shuttle
desc = "A shuttle control computer."
icon_screen = "shuttle"
icon_keyboard = "tech_key"
shuttleId = ""
light_color = LIGHT_COLOR_CYAN
req_access = list()
interaction_flags_machine = INTERACT_MACHINE_ALLOW_SILICON
possible_destinations = "whiteship_home;"
var/static/list/connections = list(COMSIG_TURF_ADDED_TO_SHUTTLE = PROC_REF(on_loc_added_to_shuttle))
/obj/machinery/computer/shuttle/custom_shuttle/on_construction(mob/user)
circuit.configure_machine(src)
if(!shuttleId)
AddElement(/datum/element/connect_loc, connections)
/obj/machinery/computer/shuttle/custom_shuttle/proc/on_loc_added_to_shuttle(turf/source, obj/docking_port/mobile/custom/port)
SIGNAL_HANDLER
if(!istype(port))
say("Cannot link to this kind of shuttle!")
else
if(connect_to_shuttle(TRUE, port))
RemoveElement(/datum/element/connect_loc, connections)
/obj/machinery/computer/shuttle/custom_shuttle/connect_to_shuttle(mapload, obj/docking_port/mobile/port, obj/docking_port/stationary/dock)
var/obj/docking_port/mobile/custom/custom_port = port
if(istype(custom_port))
if(custom_port.control_console?.resolve())
say("Control console already present!")
return FALSE
. = ..()
if(!.)
return
if(istype(custom_port))
custom_port.control_console = WEAKREF(src)
name = "[port.name] console"
/obj/machinery/computer/shuttle/custom_shuttle/proc/linkShuttle(new_id)
if(shuttleId=="")
shuttleId = new_id
possible_destinations = "whiteship_home;shuttle[new_id]_custom;"
return TRUE
return FALSE
//docking cam
/obj/machinery/computer/camera_advanced/shuttle_docker/custom
lock_override = NONE
jump_to_ports = list("whiteship_home" = 1)
designate_time = 100
circuit = /obj/item/circuitboard/computer/shuttle/docker
var/static/list/connections = list(COMSIG_TURF_ADDED_TO_SHUTTLE = PROC_REF(on_loc_added_to_shuttle))
/obj/machinery/computer/camera_advanced/shuttle_docker/custom/on_construction(mob/user)
circuit.configure_machine(src)
if(!shuttleId)
AddElement(/datum/element/connect_loc, connections)
/obj/machinery/computer/camera_advanced/shuttle_docker/custom/proc/on_loc_added_to_shuttle(turf/source, obj/docking_port/mobile/custom/port)
SIGNAL_HANDLER
if(!istype(port))
say("Cannot link to this kind of shuttle!")
else
if(connect_to_shuttle(TRUE, port))
RemoveElement(/datum/element/connect_loc, connections)
/obj/machinery/computer/camera_advanced/shuttle_docker/custom/Initialize(mapload)
. = ..()
GLOB.jam_on_wardec += src
/obj/machinery/computer/camera_advanced/shuttle_docker/custom/Destroy()
GLOB.jam_on_wardec -= src
return ..()
/obj/machinery/computer/camera_advanced/shuttle_docker/custom/connect_to_shuttle(mapload, obj/docking_port/mobile/port, obj/docking_port/stationary/dock)
if(shuttleId) //We normally should only be connecting newly-created consoles to shuttles, but just in case...
var/obj/docking_port/mobile/old_shuttle = SSshuttle.getShuttle(shuttleId)
if(old_shuttle)
UnregisterSignal(old_shuttle, COMSIG_SHUTTLE_EXPANDED)
var/obj/docking_port/mobile/custom/custom_port = port
if(istype(custom_port))
if(custom_port.navigation_console?.resolve())
say("Navigation console already present!")
return FALSE
. = ..()
if(!.)
return
name = "[port.name] navigation computer"
if(istype(custom_port))
custom_port.navigation_console = WEAKREF(src)
RegisterSignal(port, COMSIG_SHUTTLE_EXPANDED, PROC_REF(on_shuttle_expanded))
recalculate_eye_view(port)
/obj/machinery/computer/camera_advanced/shuttle_docker/custom/proc/on_shuttle_expanded(obj/docking_port/mobile/source, list/turfs)
SIGNAL_HANDLER
recalculate_eye_view(source)
if(my_port)
var/here_x = source.x
var/here_y = source.y
var/there_x = my_port.x
var/there_y = my_port.y
var/target_z = my_port.z
var/rotation = angle2dir_cardinal(dir2angle(my_port.dir) - dir2angle(source.dir))
var/docked = my_port.get_docked() == source
for(var/turf/turf as anything in turfs)
var/turf/checked_turf
if(docked)
checked_turf = turf
else
var/offset_x = turf.x - here_x
var/offset_y = turf.y - here_y
var/target_x = there_x
var/target_y = there_y
switch(rotation)
if(NORTH)
target_x += offset_x
target_y += offset_y
if(SOUTH)
target_x -= offset_x
target_y -= offset_y
if(EAST)
target_x -= offset_y
target_y += offset_x
if(WEST)
target_x += offset_y
target_y -= offset_x
checked_turf = locate(target_x, target_y, target_z)
if(checkLandingTurf(checked_turf) != SHUTTLE_DOCKER_LANDING_CLEAR)
if(docked)
my_port.unregister()
my_port.delete_after = TRUE
my_port.shuttle_id = null
my_port.name = "Old [my_port.name]"
my_port = null
else
QDEL_NULL(my_port)
break
/obj/machinery/computer/camera_advanced/shuttle_docker/custom/proc/recalculate_eye_view(obj/docking_port/mobile/shuttle)
var/bigger_shuttle_dimension = max(shuttle.width, shuttle.height)
var/list/viewsize = getviewsize(world.view)
var/smaller_view_dimension = min(viewsize[1], viewsize[2])
var/new_view_range = max(bigger_shuttle_dimension - smaller_view_dimension, 0)
if(new_view_range != view_range)
view_range = new_view_range
/obj/machinery/computer/camera_advanced/shuttle_docker/custom/placeLandingSpot()
if(!shuttleId)
return //Only way this would happen is if someone else delinks the console while in use somehow
var/obj/docking_port/mobile/M = SSshuttle.getShuttle(shuttleId)
if(M?.mode != SHUTTLE_IDLE)
to_chat(usr, "<span class='warning'>You cannot target locations while in transit.</span>")
return
..()
/obj/machinery/computer/camera_advanced/shuttle_docker/custom/checkLandingTurf(turf/T, list/overlappers)
. = ..()
var/area/area = get_area(T)
if(!is_type_in_list(area, GLOB.custom_shuttle_station_area_whitelist) && is_type_in_list(area, GLOB.the_station_areas))
return SHUTTLE_DOCKER_BLOCKED
/obj/machinery/computer/camera_advanced/shuttle_docker/custom/attack_hand(mob/user)
if(!shuttleId)
to_chat(user, "<span class='warning'>You must link the console to a shuttle first.</span>")
return
return ..()
/obj/machinery/computer/camera_advanced/shuttle_docker/custom/proc/linkShuttle(new_id)
if(shuttleId=="")
shuttleId = new_id
return TRUE
return FALSE
@@ -119,7 +119,7 @@ GLOBAL_LIST_INIT(blacklisted_cargo_types, typecacheof(list(
message_admins("Blacklisted item found on in-transit Cargo Shuttle. See cargo logs for more details.")
SSshuttle.centcom_message = "Contraband found on Cargo Shuttle. This has been returned via drop pod."
/obj/docking_port/mobile/supply/initiate_docking()
/obj/docking_port/mobile/supply/initiate_docking(obj/docking_port/stationary/new_dock, force=FALSE)
if(getDockedId() == "cargo_away") // Buy when we leave home.
buy()
create_mail()
@@ -24,7 +24,7 @@
var/x_offset = 0
///y offset for where the camera eye will spawn. Starts from the shuttle's docking port
var/y_offset = 0
var/list/whitelist_turfs = list(/turf/open/space, /turf/open/floor/plating, /turf/open/lava, /turf/open/openspace)
var/list/whitelist_turfs = list(/turf/open/space, /turf/open/floor/plating, /turf/open/lava, /turf/open/openspace, /turf/open/misc)
var/see_hidden = FALSE
var/designate_time = 0
var/turf/designating_target_loc
@@ -239,6 +239,7 @@
possible_destinations = replacetext(replacetextEx(possible_destinations, "[shuttleId]_custom", ""), ";;", ";")
shuttleId = port.shuttle_id
possible_destinations += ";[port.shuttle_id]_custom"
return TRUE
#undef SHUTTLE_CONSOLE_ACCESSDENIED
#undef SHUTTLE_CONSOLE_ENDGAME