From 46c3e9e54d64c773fbb1d7594414f8f5422995fd Mon Sep 17 00:00:00 2001 From: SmArtKar Date: Tue, 16 Jun 2020 18:16:14 +0300 Subject: [PATCH] Shuttles --- code/__DEFINES/shuttles.dm | 2 + .../configuration/entries/game_options.dm | 7 + code/game/area/areas/shuttles.dm | 4 + code/game/atoms.dm | 3 + code/game/machinery/computer/camera.dm | 5 + code/game/machinery/shuttle/custom_shuttle.dm | 33 ++ code/game/machinery/shuttle/shuttle_engine.dm | 138 +++++++ code/game/machinery/shuttle/shuttle_heater.dm | 132 +++++++ code/game/machinery/status_display.dm | 2 +- .../circuitboards/computer_circuitboards.dm | 8 + .../circuitboards/machine_circuitboards.dm | 25 ++ .../research/designs/electronics_designs.dm | 30 ++ code/modules/research/designs/misc_designs.dm | 32 +- code/modules/research/designs/tool_designs.dm | 10 + .../research/techweb/nodes/bluespace_nodes.dm | 33 ++ code/modules/shuttle/computer.dm | 2 +- code/modules/shuttle/custom_shuttle.dm | 268 +++++++++++++ code/modules/shuttle/navigation_computer.dm | 2 +- code/modules/shuttle/shuttle.dm | 169 +++++--- .../shuttle_creation/shuttle_creator.dm | 370 ++++++++++++++++++ .../shuttle_creator_actions.dm | 101 +++++ .../shuttle_creator_console.dm | 93 +++++ .../shuttle_creation/shuttle_creator_eye.dm | 54 +++ .../shuttle_creator_overlay.dm | 52 +++ .../shuttle_creation/shuttle_upgrades.dm | 39 ++ icons/mob/actions/actions_shuttle.dmi | Bin 0 -> 3034 bytes icons/obj/tools.dmi | Bin 28686 -> 29207 bytes tgstation.dme | 10 + 28 files changed, 1575 insertions(+), 49 deletions(-) create mode 100644 code/game/machinery/shuttle/custom_shuttle.dm create mode 100644 code/game/machinery/shuttle/shuttle_engine.dm create mode 100644 code/game/machinery/shuttle/shuttle_heater.dm create mode 100644 code/modules/shuttle/custom_shuttle.dm create mode 100644 code/modules/shuttle/shuttle_creation/shuttle_creator.dm create mode 100644 code/modules/shuttle/shuttle_creation/shuttle_creator_actions.dm create mode 100644 code/modules/shuttle/shuttle_creation/shuttle_creator_console.dm create mode 100644 code/modules/shuttle/shuttle_creation/shuttle_creator_eye.dm create mode 100644 code/modules/shuttle/shuttle_creation/shuttle_creator_overlay.dm create mode 100644 code/modules/shuttle/shuttle_creation/shuttle_upgrades.dm create mode 100644 icons/mob/actions/actions_shuttle.dmi diff --git a/code/__DEFINES/shuttles.dm b/code/__DEFINES/shuttles.dm index 36ecebb699..80983b34be 100644 --- a/code/__DEFINES/shuttles.dm +++ b/code/__DEFINES/shuttles.dm @@ -7,6 +7,8 @@ #define SHUTTLE_STRANDED "stranded" #define SHUTTLE_ESCAPE "escape" #define SHUTTLE_ENDGAME "endgame: game over" +#define SHUTTLE_RECHARGING "recharging" +#define SHUTTLE_PREARRIVAL "landing" #define EMERGENCY_IDLE_OR_RECALLED (SSshuttle.emergency && ((SSshuttle.emergency.mode == SHUTTLE_IDLE) || (SSshuttle.emergency.mode == SHUTTLE_RECALL))) #define EMERGENCY_ESCAPED_OR_ENDGAMED (SSshuttle.emergency && ((SSshuttle.emergency.mode == SHUTTLE_ESCAPE) || (SSshuttle.emergency.mode == SHUTTLE_ENDGAME))) diff --git a/code/controllers/configuration/entries/game_options.dm b/code/controllers/configuration/entries/game_options.dm index 7e7d939c39..435a35c05e 100644 --- a/code/controllers/configuration/entries/game_options.dm +++ b/code/controllers/configuration/entries/game_options.dm @@ -521,3 +521,10 @@ * Camera mobs, AIs, ghosts and some other are of course exempt from this. This also doesn't influence simplemob AI, for the best. */ /datum/config_entry/flag/use_field_of_vision + +//Shuttle size limiter +/datum/config_entry/number/max_shuttle_count + config_entry_value = 6 + +/datum/config_entry/number/max_shuttle_size + config_entry_value = 250 diff --git a/code/game/area/areas/shuttles.dm b/code/game/area/areas/shuttles.dm index cec0067e4b..b0d8a71cfc 100644 --- a/code/game/area/areas/shuttles.dm +++ b/code/game/area/areas/shuttles.dm @@ -103,6 +103,10 @@ /area/shuttle/custom name = "Custom player shuttle" +/area/shuttle/custom/powered + name = "Custom Powered player shuttle" + requires_power = FALSE + /area/shuttle/arrival name = "Arrival Shuttle" unique = TRUE // SSjob refers to this area for latejoiners diff --git a/code/game/atoms.dm b/code/game/atoms.dm index a83eaed9d3..e2ac541a51 100644 --- a/code/game/atoms.dm +++ b/code/game/atoms.dm @@ -846,6 +846,9 @@ /atom/proc/GenerateTag() return +/atom/proc/connect_to_shuttle(obj/docking_port/mobile/port, obj/docking_port/stationary/dock, idnum, override=FALSE) + return + // Generic logging helper /atom/proc/log_message(message, message_type, color=null, log_globally=TRUE) if(!log_globally) diff --git a/code/game/machinery/computer/camera.dm b/code/game/machinery/computer/camera.dm index 450967050c..2a166c2cdc 100644 --- a/code/game/machinery/computer/camera.dm +++ b/code/game/machinery/computer/camera.dm @@ -35,6 +35,11 @@ M.unset_machine() //to properly reset the view of the users if the console is deleted. return ..() +/obj/machinery/computer/security/connect_to_shuttle(obj/docking_port/mobile/port, obj/docking_port/stationary/dock, idnum, override=FALSE) + for(var/i in network) + network -= i + network += "[idnum][i]" + /obj/machinery/computer/security/can_interact(mob/user) if((!hasSiliconAccessInArea(user) && !Adjacent(user)) || is_blind(user) || !in_view_range(user, src)) return FALSE diff --git a/code/game/machinery/shuttle/custom_shuttle.dm b/code/game/machinery/shuttle/custom_shuttle.dm new file mode 100644 index 0000000000..7c83cade69 --- /dev/null +++ b/code/game/machinery/shuttle/custom_shuttle.dm @@ -0,0 +1,33 @@ +/obj/machinery/shuttle + name = "shuttle component" + desc = "Something for shuttles." + density = TRUE + obj_integrity = 250 + max_integrity = 250 + icon = 'icons/turf/shuttle.dmi' + icon_state = "burst_plasma" + idle_power_usage = 150 + circuit = /obj/item/circuitboard/machine/shuttle/engine + var/icon_state_closed = "burst_plasma" + var/icon_state_open = "burst_plasma_open" + var/icon_state_off = "burst_plasma_off" + +/obj/machinery/shuttle/Initialize() + . = ..() + GLOB.custom_shuttle_machines += src + +/obj/machinery/shuttle/Destroy() + . = ..() + GLOB.custom_shuttle_machines -= src + +/obj/machinery/shuttle/attackby(obj/item/I, mob/living/user, params) + if(default_deconstruction_screwdriver(user, icon_state_open, icon_state_closed, I)) + return + if(default_pry_open(I)) + return + if(panel_open) + if(default_change_direction_wrench(user, I)) + return + if(default_deconstruction_crowbar(I)) + return + return ..() diff --git a/code/game/machinery/shuttle/shuttle_engine.dm b/code/game/machinery/shuttle/shuttle_engine.dm new file mode 100644 index 0000000000..bdc16c565b --- /dev/null +++ b/code/game/machinery/shuttle/shuttle_engine.dm @@ -0,0 +1,138 @@ +//----------------------------------------------- +//-------------Engine Thrusters------------------ +//----------------------------------------------- + +#define ENGINE_HEAT_TARGET 600 +#define ENGINE_HEATING_POWER 5000000 + +/obj/machinery/shuttle/engine + name = "shuttle thruster" + desc = "A thruster for shuttles." + density = TRUE + obj_integrity = 250 + max_integrity = 250 + icon = 'icons/turf/shuttle.dmi' + icon_state = "burst_plasma" + idle_power_usage = 150 + circuit = /obj/item/circuitboard/machine/shuttle/engine + var/thrust = 0 + var/fuel_use = 0 + var/bluespace_capable = TRUE + var/cooldown = 0 + var/thruster_active = FALSE + var/datum/weakref/attached_heater + +/obj/machinery/shuttle/engine/plasma + name = "plasma thruster" + desc = "A thruster that burns plasma stored in an adjacent plasma thruster heater." + icon_state = "burst_plasma" + icon_state_off = "burst_plasma_off" + + idle_power_usage = 0 + circuit = /obj/item/circuitboard/machine/shuttle/engine/plasma + thrust = 25 + fuel_use = 0.24 + bluespace_capable = FALSE + cooldown = 45 + +/obj/machinery/shuttle/engine/void + name = "void thruster" + desc = "A thruster using technology to breach voidspace for propulsion." + icon_state = "burst_void" + icon_state_off = "burst_void" + icon_state_closed = "burst_void" + icon_state_open = "burst_void_open" + idle_power_usage = 0 + circuit = /obj/item/circuitboard/machine/shuttle/engine/void + thrust = 400 + fuel_use = 0 + bluespace_capable = TRUE + cooldown = 90 + +/obj/machinery/shuttle/engine/Initialize() + . = ..() + check_setup() + +/obj/machinery/shuttle/engine/on_construction() + . = ..() + check_setup() + +/obj/machinery/shuttle/engine/proc/check_setup() + var/heater_turf + switch(dir) + if(NORTH) + heater_turf = get_offset_target_turf(src, 0, 1) + if(SOUTH) + heater_turf = get_offset_target_turf(src, 0, -1) + if(EAST) + heater_turf = get_offset_target_turf(src, 1, 0) + if(WEST) + heater_turf = get_offset_target_turf(src, -1, 0) + if(!heater_turf) + attached_heater = null + update_engine() + return + attached_heater = null + for(var/obj/machinery/atmospherics/components/unary/shuttle/heater/as_heater in heater_turf) + if(as_heater.dir != dir) + continue + if(as_heater.panel_open) + continue + if(!as_heater.anchored) + continue + attached_heater = WEAKREF(as_heater) + break + update_engine() + return + +/obj/machinery/shuttle/engine/proc/update_engine() + if(!attached_heater) + icon_state = icon_state_off + thruster_active = FALSE + return + var/obj/machinery/atmospherics/components/unary/shuttle/heater/resolved_heater = attached_heater.resolve() + if(panel_open) + thruster_active = FALSE + else if(resolved_heater?.hasFuel(1)) + icon_state = icon_state_closed + thruster_active = TRUE + else + thruster_active = FALSE + icon_state = icon_state_off + return + +/obj/machinery/shuttle/engine/void/update_engine() + if(panel_open) + thruster_active = FALSE + return + thruster_active = TRUE + icon_state = icon_state_closed + return + +//Thanks to spaceheater.dm for inspiration :) +/obj/machinery/shuttle/engine/proc/fireEngine() + var/turf/heatTurf = loc + if(!heatTurf) + return + var/datum/gas_mixture/env = heatTurf.return_air() + var/heat_cap = env.heat_capacity() + var/req_power = abs(env.return_temperature() - ENGINE_HEAT_TARGET) * heat_cap + req_power = min(req_power, ENGINE_HEATING_POWER) + var/deltaTemperature = req_power / heat_cap + if(deltaTemperature < 0) + return + env.temperature += deltaTemperature + air_update_turf() + +/obj/machinery/shuttle/engine/attackby(obj/item/I, mob/living/user, params) + check_setup() + if(default_deconstruction_screwdriver(user, icon_state_open, icon_state_closed, I)) + return + if(default_pry_open(I)) + return + if(panel_open) + if(default_change_direction_wrench(user, I)) + return + if(default_deconstruction_crowbar(I)) + return + return ..() diff --git a/code/game/machinery/shuttle/shuttle_heater.dm b/code/game/machinery/shuttle/shuttle_heater.dm new file mode 100644 index 0000000000..3c36e53b5a --- /dev/null +++ b/code/game/machinery/shuttle/shuttle_heater.dm @@ -0,0 +1,132 @@ +//----------------------------------------------- +//--------------Engine Heaters------------------- +//This uses atmospherics, much like a thermomachine, +//but instead of changing temp, it stores plasma and uses +//it for the engine. +//----------------------------------------------- +/obj/machinery/atmospherics/components/unary/shuttle + name = "shuttle atmospherics device" + desc = "This does something to do with shuttle atmospherics" + icon_state = "heater" + icon = 'icons/turf/shuttle.dmi' + +/obj/machinery/atmospherics/components/unary/shuttle/heater + name = "engine heater" + desc = "Directs energy into compressed particles in order to power an attached thruster." + icon_state = "heater_pipe" + var/icon_state_closed = "heater_pipe" + var/icon_state_open = "heater_pipe_open" + var/icon_state_off = "heater_pipe" + idle_power_usage = 50 + circuit = /obj/item/circuitboard/machine/shuttle/heater + + density = TRUE + max_integrity = 400 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 100, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 30) + layer = OBJ_LAYER + showpipe = TRUE + + pipe_flags = PIPING_ONE_PER_TURF | PIPING_DEFAULT_LAYER_ONLY + + var/gas_type = /datum/gas/plasma + var/efficiency_multiplier = 1 + var/gas_capacity = 0 + +/obj/machinery/atmospherics/components/unary/shuttle/heater/New() + . = ..() + GLOB.custom_shuttle_machines += src + SetInitDirections() + update_adjacent_engines() + updateGasStats() + +/obj/machinery/atmospherics/components/unary/shuttle/heater/Destroy() + . = ..() + update_adjacent_engines() + GLOB.custom_shuttle_machines -= src + +/obj/machinery/atmospherics/components/unary/shuttle/heater/on_construction() + ..(dir, dir) + SetInitDirections() + update_adjacent_engines() + +/obj/machinery/atmospherics/components/unary/shuttle/heater/default_change_direction_wrench(mob/user, obj/item/I) + if(!..()) + return FALSE + SetInitDirections() + var/obj/machinery/atmospherics/node = nodes[1] + if(node) + node.disconnect(src) + nodes[1] = null + if(!parents[1]) + return + nullifyPipenet(parents[1]) + + atmosinit() + node = nodes[1] + if(node) + node.atmosinit() + node.addMember(src) + build_network() + return TRUE + +/obj/machinery/atmospherics/components/unary/shuttle/heater/RefreshParts() + var/cap = 0 + var/eff = 0 + for(var/obj/item/stock_parts/matter_bin/M in component_parts) + cap += M.rating + for(var/obj/item/stock_parts/micro_laser/L in component_parts) + eff += L.rating + gas_capacity = 5000 * ((cap - 1) ** 2) + 1000 + efficiency_multiplier = round(((eff / 2) / 2.8) ** 2, 0.1) + updateGasStats() + +/obj/machinery/atmospherics/components/unary/shuttle/heater/examine(mob/user) + . = ..() + var/datum/gas_mixture/air_contents = airs[1] + . += "The engine heater's gas dial reads [air_contents.return_volume()] liters in internal tank.
" + +/obj/machinery/atmospherics/components/unary/shuttle/heater/proc/updateGasStats() + var/datum/gas_mixture/air_contents = airs[1] + if(!air_contents) + return + air_contents.volume = gas_capacity + air_contents.temperature = T20C + +/obj/machinery/atmospherics/components/unary/shuttle/heater/proc/hasFuel(var/required) + var/datum/gas_mixture/air_contents = airs[1] + var/moles = air_contents.total_moles() + return moles >= required + +/obj/machinery/atmospherics/components/unary/shuttle/heater/proc/consumeFuel(var/amount) + var/datum/gas_mixture/air_contents = airs[1] + air_contents.remove(amount) + return + +/obj/machinery/atmospherics/components/unary/shuttle/heater/attackby(obj/item/I, mob/living/user, params) + update_adjacent_engines() + if(default_deconstruction_screwdriver(user, icon_state_open, icon_state_closed, I)) + return + if(default_pry_open(I)) + return + if(panel_open) + if(default_change_direction_wrench(user, I)) + return + if(default_deconstruction_crowbar(I)) + return + return ..() + +/obj/machinery/atmospherics/components/unary/shuttle/heater/proc/update_adjacent_engines() + var/engine_turf + switch(dir) + if(NORTH) + engine_turf = get_offset_target_turf(src, 0, -1) + if(SOUTH) + engine_turf = get_offset_target_turf(src, 0, 1) + if(EAST) + engine_turf = get_offset_target_turf(src, -1, 0) + if(WEST) + engine_turf = get_offset_target_turf(src, 1, 0) + if(!engine_turf) + return + for(var/obj/machinery/shuttle/engine/E in engine_turf) + E.check_setup() diff --git a/code/game/machinery/status_display.dm b/code/game/machinery/status_display.dm index 02bb07b8ed..3bb67e2a1c 100644 --- a/code/game/machinery/status_display.dm +++ b/code/game/machinery/status_display.dm @@ -290,7 +290,7 @@ if("shuttle_id") update() -/obj/machinery/status_display/shuttle/proc/connect_to_shuttle(obj/docking_port/mobile/port, obj/docking_port/stationary/dock, idnum, override) +/obj/machinery/status_display/shuttle/connect_to_shuttle(obj/docking_port/mobile/port, obj/docking_port/stationary/dock, idnum, override) if (port && (shuttle_id == initial(shuttle_id) || override)) shuttle_id = port.id update() diff --git a/code/game/objects/items/circuitboards/computer_circuitboards.dm b/code/game/objects/items/circuitboards/computer_circuitboards.dm index 1b378dcfb1..ae579054b1 100644 --- a/code/game/objects/items/circuitboards/computer_circuitboards.dm +++ b/code/game/objects/items/circuitboards/computer_circuitboards.dm @@ -379,3 +379,11 @@ /obj/item/circuitboard/computer/nanite_cloud_controller name = "Nanite Cloud Control (Computer Board)" build_path = /obj/machinery/computer/nanite_cloud_controller + +/obj/item/circuitboard/computer/shuttle/flight_control + name = "Shuttle Flight Control (Computer Board)" + build_path = /obj/machinery/computer/custom_shuttle + +/obj/item/circuitboard/computer/shuttle/docker + name = "Shuttle Navigation Computer (Computer Board)" + build_path = /obj/machinery/computer/camera_advanced/shuttle_docker/custom diff --git a/code/game/objects/items/circuitboards/machine_circuitboards.dm b/code/game/objects/items/circuitboards/machine_circuitboards.dm index b90fd1c1af..ea839ae12d 100644 --- a/code/game/objects/items/circuitboards/machine_circuitboards.dm +++ b/code/game/objects/items/circuitboards/machine_circuitboards.dm @@ -1102,3 +1102,28 @@ /obj/item/stock_parts/micro_laser = 2, /obj/item/stock_parts/scanning_module = 2 ) + +/obj/item/circuitboard/machine/shuttle/engine + name = "Thruster (Machine Board)" + build_path = /obj/machinery/shuttle/engine + req_components = list() + +/obj/item/circuitboard/machine/shuttle/engine/plasma + name = "Plasma Thruster (Machine Board)" + build_path = /obj/machinery/shuttle/engine/plasma + req_components = list(/obj/item/stock_parts/capacitor = 2, + /obj/item/stack/cable_coil = 5, + /obj/item/stock_parts/micro_laser = 1) + +/obj/item/circuitboard/machine/shuttle/engine/void + name = "Void Thruster (Machine Board)" + build_path = /obj/machinery/shuttle/engine/void + req_components = list(/obj/item/stock_parts/capacitor/quadratic = 2, + /obj/item/stack/cable_coil = 5, + /obj/item/stock_parts/micro_laser/quadultra = 1) + +/obj/item/circuitboard/machine/shuttle/heater + name = "Electronic Engine Heater (Machine Board)" + build_path = /obj/machinery/atmospherics/components/unary/shuttle/heater + req_components = list(/obj/item/stock_parts/micro_laser = 2, + /obj/item/stock_parts/matter_bin = 1) diff --git a/code/modules/research/designs/electronics_designs.dm b/code/modules/research/designs/electronics_designs.dm index 4e9238c4c7..806e2ef75c 100644 --- a/code/modules/research/designs/electronics_designs.dm +++ b/code/modules/research/designs/electronics_designs.dm @@ -167,4 +167,34 @@ id = "xenobio_slimeadv" build_path = /obj/item/disk/xenobio_console_upgrade/slimeadv +/datum/design/board/shuttle/engine/plasma + name = "Machine Design (Plasma Thruster Board)" + desc = "The circuit board for a plasma thruster." + id = "engine_plasma" + build_path = /obj/item/circuitboard/machine/shuttle/engine/plasma + category = list ("Shuttle Machinery") + departmental_flags = DEPARTMENTAL_FLAG_ENGINEERING | DEPARTMENTAL_FLAG_SCIENCE +/datum/design/board/shuttle/engine/void + name = "Machine Design (Void Thruster Board)" + desc = "The circuit board for a void thruster." + id = "engine_void" + build_path = /obj/item/circuitboard/machine/shuttle/engine/void + category = list ("Shuttle Machinery") + departmental_flags = DEPARTMENTAL_FLAG_ENGINEERING | DEPARTMENTAL_FLAG_SCIENCE + +/datum/design/board/shuttle/engine/heater + name = "Machine Design (Engine Heater Board)" + desc = "The circuit board for an engine heater." + id = "engine_heater" + build_path = /obj/item/circuitboard/machine/shuttle/heater + category = list ("Shuttle Machinery") + departmental_flags = DEPARTMENTAL_FLAG_ENGINEERING | DEPARTMENTAL_FLAG_SCIENCE + +/obj/item/circuitboard/computer/shuttle/flight_control + name = "Shuttle Flight Control (Computer Board)" + build_path = /obj/machinery/computer/custom_shuttle + +/obj/item/circuitboard/computer/shuttle/docker + name = "Shuttle Navigation Computer (Computer Board)" + build_path = /obj/machinery/computer/camera_advanced/shuttle_docker/custom diff --git a/code/modules/research/designs/misc_designs.dm b/code/modules/research/designs/misc_designs.dm index 9b44264b07..c59ee3ec61 100644 --- a/code/modules/research/designs/misc_designs.dm +++ b/code/modules/research/designs/misc_designs.dm @@ -760,4 +760,34 @@ materials = list(/datum/material/iron = 1000) build_path = /obj/item/tank/internals/emergency_oxygen/engi/empty category = list("Equipment") - departmental_flags = DEPARTMENTAL_FLAG_ENGINEERING \ No newline at end of file + departmental_flags = DEPARTMENTAL_FLAG_ENGINEERING + +/datum/design/shuttle_speed_upgrade + name = "Shuttle Route Optimisation Upgrade" + desc = "A disk that allows for calculating shorter routes when inserted into a flight control console." + id = "disk_shuttle_route" + build_type = PROTOLATHE + materials = list(/datum/material/iron = 1000, /datum/material/glass = 1000) + build_path = /obj/item/shuttle_route_optimisation + category = list("Equipment") + departmental_flags = DEPARTMENTAL_FLAG_SCIENCE | DEPARTMENTAL_FLAG_ENGINEERING + +/datum/design/shuttle_speed_upgrade_hyper + name = "Shuttle Bluespace Hyperlane Optimisation Upgrade" + desc = "A disk that allows for calculating shorter routes when inserted into a flight control console. This one abuses bluespace hyperlanes for increased efficiency." + id = "disk_shuttle_route_hyper" + build_type = PROTOLATHE + materials = list(/datum/material/iron = 1000, /datum/material/glass = 1000) + build_path = /obj/item/shuttle_route_optimisation/hyperlane + category = list("Equipment") + departmental_flags = DEPARTMENTAL_FLAG_SCIENCE | DEPARTMENTAL_FLAG_ENGINEERING + +/datum/design/shuttle_speed_upgrade_void + name = "Shuttle Voidspace Optimisation Upgrade" + desc = "A disk that allows for calculating shorter routes when inserted into a flight control console. This one access voidspace for increased efficiency." + id = "disk_shuttle_route_void" + build_type = PROTOLATHE + materials = list(/datum/material/iron = 1000, /datum/material/glass = 1000) + build_path = /obj/item/shuttle_route_optimisation/void + category = list("Equipment") + departmental_flags = DEPARTMENTAL_FLAG_SCIENCE | DEPARTMENTAL_FLAG_ENGINEERING \ No newline at end of file diff --git a/code/modules/research/designs/tool_designs.dm b/code/modules/research/designs/tool_designs.dm index e500166e76..551d6fa0e3 100644 --- a/code/modules/research/designs/tool_designs.dm +++ b/code/modules/research/designs/tool_designs.dm @@ -42,6 +42,16 @@ category = list("Tool Designs") departmental_flags = DEPARTMENTAL_FLAG_ENGINEERING +/datum/design/shuttlecreator + name = "Rapid Shuttle Designator" + desc = "An advanced device capable of defining areas for use in the creation of shuttles" + id = "shuttle_creator" + build_path = /obj/item/shuttle_creator + build_type = PROTOLATHE + materials = list(/datum/material/iron = 8000, /datum/material/titanium = 5000, /datum/material/bluespace = 5000) + category = list("Tool Designs") + departmental_flags = DEPARTMENTAL_FLAG_SCIENCE | DEPARTMENTAL_FLAG_ENGINEERING + /datum/design/handdrill name = "Hand Drill" desc = "A small electric hand drill with an interchangeable screwdriver and bolt bit" diff --git a/code/modules/research/techweb/nodes/bluespace_nodes.dm b/code/modules/research/techweb/nodes/bluespace_nodes.dm index 85142cd9b8..09dd4cdd06 100644 --- a/code/modules/research/techweb/nodes/bluespace_nodes.dm +++ b/code/modules/research/techweb/nodes/bluespace_nodes.dm @@ -63,3 +63,36 @@ prereq_ids = list("bluespace_warping", "syndicate_basic") design_ids = list("desynchronizer") research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500) + +/////////////////////////shuttle tech///////////////////////// +/datum/techweb_node/basic_shuttle_tech + id = "basic_shuttle" + display_name = "Basic Shuttle Research" + description = "Research the technology required to create and use basic shuttles." + prereq_ids = list("bluespace_travel", "adv_engi") + design_ids = list("shuttle_creator", "engine_plasma", "engine_heater", "shuttle_control", "shuttle_docker") + research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 5000) + +/datum/techweb_node/shuttle_route_upgrade + id = "shuttle_route_upgrade" + display_name = "Route Optimisation Upgrade" + description = "Research into bluespace tunnelling, allowing us to reduce flight times by up to 20%!" + prereq_ids = list("basic_shuttle") + design_ids = list("disk_shuttle_route") + research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500) + +/datum/techweb_node/shuttle_route_upgrade_hyper + id = "shuttle_route_upgrade_hyper" + display_name = "Hyperlane Optimisation Upgrade" + description = "Research into bluespace hyperlane, allowing us to reduce flight times by up to 40%!" + prereq_ids = list("shuttle_route_upgrade", "micro_bluespace") + design_ids = list("disk_shuttle_route_hyper") + research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 5000) + +/datum/techweb_node/shuttle_route_upgrade_void + id = "shuttle_route_upgrade_void" + display_name = "Nullspace Breaching Upgrade" + description = "Research into voidspace tunnelling, allowing us to significantly reduce flight times." + prereq_ids = list("shuttle_route_upgrade_hyper", "alientech") + design_ids = list("disk_shuttle_route_void", "engine_void") + research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 12500) diff --git a/code/modules/shuttle/computer.dm b/code/modules/shuttle/computer.dm index d2b13fc71a..75bf55a5a3 100644 --- a/code/modules/shuttle/computer.dm +++ b/code/modules/shuttle/computer.dm @@ -71,6 +71,6 @@ to_chat(user, "You fried the consoles ID checking system.") return TRUE -/obj/machinery/computer/shuttle/proc/connect_to_shuttle(obj/docking_port/mobile/port, obj/docking_port/stationary/dock, idnum, override=FALSE) +/obj/machinery/computer/shuttle/connect_to_shuttle(obj/docking_port/mobile/port, obj/docking_port/stationary/dock, idnum, override=FALSE) if(port && (shuttleId == initial(shuttleId) || override)) shuttleId = port.id \ No newline at end of file diff --git a/code/modules/shuttle/custom_shuttle.dm b/code/modules/shuttle/custom_shuttle.dm new file mode 100644 index 0000000000..6e06e3fefe --- /dev/null +++ b/code/modules/shuttle/custom_shuttle.dm @@ -0,0 +1,268 @@ +#define Z_DIST 500 +#define CUSTOM_ENGINES_START_TIME 65 +#define CALCULATE_STATS_COOLDOWN 2 + +/obj/machinery/computer/custom_shuttle + name = "nanotrasen shuttle flight controller" + desc = "A terminal used to fly shuttles defined by the Shuttle Zoning Designator" + circuit = /obj/item/circuitboard/computer/shuttle/flight_control + icon_screen = "shuttle" + icon_keyboard = "tech_key" + light_color = LIGHT_COLOR_CYAN + req_access = list( ) + var/shuttleId + var/possible_destinations = "whiteship_home" + var/admin_controlled + var/no_destination_swap = 0 + var/calculated_mass = 0 + var/calculated_dforce = 0 + var/calculated_speed = 0 + var/calculated_engine_count = 0 + var/calculated_consumption = 0 + var/calculated_cooldown = 0 + var/calculated_non_operational_thrusters = 0 + var/calculated_fuel_less_thrusters = 0 + var/target_fuel_cost = 0 + var/targetLocation + var/datum/browser/popup + + var/stat_calc_cooldown = 0 + + //Upgrades + var/distance_multiplier = 1 + +/obj/machinery/computer/custom_shuttle/examine(mob/user) + . = ..() + . += distance_multiplier < 1 ? "Bluespace shortcut module installed. Route is [distance_multiplier]x the original length." : "" + +/obj/machinery/computer/custom_shuttle/ui_interact(mob/user) + var/list/options = params2list(possible_destinations) + var/obj/docking_port/mobile/M = SSshuttle.getShuttle(shuttleId) + var/dat = "[M ? "Current Location : [M.getStatusText()]" : "Shuttle link required."]

" + if(M) + dat += "Run Flight Calculations
" + dat += "Shuttle Data
" + dat += "Shuttle Mass: [calculated_mass/10]tons
" + dat += "Engine Force: [calculated_dforce]kN ([calculated_engine_count] engines)
" + dat += "Sublight Speed: [calculated_speed]ms-1
" + dat += calculated_speed < 1 ? "INSUFFICIENT ENGINE POWER
" : "" + dat += calculated_non_operational_thrusters > 0 ? "Warning: [calculated_non_operational_thrusters] thrusters offline.
" : "" + dat += "Fuel Consumption: [calculated_consumption]units per distance
" + dat += "Engine Cooldown: [calculated_cooldown]s
" + var/destination_found + for(var/obj/docking_port/stationary/S in SSshuttle.stationary) + if(!options.Find(S.id)) + continue + if(!M.check_dock(S, silent=TRUE)) + continue + if(calculated_speed == 0) + break + destination_found = TRUE + var/dist = round(calculateDistance(S)) + dat += "Target [S.name] (Dist: [dist] | Fuel Cost: [round(dist * calculated_consumption)] | Time: [round(dist / calculated_speed)])
" + if(!destination_found) + dat += "No valid destinations
" + dat += "
[targetLocation ? "Target Location : [targetLocation]" : "No Target Location"]" + dat += "
Initate Flight
" + dat += "Close" + + popup = new(user, "computer", M ? M.name : "shuttle", 350, 450) + popup.set_content("
[dat]
") + popup.set_title_image(usr.browse_rsc_icon(src.icon, src.icon_state)) + popup.open() + +/obj/machinery/computer/custom_shuttle/Topic(href, href_list) + if(..()) + return + usr.set_machine(src) + src.add_fingerprint(usr) + if(!allowed(usr)) + to_chat(usr, "Access denied.") + return + + if(href_list["calculate"]) + calculateStats() + ui_interact(usr) + return + var/obj/docking_port/mobile/M = SSshuttle.getShuttle(shuttleId) + if(!M) + return + if(M.launch_status == ENDGAME_LAUNCHED) + return + if(href_list["setloc"]) + SetTargetLocation(href_list["setloc"]) + ui_interact(usr) + return + else if(href_list["fly"]) + Fly() + ui_interact(usr) + return + +/obj/machinery/computer/custom_shuttle/proc/calculateDistance(var/obj/docking_port/stationary/port) + var/deltaX = port.x - x + var/deltaY = port.y - y + var/deltaZ = (port.z - z) * Z_DIST + return sqrt(deltaX * deltaX + deltaY * deltaY + deltaZ * deltaZ) * distance_multiplier + +/obj/machinery/computer/custom_shuttle/proc/linkShuttle(var/new_id) + shuttleId = new_id + possible_destinations = "whiteship_home;shuttle[new_id]_custom" + +/obj/machinery/computer/custom_shuttle/proc/calculateStats(var/useFuel = FALSE, var/dist = 0, var/ignore_cooldown = FALSE) + if(!ignore_cooldown && stat_calc_cooldown >= world.time) + to_chat(usr, "You are using this too fast, please slow down") + return + stat_calc_cooldown = world.time + CALCULATE_STATS_COOLDOWN + var/obj/docking_port/mobile/M = SSshuttle.getShuttle(shuttleId) + if(!M) + return FALSE + //Reset data + calculated_mass = 0 + calculated_dforce = 0 + calculated_speed = 0 + calculated_engine_count = 0 + calculated_consumption = 0 + calculated_cooldown = 0 + calculated_fuel_less_thrusters = 0 + calculated_non_operational_thrusters = 0 + //Calculate all the data + var/list/areas = M.shuttle_areas + for(var/shuttleArea in areas) + calculated_mass += length(get_area_turfs(shuttleArea)) + for(var/obj/machinery/shuttle/engine/E in shuttleArea) + E.check_setup() + if(!E.thruster_active) //Skipover thrusters with no valid heater + calculated_non_operational_thrusters ++ + continue + if(E.attached_heater) + var/obj/machinery/atmospherics/components/unary/shuttle/heater/resolvedHeater = E.attached_heater.resolve() + if(resolvedHeater && !resolvedHeater.hasFuel(dist * E.fuel_use) && useFuel) + calculated_fuel_less_thrusters ++ + continue + calculated_engine_count++ + calculated_dforce += E.thrust + calculated_consumption += E.fuel_use + calculated_cooldown = max(calculated_cooldown, E.cooldown) + //This should really be accelleration, but its a 2d spessman game so who cares + if(calculated_mass == 0) + return FALSE + calculated_speed = (calculated_dforce*1000) / (calculated_mass*100) + return TRUE + +/obj/machinery/computer/custom_shuttle/proc/consumeFuel(var/dist) + var/obj/docking_port/mobile/M = SSshuttle.getShuttle(shuttleId) + if(!M) + return FALSE + //Calculate all the data + for(var/obj/machinery/shuttle/engine/shuttle_machine in GLOB.custom_shuttle_machines) + shuttle_machine.check_setup() + if(!shuttle_machine.thruster_active) + continue + if(get_area(M) != get_area(shuttle_machine)) + continue + if(shuttle_machine.attached_heater) + var/obj/machinery/atmospherics/components/unary/shuttle/heater/resolvedHeater = shuttle_machine.attached_heater.resolve() + if(resolvedHeater && !resolvedHeater.hasFuel(dist * shuttle_machine.fuel_use)) + continue + resolvedHeater?.consumeFuel(dist * shuttle_machine.fuel_use) + shuttle_machine.fireEngine() + +/obj/machinery/computer/custom_shuttle/proc/SetTargetLocation(var/newTarget) + if(!(newTarget in params2list(possible_destinations))) + log_admin("[usr] attempted to href dock exploit on [src] with target location \"[newTarget]\"") + message_admins("[usr] just attempted to href dock exploit on [src] with target location \"[newTarget]\"") + return + targetLocation = newTarget + say("Shuttle route calculated.") + return + +/obj/machinery/computer/custom_shuttle/proc/Fly() + if(!targetLocation) + return + var/obj/docking_port/mobile/linkedShuttle = SSshuttle.getShuttle(shuttleId) + if(!linkedShuttle) + return + if(linkedShuttle.mode != SHUTTLE_IDLE) + return + if(!calculateStats(TRUE, 0, TRUE)) + return + if(calculated_fuel_less_thrusters > 0) + say("Warning, [calculated_fuel_less_thrusters] do not have enough fuel for this journey, engine output may be limitted.") + if(calculated_speed < 1) + say("Insufficient engine power, shuttle requires [calculated_mass / 10]kN of thrust.") + return + var/obj/docking_port/stationary/targetPort = SSshuttle.getDock(targetLocation) + if(!targetPort) + return + var/dist = calculateDistance(targetPort) + var/time = min(max(round(dist / calculated_speed), 10), 90) + linkedShuttle.callTime = time * 10 + linkedShuttle.rechargeTime = calculated_cooldown + //We need to find the direction of this console to the port + linkedShuttle.port_direction = angle2dir(dir2angle(dir) - (dir2angle(linkedShuttle.dir)) + 180) + linkedShuttle.preferred_direction = NORTH + linkedShuttle.ignitionTime = CUSTOM_ENGINES_START_TIME + linkedShuttle.count_engines() + linkedShuttle.hyperspace_sound(HYPERSPACE_WARMUP) + var/throwForce = clamp((calculated_speed / 2) - 5, 0, 10) + linkedShuttle.movement_force = list("KNOCKDOWN" = calculated_speed > 5 ? 3 : 0, "THROW" = throwForce) + if(!(targetLocation in params2list(possible_destinations))) + log_admin("[usr] attempted to launch a shuttle that has been affected by href dock exploit on [src] with target location \"[targetLocation]\"") + message_admins("[usr] attempted to launch a shuttle that has been affected by href dock exploit on [src] with target location \"[targetLocation]\"") + return + switch(SSshuttle.moveShuttle(shuttleId, targetLocation, 1)) + if(0) + consumeFuel(dist) + say("Shuttle departing. Please stand away from the doors.") + if(1) + to_chat(usr, "Invalid shuttle requested.") + else + to_chat(usr, "Unable to comply.") + return + +/obj/machinery/computer/custom_shuttle/connect_to_shuttle(obj/docking_port/mobile/port, obj/docking_port/stationary/dock, idnum, override=FALSE) + if(port && (shuttleId == initial(shuttleId) || override)) + linkShuttle(port.id) + +//Custom shuttle docker locations +/obj/machinery/computer/camera_advanced/shuttle_docker/custom + name = "Shuttle Navigation Computer" + desc = "Used to designate a precise transit location for private ships." + lock_override = NONE + whitelist_turfs = list(/turf/open/space, + /turf/open/lava, + /turf/open/floor/plating/beach, + /turf/open/floor/plating/ashplanet, + /turf/open/floor/plating/asteroid, + /turf/open/floor/plating/lavaland_baseturf) + jumpto_ports = list("whiteship_home" = 1) + view_range = 12 + designate_time = 100 + circuit = /obj/item/circuitboard/computer/shuttle/docker + +/obj/machinery/computer/camera_advanced/shuttle_docker/custom/Initialize() + . = ..() + 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/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, "You cannot target locations while in transit.") + return + ..() + +/obj/machinery/computer/camera_advanced/shuttle_docker/custom/attack_hand(mob/user) + if(!shuttleId) + to_chat(user, "You must link the console to a shuttle first.") + return + return ..() + +/obj/machinery/computer/camera_advanced/shuttle_docker/custom/proc/linkShuttle(var/new_id) + shuttleId = new_id + shuttlePortId = "shuttle[new_id]_custom" diff --git a/code/modules/shuttle/navigation_computer.dm b/code/modules/shuttle/navigation_computer.dm index 69eb4a6be4..4f53e5d9d0 100644 --- a/code/modules/shuttle/navigation_computer.dm +++ b/code/modules/shuttle/navigation_computer.dm @@ -253,7 +253,7 @@ current_user.client.images -= remove_images current_user.client.images += add_images -/obj/machinery/computer/camera_advanced/shuttle_docker/proc/connect_to_shuttle(obj/docking_port/mobile/port, obj/docking_port/stationary/dock, idnum, override=FALSE) +/obj/machinery/computer/camera_advanced/shuttle_docker/connect_to_shuttle(obj/docking_port/mobile/port, obj/docking_port/stationary/dock, idnum, override=FALSE) if(port && (shuttleId == initial(shuttleId) || override)) shuttleId = port.id shuttlePortId = "[port.id]_custom" diff --git a/code/modules/shuttle/shuttle.dm b/code/modules/shuttle/shuttle.dm index 665361af49..77fb37b65c 100644 --- a/code/modules/shuttle/shuttle.dm +++ b/code/modules/shuttle/shuttle.dm @@ -11,17 +11,31 @@ resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF anchored = TRUE -// + /// 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/id - // this should point -away- from the dockingport door, ie towards the ship + ///Common standard is for this to point -away- from the dockingport door, ie towards the ship dir = NORTH - var/width = 0 //size of covered area, perpendicular to dir - var/height = 0 //size of covered area, parallel to dir - var/dwidth = 0 //position relative to covered area, perpendicular to dir - var/dheight = 0 //position relative to covered area, parallel to dir + ///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 - var/hidden = FALSE //are we invisible to shuttle navigation computers? + ///are we invisible to shuttle navigation computers? + var/hidden = FALSE + + ///Delete this port after ship fly off. + var/delete_after = FALSE + +/obj/docking_port/proc/get_save_vars() + return list("pixel_x", "pixel_y", "dir", "name", "req_access", "req_access_txt", "piping_layer", "color", "icon_state", "pipe_color", "amount", "width", "height", "dwidth", "dheight") //these objects are indestructible /obj/docking_port/Destroy(force) @@ -155,8 +169,6 @@ /obj/docking_port/stationary name = "dock" - area_type = SHUTTLE_DEFAULT_UNDERLYING_AREA - var/last_dock_time var/datum/map_template/shuttle/roundstart_template @@ -169,6 +181,9 @@ id = "[SSshuttle.stationary.len]" if(name == "dock") name = "dock[SSshuttle.stationary.len]" + 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()) @@ -183,6 +198,13 @@ SSshuttle.stationary -= src . = ..() +/obj/docking_port/stationary/Moved(atom/oldloc, dir, forced) + . = ..() + 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] @@ -196,9 +218,6 @@ if(!roundstart_template) CRASH("Invalid path ([roundstart_template]) passed to docking port.") - if(roundstart_template) - SSshuttle.manipulator.action_load(roundstart_template, src) - //returns first-found touching shuttleport /obj/docking_port/stationary/get_docked() . = locate(/obj/docking_port/mobile) in loc @@ -235,16 +254,25 @@ var/list/shuttle_areas - var/timer //used as a timer (if you want time left to complete move, use timeLeft proc) + ///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 - var/mode = SHUTTLE_IDLE //current shuttle mode - var/callTime = 100 //time spent in transit (deciseconds). Should not be lower then 10 seconds without editing the animation of the hyperspace ripples. - var/ignitionTime = 55 // time spent "starting the engines". Also rate limits how often we try to reserve transit space if its ever full of transiting shuttles. - - // The direction the shuttle prefers to travel in + /// 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 - // And the angle from the front of the shuttle to the port + /// 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 @@ -254,13 +282,16 @@ var/launch_status = NOLAUNCH - var/list/movement_force = list("KNOCKDOWN" = 3, "THROW" = 2) + ///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) var/list/ripples = list() - var/engine_coeff = 1 //current engine coeff - var/current_engines = 0 //current engine power - var/initial_engines = 0 //initial engine power - var/can_move_docking_ports = FALSE //if this shuttle can move docking ports other than the one it is docked at + var/engine_coeff = 1 + var/current_engines = 0 + var/initial_engines = 0 + var/list/engine_list = list() + ///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() /obj/docking_port/mobile/proc/register() @@ -308,14 +339,12 @@ id = "[id][idnum]" if(name == initial(name)) name = "[name] [idnum]" - for(var/i in shuttle_areas) - var/area/place = i - for(var/obj/machinery/computer/shuttle/comp in place) - comp.connect_to_shuttle(src, dock, idnum) - for(var/obj/machinery/computer/camera_advanced/shuttle_docker/comp in place) - comp.connect_to_shuttle(src, dock, idnum) - for(var/obj/machinery/status_display/shuttle/sd in place) - sd.connect_to_shuttle(src, dock, idnum) + for(var/place in shuttle_areas) + var/area/area = place + area.connect_to_shuttle(src, dock, idnum, FALSE) + for(var/each in place) + var/atom/atom = each + atom.connect_to_shuttle(src, dock, idnum, FALSE) //this is a hook for custom behaviour. Maybe at some point we could add checks to see if engines are intact @@ -423,7 +452,10 @@ if(initiate_docking(S1) != DOCKING_SUCCESS) WARNING("shuttle \"[id]\" could not enter transit space. Docked at [S0 ? S0.id : "null"]. Transit dock [S1 ? S1.id : "null"].") else - previous = S0 + if(S0.delete_after) + qdel(S0, TRUE) + else + previous = S0 else WARNING("shuttle \"[id]\" could not enter transit space. S0=[S0 ? S0.id : "null"] S1=[S1 ? S1.id : "null"]") @@ -536,7 +568,11 @@ // 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) + 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)])" @@ -547,6 +583,10 @@ else if(error) setTimer(20) return + if(rechargeTime) + mode = SHUTTLE_RECHARGING + setTimer(rechargeTime) + return if(SHUTTLE_RECALL) if(initiate_docking(previous) != DOCKING_SUCCESS) setTimer(20) @@ -649,6 +689,10 @@ return "ESC" if(SHUTTLE_STRANDED) return "ERR" + if(SHUTTLE_RECHARGING) + return "RCH" + if(SHUTTLE_PREARRIVAL) + return "LDN" return "" // returns 5-letter timer string, used by status screens and mob status panel @@ -660,14 +704,14 @@ 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")]" + return "[add_leading(num2text((timeleft / 60) % 60), 2, "0")]:[add_leading(num2text(timeleft % 60), 2, " ")]" else return "00:00" /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" @@ -678,8 +722,10 @@ else dst = destination . = "transit towards [dst?.name || "unknown location"] ([getTimerStr()])" + else if(mode == SHUTTLE_RECHARGING) + return "[docked_at], recharging [getTimerStr()]" else - return dockedAt?.name || "unknown" + return docked_at /obj/docking_port/mobile/proc/getDbgStatusText() @@ -711,19 +757,47 @@ return null /obj/docking_port/mobile/proc/hyperspace_sound(phase, list/areas) - var/s + var/selected_sound switch(phase) if(HYPERSPACE_WARMUP) - s = 'sound/effects/hyperspace_begin.ogg' + selected_sound = "hyperspace_begin" if(HYPERSPACE_LAUNCH) - s = 'sound/effects/hyperspace_progress.ogg' + selected_sound = "hyperspace_progress" if(HYPERSPACE_END) - s = 'sound/effects/hyperspace_end.ogg' + selected_sound = "hyperspace_end" else CRASH("Invalid hyperspace sound phase: [phase]") - for(var/A in areas) - for(var/obj/machinery/door/E in A) //dumb, I know, but playing it on the engines doesn't do it justice - playsound(E, s, 100, FALSE, max(width, height) - world.view) + // 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(LAZYLEN(engine_list)) + distant_source = engine_list[1] + else + for(var/A in areas) + distant_source = locate(/obj/machinery/door) in A + if(distant_source) + break + + if(distant_source) + for(var/mob/M in SSmobs.clients_by_zlevel[z]) + var/dist_far = get_dist(M, distant_source) + if(dist_far <= long_range && dist_far > range) + M.playsound_local(distant_source, "sound/effects/[selected_sound]_distance.ogg", 100, falloff = 20) + else if(dist_far <= range) + var/source + if(engine_list.len == 0) + source = distant_source + else + var/closest_dist = 10000 + for(var/obj/O in engine_list) + var/dist_near = get_dist(M, O) + if(dist_near < closest_dist) + source = O + closest_dist = dist_near + M.playsound_local(source, "sound/effects/[selected_sound].ogg", 100, falloff = range / 2) // Losing all initial engines should get you 2 // Adding another set of engines at 0.5 time @@ -743,7 +817,12 @@ var/area/shuttle/areaInstance = thing for(var/obj/structure/shuttle/engine/E in areaInstance.contents) if(!QDELETED(E)) + engine_list += E . += E.engine_power + for(var/obj/machinery/shuttle/engine/E in areaInstance.contents) + if(!QDELETED(E)) + engine_list += E + . += E.thruster_active ? 1 : 0 // Double initial engines to get to 0.5 minimum // Lose all initial engines to get to 2 @@ -768,7 +847,7 @@ /obj/docking_port/mobile/proc/in_flight() switch(mode) - if(SHUTTLE_CALL,SHUTTLE_RECALL) + if(SHUTTLE_CALL,SHUTTLE_RECALL,SHUTTLE_PREARRIVAL) return TRUE if(SHUTTLE_IDLE,SHUTTLE_IGNITING) return FALSE diff --git a/code/modules/shuttle/shuttle_creation/shuttle_creator.dm b/code/modules/shuttle/shuttle_creation/shuttle_creator.dm new file mode 100644 index 0000000000..f5a11db60f --- /dev/null +++ b/code/modules/shuttle/shuttle_creation/shuttle_creator.dm @@ -0,0 +1,370 @@ +#define SHUTTLE_CREATOR_MAX_SIZE CONFIG_GET(number/max_shuttle_size) +#define CUSTOM_SHUTTLE_LIMIT CONFIG_GET(number/max_shuttle_count) +#define CARDINAL_DIRECTIONS_X list(1, 0, -1, 0) +#define CARDINAL_DIRECTIONS_Y list(0, 1, 0, -1) + +GLOBAL_VAR_INIT(custom_shuttle_count, 0) //The amount of custom shuttles created to prevent creating hundreds +GLOBAL_LIST_EMPTY(custom_shuttle_machines) //Machines that require updating (Heaters, engines) + +//============ Shuttle Creator Object ============ +/obj/item/shuttle_creator + name = "Rapid Shuttle Designator" + icon = 'icons/obj/tools.dmi' + icon_state = "rsd" + lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi' + desc = "A device used to define the area required for custom ships. Uses bluespace crystals to create bluespace-capable ships." + density = FALSE + anchored = FALSE + flags_1 = CONDUCT_1 + item_flags = NOBLUDGEON + force = 0 + throwforce = 8 + throw_speed = 3 + throw_range = 5 + w_class = WEIGHT_CLASS_NORMAL + req_access_txt = "11" + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 50) + resistance_flags = FIRE_PROOF + var/ready = TRUE + //pre-designation + var/override_max_shuttles = FALSE + var/obj/machinery/computer/camera_advanced/shuttle_creator/internal_shuttle_creator + //During designation + var/overwritten_area = /area/space + var/list/loggedTurfs = list() + var/loggedOldArea + var/recorded_shuttle_area + var/datum/shuttle_creator_overlay_holder/overlay_holder + //After designation + var/linkedShuttleId + +/obj/item/shuttle_creator/Initialize() + . = ..() + internal_shuttle_creator = new() + internal_shuttle_creator.owner_rsd = src + overlay_holder = new() + +/obj/item/shuttle_creator/Destroy() + . = ..() + if(internal_shuttle_creator) + internal_shuttle_creator.owner_rsd = null + QDEL_NULL(internal_shuttle_creator) + if(overlay_holder) + QDEL_NULL(overlay_holder) + +/obj/item/shuttle_creator/attack_self(mob/user) + ..() + if(linkedShuttleId) + return + if(GLOB.custom_shuttle_count > CUSTOM_SHUTTLE_LIMIT && !override_max_shuttles) + to_chat(user, "Too many shuttles have been created.") + message_admins("[ADMIN_FLW(user)] attempted to create a shuttle, however [CUSTOM_SHUTTLE_LIMIT] have already been created.") + return + if(!internal_shuttle_creator) + return + overlay_holder.add_client(user.client) + internal_shuttle_creator.attack_hand(user) + +/obj/item/shuttle_creator/afterattack(atom/target, mob/user, proximity_flag) + . = ..() + if(!ready) + to_chat(user, "You need to define a shuttle area first.") + return + if(!proximity_flag) + return + if(istype(target, /obj/machinery/computer/custom_shuttle)) + if(!linkedShuttleId) + to_chat(user, "Error, no defined shuttle linked to device") + return + var/obj/machinery/computer/custom_shuttle/console = target + console.linkShuttle(linkedShuttleId) + to_chat(user, "Console linked successfully!") + return + else if(istype(target, /obj/machinery/computer/camera_advanced/shuttle_docker/custom)) + if(!linkedShuttleId) + to_chat(user, "Error, no defined shuttle linked to device") + return + var/obj/machinery/computer/camera_advanced/shuttle_docker/custom/console = target + console.linkShuttle(linkedShuttleId) + to_chat(user, "Console linked successfully!") + return + to_chat(user, "The [src] bleeps. Select an airlock to create a docking port, or a valid machine to link.") + return + +//=========== shuttle designation actions ============ +/obj/item/shuttle_creator/proc/calculate_bounds(obj/docking_port/mobile/port) + if(!port || !istype(port, /obj/docking_port/mobile)) + return FALSE + //Heights is the distance away from the port + //width is the distance perpendicular to the port + var/minX = INFINITY + var/maxX = 0 + var/minY = INFINITY + var/maxY = 0 + for(var/turf/T in loggedTurfs) + minX = min(T.x, minX) + maxX = max(T.x, maxX) + minY = min(T.y, minY) + maxY = max(T.y, maxY) + //Make sure shuttle was actually found. + if(maxX == INFINITY || maxY == INFINITY) + return FALSE + minX-- + minY-- + var/width = maxX - minX + var/height = maxY - minY + var/offset_x = port.x - minX + var/offset_y = port.y - minY + switch(port.dir) //Source: code/datums/shuttles.dm line 77 (14/03/2020) :) + if(NORTH) + port.width = width + port.height = height + port.dwidth = offset_x - 1 + port.dheight = offset_y - 1 + if(EAST) + port.width = height + port.height = width + port.dwidth = height - offset_y + port.dheight = offset_x - 1 + if(SOUTH) + port.width = width + port.height = height + port.dwidth = width - offset_x + port.dheight = height - offset_y + if(WEST) + port.width = height + port.height = width + port.dwidth = offset_y - 1 + port.dheight = width - offset_x + return TRUE + +//Go through all the all_turfs and check which direction doesn't have the shuttle +/obj/item/shuttle_creator/proc/getNonShuttleDirection(turf/targetTurf) + var/position = null + if(!(get_offset_target_turf(targetTurf, 0, 1) in loggedTurfs)) + if(position != null) + return null + position = NORTH + if(!(get_offset_target_turf(targetTurf, 0, -1) in loggedTurfs)) + if(position != null) + return null + position = SOUTH + if(!(get_offset_target_turf(targetTurf, 1, 0) in loggedTurfs)) + if(position != null) + return null + position = EAST + if(!(get_offset_target_turf(targetTurf, -1, 0) in loggedTurfs)) + if(position != null) + return null + position = WEST + return position + +/obj/item/shuttle_creator/proc/invertDir(var/input_dir) + if(input_dir == NORTH) + return SOUTH + else if(input_dir == SOUTH) + return NORTH + else if(input_dir == EAST) + return WEST + else if(input_dir == WEST) + return EAST + return null + +/obj/item/shuttle_creator/proc/shuttle_create_docking_port(atom/target, mob/user) + + if(loggedTurfs.len == 0 || !recorded_shuttle_area) + to_chat(user, "Invalid shuttle, restarting bluespace systems...") + return FALSE + + var/datum/map_template/shuttle/new_shuttle = new /datum/map_template/shuttle() + + var/obj/docking_port/mobile/port = new /obj/docking_port/mobile(get_turf(target)) + var/obj/docking_port/stationary/stationary_port = new /obj/docking_port/stationary(get_turf(target)) + port.callTime = 50 + port.dir = 1 //Point away from space. + port.id = "custom_[GLOB.custom_shuttle_count]" + linkedShuttleId = port.id + port.ignitionTime = 25 + port.name = "Custom Shuttle" + port.port_direction = 2 + port.preferred_direction = 4 + port.area_type = recorded_shuttle_area + + stationary_port.area_type = overwritten_area + + var/portDirection = getNonShuttleDirection(get_turf(port)) + var/invertedDir = invertDir(portDirection) + if(!portDirection || !invertedDir) + to_chat(usr, "Shuttle creation aborted, docking airlock must be on an external wall. Please select a new airlock.") + port.Destroy() + stationary_port.Destroy() + linkedShuttleId = null + return FALSE + port.dir = invertedDir + port.port_direction = portDirection + + if(!calculate_bounds(port)) + to_chat(usr, "Bluespace calculations failed, please select a new airlock.") + port.Destroy() + stationary_port.Destroy() + linkedShuttleId = null + return FALSE + + port.shuttle_areas = list() + //var/list/all_turfs = port.return_ordered_turfs(port.x, port.y, port.z, port.dir) + var/list/all_turfs = loggedTurfs + for(var/i in 1 to all_turfs.len) + var/turf/curT = all_turfs[i] + var/area/cur_area = curT.loc + //Add the area to the shuttle <3 + if(istype(cur_area, recorded_shuttle_area)) + if(istype(curT, /turf/open/space)) + continue + if(length(curT.baseturfs) < 2) + continue + //Add the shuttle base shit to the shuttle + curT.baseturfs.Insert(3, /turf/baseturf_skipover/shuttle) + port.shuttle_areas[cur_area] = TRUE + + port.linkup(new_shuttle, stationary_port) + + port.movement_force = list("KNOCKDOWN" = 0, "THROW" = 0) + port.initiate_docking(stationary_port) + + port.mode = SHUTTLE_IDLE + port.timer = 0 + + port.register() + + icon_state = "rsd_used" + + //Clear highlights + overlay_holder.clear_highlights() + GLOB.custom_shuttle_count ++ + message_admins("[ADMIN_LOOKUPFLW(user)] created a new shuttle with a [src] at [ADMIN_VERBOSEJMP(user)] ([GLOB.custom_shuttle_count] custom shuttles, limit is [CUSTOM_SHUTTLE_LIMIT])") + log_game("[key_name(user)] created a new shuttle with a [src] at [AREACOORD(user)] ([GLOB.custom_shuttle_count] custom shuttles, limit is [CUSTOM_SHUTTLE_LIMIT])") + return TRUE + +/obj/item/shuttle_creator/proc/create_shuttle_area(mob/user) + //Check to see if the user can make a new area to prevent spamming + if(user) + if(user.create_area_cooldown >= world.time) + to_chat(user, "Smoke vents from the [src], maybe you should let it cooldown before using it again.") + return FALSE + user.create_area_cooldown = world.time + 10 + if(!loggedTurfs) + return FALSE + if(!check_area(loggedTurfs, FALSE)) //Makes sure nothing (Shuttles) has moved into the area during creation + return FALSE + //Create the new area + var/area/shuttle/custom/powered/newS + var/area/oldA = loggedOldArea + var/str = stripped_input(user, "Shuttle Name:", "Blueprint Editing", "", MAX_NAME_LEN) + if(!str || !length(str)) + return FALSE + if(length(str) > 50) + to_chat(user, "The provided ship name is too long, blares the [src]") + return FALSE + newS = new /area/shuttle/custom/powered() + newS.setup(str) + newS.set_dynamic_lighting() + //Shuttles always have gravity + newS.has_gravity = TRUE + newS.requires_power = TRUE + //Record the area for use when creating the docking port + recorded_shuttle_area = newS + + for(var/i in 1 to loggedTurfs.len) + var/turf/turf_holder = loggedTurfs[i] + var/area/old_area = turf_holder.loc + newS.contents += turf_holder + turf_holder.change_area(old_area, newS) + + newS.reg_in_areas_in_z() + + var/list/firedoors = oldA.firedoors + for(var/door in firedoors) + var/obj/machinery/door/firedoor/FD = door + FD.CalculateAffectingAreas() + return TRUE + +//Checks an area to ensure that the turfs provided are valid to be made into a shuttle +/obj/item/shuttle_creator/proc/check_area(list/turfs, addingTurfs = TRUE) + if(!turfs) + to_chat(usr, "Shuttles must be created in an airtight space, ensure that the shuttle is airtight, including corners.") + return FALSE + if(turfs.len + (addingTurfs ? loggedTurfs.len : 0) > SHUTTLE_CREATOR_MAX_SIZE) + to_chat(usr, "The [src]'s internal cooling system wizzes violently and a message appears on the screen, \"Caution, this device can only handle the creation of shuttles up to [SHUTTLE_CREATOR_MAX_SIZE] units in size. Please reduce your shuttle by [turfs.len-SHUTTLE_CREATOR_MAX_SIZE]. Sorry for the inconvinience\"") + return FALSE + //Check to see if it's a valid shuttle + for(var/i in 1 to turfs.len) + var/area/place = get_area(turfs[i]) + //If any of the turfs are on station / not in space, a shuttle cannot be forced there + if(!place) + to_chat(usr, "You can't seem to overpower the bluespace harmonics in this location, try somewhere else.") + return FALSE + if(istype(place, /area/space)) + overwritten_area = /area/space + else if(istype(place, /area/lavaland/surface/outdoors)) + overwritten_area = /area/lavaland/surface/outdoors + else + to_chat(usr, "Caution, shuttle must not use any material connected to the station. Your shuttle is currenly overlapping with [place.name]") + return FALSE + //Finally, check to see if the area is actually attached + if(!LAZYLEN(loggedTurfs)) + return TRUE + for(var/turf/T in turfs) + if(turf_connected_to_saved_turfs(T)) + return TRUE + CHECK_TICK + to_chat(usr, "Caution, new areas of the shuttle must be connected to the other areas of the shuttle.") + return FALSE + +/obj/item/shuttle_creator/proc/turf_connected_to_saved_turfs(turf/T) + for(var/i in 1 to 4) + var/turf/adjacentT = get_offset_target_turf(T, CARDINAL_DIRECTIONS_X[i], CARDINAL_DIRECTIONS_Y[i]) + if(adjacentT in loggedTurfs) + return TRUE + return FALSE + +/obj/item/shuttle_creator/proc/turf_in_list(turf/T) + return loggedTurfs.Find(T) + +/obj/item/shuttle_creator/proc/add_single_turf(turf/T) + if(!check_area(list(T))) + return FALSE + loggedTurfs |= T + loggedOldArea = get_area(T) + overlay_holder.highlight_turf(T) + +/obj/item/shuttle_creator/proc/add_saved_area(mob/user) + var/static/area_or_turf_fail_types = typecacheof(list( + /turf/open/space, + /area/shuttle + )) + //Detect the turfs connected in the curerrent enclosed area + var/list/turfs = detect_room(get_turf(user), area_or_turf_fail_types) + if(!check_area(turfs)) + return FALSE + loggedOldArea = get_area(get_turf(user)) + loggedTurfs |= turfs + overlay_holder.highlight_area(turfs) + //TODO READD THIS SHIT: icon_state = "rsd_used" + to_chat(user, "You add the area into the buffer of the [src], you made add more areas or select an airlock to act as a docking port to complete the shuttle.") + return turfs + +/obj/item/shuttle_creator/proc/remove_single_turf(turf/T) + if(!turf_in_list(T)) + return + loggedTurfs -= T + loggedOldArea = get_area(T) + overlay_holder.unhighlight_turf(T) + +/obj/item/shuttle_creator/proc/reset_saved_area() + overlay_holder.clear_highlights() + loggedTurfs.Cut() + to_chat(usr, "You reset the area buffer on the [src].") + +#undef CARDINAL_DIRECTIONS_X +#undef CARDINAL_DIRECTIONS_Y diff --git a/code/modules/shuttle/shuttle_creation/shuttle_creator_actions.dm b/code/modules/shuttle/shuttle_creation/shuttle_creator_actions.dm new file mode 100644 index 0000000000..f50b3bf59f --- /dev/null +++ b/code/modules/shuttle/shuttle_creation/shuttle_creator_actions.dm @@ -0,0 +1,101 @@ +//============ Actions ============ +/datum/action/innate/shuttle_creator + icon_icon = 'icons/mob/actions/actions_shuttle.dmi' + var/mob/living/C + var/mob/camera/aiEye/remote/shuttle_creation/remote_eye + var/obj/item/shuttle_creator/shuttle_creator + +/datum/action/innate/shuttle_creator/Activate() + if(!target) + return TRUE + C = owner + remote_eye = C.remote_control + var/obj/machinery/computer/camera_advanced/shuttle_creator/internal_console = target + shuttle_creator = internal_console.owner_rsd + +//Add an area +/datum/action/innate/shuttle_creator/designate_area + name = "Designate Room" + button_icon_state = "designate_area" + +/datum/action/innate/shuttle_creator/designate_area/Activate() + if(..()) + return + shuttle_creator.add_saved_area(remote_eye) + +//Add a single turf +/datum/action/innate/shuttle_creator/designate_turf + name = "Designate Turf" + button_icon_state = "designate_turf" + +/datum/action/innate/shuttle_creator/designate_turf/Activate() + if(..()) + return + var/turf/T = get_turf(remote_eye) + if(istype(T, /turf/open/space)) + var/connectors_exist = FALSE + for(var/obj/structure/lattice/lattice in T) + connectors_exist = TRUE + break + if(!connectors_exist) + to_chat(usr, "This turf requires support, build some catwalks or lattices.") + return + if(!shuttle_creator.check_area(list(T))) + return + if(shuttle_creator.turf_in_list(T)) + return + shuttle_creator.add_single_turf(T) + +//Clear a single entire area +/datum/action/innate/shuttle_creator/clear_turf + name = "Clear Turf" + button_icon_state = "clear_turf" + +/datum/action/innate/shuttle_creator/clear_turf/Activate() + if(..()) + return + shuttle_creator.remove_single_turf(get_turf(remote_eye)) + +//Clear the entire area +/datum/action/innate/shuttle_creator/reset + name = "Reset Buffer" + button_icon_state = "clear_area" + +/datum/action/innate/shuttle_creator/reset/Activate() + if(..()) + return + shuttle_creator.reset_saved_area() + +//Finish the shuttle +/datum/action/innate/shuttle_creator/airlock + name = "Select Docking Airlock" + button_icon_state = "select_airlock" + +/datum/action/innate/shuttle_creator/airlock/Activate() + if(..()) + return + var/turf/T = get_turf(remote_eye) + for(var/obj/machinery/door/airlock/A in T) + if(get_area(A) != shuttle_creator.loggedOldArea) + to_chat(C, "Caution, airlock must be on the shuttle to function as a dock.") + return + if(shuttle_creator.linkedShuttleId) + return + if(GLOB.custom_shuttle_count > CUSTOM_SHUTTLE_LIMIT) + to_chat(C, "Shuttle limit reached, sorry.") + return + if(shuttle_creator.loggedTurfs.len > SHUTTLE_CREATOR_MAX_SIZE) + to_chat(C, "This shuttle is too large!") + return + if(!shuttle_creator.getNonShuttleDirection(T)) + to_chat(C, "Docking port must be on an external wall, with only 1 side exposed to space.") + return + if(!shuttle_creator.create_shuttle_area(C)) + return + if(shuttle_creator.shuttle_create_docking_port(A, C)) + to_chat(C, "Shuttle created!") + //Remove eye control + var/obj/machinery/computer/camera_advanced/shuttle_creator/internal_console = target + internal_console.remove_eye_control() + qdel(internal_console) + return diff --git a/code/modules/shuttle/shuttle_creation/shuttle_creator_console.dm b/code/modules/shuttle/shuttle_creation/shuttle_creator_console.dm new file mode 100644 index 0000000000..6945c93427 --- /dev/null +++ b/code/modules/shuttle/shuttle_creation/shuttle_creator_console.dm @@ -0,0 +1,93 @@ +//============The internal camera console used for designating the area============= +/obj/machinery/computer/camera_advanced/shuttle_creator + name = "internal shuttle creator console" + desc = "You should not have access to this, please report this as a bug" + networks = list() + var/obj/item/shuttle_creator/owner_rsd + var/datum/action/innate/shuttle_creator/designate_area/area_action = new + var/datum/action/innate/shuttle_creator/designate_turf/turf_action = new + var/datum/action/innate/shuttle_creator/clear_turf/clear_turf_action = new + var/datum/action/innate/shuttle_creator/reset/reset_action = new + var/datum/action/innate/shuttle_creator/airlock/airlock_action = new + +/obj/machinery/computer/camera_advanced/shuttle_creator/check_eye(mob/user) + if(user.eye_blind || user.incapacitated()) + user.unset_machine() + +/obj/machinery/computer/camera_advanced/shuttle_creator/CreateEye() + eyeobj = new /mob/camera/aiEye/remote/shuttle_creation(get_turf(owner_rsd)) + eyeobj.origin = src + eyeobj.use_static = USE_STATIC_NONE + +/obj/machinery/computer/camera_advanced/shuttle_creator/is_operational() + return TRUE + +/obj/machinery/computer/camera_advanced/shuttle_creator/can_interact(mob/user) + if(!isliving(user)) + return FALSE + var/mob/living/L = user + if(L.incapacitated()) + return FALSE + return TRUE + +/obj/machinery/computer/camera_advanced/shuttle_creator/GrantActions(mob/living/user) + ..(user) + eyeobj.invisibility = SEE_INVISIBLE_LIVING + if(area_action) + area_action.target = src + area_action.Grant(user) + actions += area_action + if(turf_action) + turf_action.target = src + turf_action.Grant(user) + actions += turf_action + if(clear_turf_action) + clear_turf_action.target = src + clear_turf_action.Grant(user) + actions += clear_turf_action + if(reset_action) + reset_action.target = src + reset_action.Grant(user) + actions += reset_action + if(airlock_action) + airlock_action.target = src + airlock_action.Grant(user) + actions += airlock_action + +/obj/machinery/computer/camera_advanced/shuttle_creator/remove_eye_control(mob/living/user) + . = ..() + owner_rsd.overlay_holder.remove_client() + eyeobj.invisibility = INVISIBILITY_MAXIMUM + if(user.client) + user.client.images -= eyeobj.user_image + +/obj/machinery/computer/camera_advanced/shuttle_creator/attack_hand(mob/user) + if(!is_operational()) //you cant use broken machine you chumbis + return + if(current_user) + to_chat(user, "The console is already in use!") + return + var/mob/living/L = user + if(!can_use(user)) + return + if(!eyeobj) + CreateEye() + if(!eyeobj.eye_initialized) + var/camera_location = get_turf(owner_rsd) + if(camera_location) + eyeobj.eye_initialized = TRUE + give_eye_control(L) + eyeobj.setLoc(camera_location) + var/mob/camera/aiEye/remote/shuttle_creation/shuttle_eye = eyeobj + shuttle_eye.source_turf = get_turf(user) + else + user.unset_machine() + else + var/camera_location = get_turf(owner_rsd) + var/mob/camera/aiEye/remote/shuttle_creation/eye = eyeobj + give_eye_control(L) + if(camera_location) + eye.source_turf = camera_location + eyeobj.setLoc(camera_location) + else + eyeobj.setLoc(eyeobj.loc) diff --git a/code/modules/shuttle/shuttle_creation/shuttle_creator_eye.dm b/code/modules/shuttle/shuttle_creation/shuttle_creator_eye.dm new file mode 100644 index 0000000000..ff96e2ae65 --- /dev/null +++ b/code/modules/shuttle/shuttle_creation/shuttle_creator_eye.dm @@ -0,0 +1,54 @@ +//===============Camera Eye================ +/mob/camera/aiEye/remote/shuttle_creation + name = "shuttle holo-drone" + icon = 'icons/obj/mining.dmi' + icon_state = "construction_drone" + visible_icon = FALSE + acceleration = 0 + var/turf/source_turf + var/max_range = 12 + +/mob/camera/aiEye/remote/shuttle_creation/Initialize() + . = ..() + setLoc(get_turf(source_turf)) + +/mob/camera/aiEye/remote/shuttle_creation/update_remote_sight(mob/living/user) + user.sight = BLIND|SEE_TURFS + user.lighting_alpha = LIGHTING_PLANE_ALPHA_INVISIBLE + user.sync_lighting_plane_alpha() + return TRUE + +/mob/camera/aiEye/remote/shuttle_creation/relaymove(mob/user, direct) + dir = direct //This camera eye is visible as a drone, and needs to keep the dir updated + var/initial = initial(sprint) + var/max_sprint = 50 + + if(cooldown && cooldown < world.timeofday) // 3 seconds + sprint = initial + + for(var/i = 0; i < max(sprint, initial); i += 20) + var/turf/step = get_turf(get_step(src, direct)) + if(step && can_move_to(step)) + setLoc(step) + + cooldown = world.timeofday + 5 + if(acceleration) + sprint = min(sprint + 0.5, max_sprint) + else + sprint = initial + +/mob/camera/aiEye/remote/shuttle_creation/proc/can_move_to(var/turf/T) + var/origin_x = source_turf.x + var/origin_y = source_turf.y + var/change_X = abs(origin_x - T.x) + var/change_Y = abs(origin_y - T.y) + return (change_X < max_range && change_Y < max_range) + +/mob/camera/aiEye/remote/shuttle_creation/setLoc(T) + ..() + if(eye_user?.client) + eye_user.client.images -= user_image + var/image/I = image(icon, loc, icon_state, FLY_LAYER, dir) + I.plane = MASSIVE_OBJ_LAYER + user_image = I + eye_user.client.images += user_image diff --git a/code/modules/shuttle/shuttle_creation/shuttle_creator_overlay.dm b/code/modules/shuttle/shuttle_creation/shuttle_creator_overlay.dm new file mode 100644 index 0000000000..919b1f0221 --- /dev/null +++ b/code/modules/shuttle/shuttle_creation/shuttle_creator_overlay.dm @@ -0,0 +1,52 @@ +/* + * Manages the overlays for the shuttle creator drone. +*/ + +/datum/shuttle_creator_overlay_holder + var/client/holder + var/list/images = list() + var/list/turfs = list() + +/datum/shuttle_creator_overlay_holder/proc/add_client(client/C) + holder = C + holder.images += images + +/datum/shuttle_creator_overlay_holder/proc/remove_client() + holder.images -= images + holder = null + +/datum/shuttle_creator_overlay_holder/proc/clear_highlights() + if(holder) + holder.images -= images + images.Cut() + turfs.Cut() + +/datum/shuttle_creator_overlay_holder/proc/create_hightlight(turf/T) + if(T in turfs) + return + var/image/I = image('icons/turf/overlays.dmi', T, "greenOverlay") + I.plane = ABOVE_LIGHTING_PLANE + images += I + holder.images += I + turfs += T + +/datum/shuttle_creator_overlay_holder/proc/remove_hightlight(turf/T) + if(!(T in turfs)) + return + turfs -= T + holder.images -= images + for(var/image/I in images) + if(get_turf(I) != T) + continue + images -= I + holder.images += images + +/datum/shuttle_creator_overlay_holder/proc/highlight_area(list/turfs) + for(var/turf/T in turfs) + highlight_turf(T) + +/datum/shuttle_creator_overlay_holder/proc/highlight_turf(turf/T) + create_hightlight(T) + +/datum/shuttle_creator_overlay_holder/proc/unhighlight_turf(turf/T) + remove_hightlight(T) diff --git a/code/modules/shuttle/shuttle_creation/shuttle_upgrades.dm b/code/modules/shuttle/shuttle_creation/shuttle_upgrades.dm new file mode 100644 index 0000000000..b014b7bbf8 --- /dev/null +++ b/code/modules/shuttle/shuttle_creation/shuttle_upgrades.dm @@ -0,0 +1,39 @@ +/obj/item/shuttle_route_optimisation + name = "Route Optimisation Upgrade" + desc = "Used on a custom shuttle control console to calculate more efficient routes." + icon = 'icons/obj/module.dmi' + icon_state = "shuttledisk" + force = 0 + throwforce = 8 + throw_speed = 3 + throw_range = 5 + density = FALSE + anchored = FALSE + item_flags = NOBLUDGEON + var/upgrade_amount = 0.8 + +/obj/item/shuttle_route_optimisation/hyperlane + name = "Bluespace Hyperlane Calculator" + desc = "Used on a custom shuttle control console to allow for the following of bluespace hyperlanes, increasing the efficiency of the shuttle." + icon_state = "shuttledisk_better" + upgrade_amount = 0.6 + +/obj/item/shuttle_route_optimisation/void + name = "Voidspace Route Calculator" + desc = "Used on a custom shuttle control console to allow it to navigate into voidspace, making the routes almost instant." + icon_state = "shuttledisk_void" + upgrade_amount = 0.2 + +/obj/item/shuttle_route_optimisation/attack_obj(obj/O, mob/living/user) + . = ..() + if(!istype(O, /obj/machinery/computer)) + return + if(!istype(O, /obj/machinery/computer/custom_shuttle)) + to_chat(user, "This upgrade only works on a custom shuttle flight console.") + return + if (!user.transferItemToLoc(src, get_turf(O))) + return + var/obj/machinery/computer/custom_shuttle/link_comp = O + link_comp.distance_multiplier = clamp(link_comp.distance_multiplier, 0, upgrade_amount) + playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, 0) + to_chat(usr, "You insert the disk into the flight computer, allowing for routes to be [upgrade_amount]x the original distance.") diff --git a/icons/mob/actions/actions_shuttle.dmi b/icons/mob/actions/actions_shuttle.dmi new file mode 100644 index 0000000000000000000000000000000000000000..ef35786a2b2b9ccb981a3ec79ef57e0786f8e00c GIT binary patch literal 3034 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 diff --git a/tgstation.dme b/tgstation.dme index 39f79a0b71..a3b5c105b4 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -828,6 +828,9 @@ #include "code\game\machinery\porta_turret\portable_turret.dm" #include "code\game\machinery\porta_turret\portable_turret_construct.dm" #include "code\game\machinery\porta_turret\portable_turret_cover.dm" +#include "code\game\machinery\shuttle\custom_shuttle.dm" +#include "code\game\machinery\shuttle\shuttle_engine.dm" +#include "code\game\machinery\shuttle\shuttle_heater.dm" #include "code\game\machinery\telecomms\broadcasting.dm" #include "code\game\machinery\telecomms\machine_interactions.dm" #include "code\game\machinery\telecomms\telecomunications.dm" @@ -3139,6 +3142,7 @@ #include "code\modules\shuttle\arrivals.dm" #include "code\modules\shuttle\assault_pod.dm" #include "code\modules\shuttle\computer.dm" +#include "code\modules\shuttle\custom_shuttle.dm" #include "code\modules\shuttle\docking.dm" #include "code\modules\shuttle\elevator.dm" #include "code\modules\shuttle\emergency.dm" @@ -3155,6 +3159,12 @@ #include "code\modules\shuttle\supply.dm" #include "code\modules\shuttle\syndicate.dm" #include "code\modules\shuttle\white_ship.dm" +#include "code\modules\shuttle\shuttle_creation\shuttle_creator.dm" +#include "code\modules\shuttle\shuttle_creation\shuttle_creator_actions.dm" +#include "code\modules\shuttle\shuttle_creation\shuttle_creator_console.dm" +#include "code\modules\shuttle\shuttle_creation\shuttle_creator_eye.dm" +#include "code\modules\shuttle\shuttle_creation\shuttle_creator_overlay.dm" +#include "code\modules\shuttle\shuttle_creation\shuttle_upgrades.dm" #include "code\modules\spells\spell.dm" #include "code\modules\spells\spell_types\aimed.dm" #include "code\modules\spells\spell_types\area_teleport.dm"