diff --git a/aurorastation.dme b/aurorastation.dme index b608b3e2674..f1c09b12336 100644 --- a/aurorastation.dme +++ b/aurorastation.dme @@ -44,6 +44,7 @@ #include "code\__defines\mobs.dm" #include "code\__defines\modular_guns.dm" #include "code\__defines\obj.dm" +#include "code\__defines\overmap.dm" #include "code\__defines\psi.dm" #include "code\__defines\qdel.dm" #include "code\__defines\radio.dm" @@ -2209,6 +2210,21 @@ #include "code\modules\organs\subtypes\unbreakable.dm" #include "code\modules\organs\subtypes\vaurca.dm" #include "code\modules\organs\subtypes\xenos.dm" +#include "code\modules\overmap\_defines.dm" +#include "code\modules\overmap\overmap_object.dm" +#include "code\modules\overmap\overmap_shuttle.dm" +#include "code\modules\overmap\README.dm" +#include "code\modules\overmap\sectors.dm" +#include "code\modules\overmap\spacetravel.dm" +#include "code\modules\overmap\ships\landable.dm" +#include "code\modules\overmap\ships\ship.dm" +#include "code\modules\overmap\ships\computers\engine_control.dm" +#include "code\modules\overmap\ships\computers\helm.dm" +#include "code\modules\overmap\ships\computers\sensors.dm" +#include "code\modules\overmap\ships\computers\ship.dm" +#include "code\modules\overmap\ships\computers\shuttle.dm" +#include "code\modules\overmap\ships\engines\engine.dm" +#include "code\modules\overmap\ships\engines\gas_thruster.dm" #include "code\modules\paperwork\carbonpaper.dm" #include "code\modules\paperwork\clipboard.dm" #include "code\modules\paperwork\faxmachine.dm" @@ -2759,6 +2775,7 @@ #include "code\unit_tests\mob_tests.dm" #include "code\unit_tests\object_tests.dm" #include "code\unit_tests\observation_tests.dm" +#include "code\unit_tests\overmap_tests.dm" #include "code\unit_tests\spawner_tests.dm" #include "code\unit_tests\sql_tests.dm" #include "code\unit_tests\ss_test.dm" diff --git a/code/__defines/_macros.dm b/code/__defines/_macros.dm index e97ef70ad61..8366200602c 100644 --- a/code/__defines/_macros.dm +++ b/code/__defines/_macros.dm @@ -49,6 +49,8 @@ #define isobj(A) istype(A, /obj) +#define isspace(A) istype(A, /area/space) + #define isobserver(A) istype(A, /mob/abstract/observer) #define isorgan(A) istype(A, /obj/item/organ/external) @@ -88,4 +90,5 @@ #define isopenturf(target) istype(target, /turf/simulated/open) #define isweakref(target) istype(target, /datum/weakref) +#define isopenspace(A) istype(A, /turf/simulated/open) #define isdatum(target) istype(target, /datum) diff --git a/code/__defines/math_physics.dm b/code/__defines/math_physics.dm index 55eb4ecdea0..5779e845f64 100644 --- a/code/__defines/math_physics.dm +++ b/code/__defines/math_physics.dm @@ -44,3 +44,6 @@ #define round_duration_in_ticks (round_start_time ? world.time - round_start_time : 0) #define station_time_in_ticks (roundstart_hour HOURS + round_duration_in_ticks) + +#define SIMPLE_SIGN(X) ((X) < 0 ? -1 : 1) +#define SIGN(X) ((X) ? SIMPLE_SIGN(X) : 0) diff --git a/code/__defines/overmap.dm b/code/__defines/overmap.dm new file mode 100644 index 00000000000..06ab37a0580 --- /dev/null +++ b/code/__defines/overmap.dm @@ -0,0 +1,13 @@ +#define SHIP_SIZE_TINY 1 +#define SHIP_SIZE_SMALL 2 +#define SHIP_SIZE_LARGE 3 + +//multipliers for max_speed to find 'slow' and 'fast' speeds for the ship +#define SHIP_SPEED_SLOW 1/(40 SECONDS) +#define SHIP_SPEED_FAST 3/(20 SECONDS)// 15 speed + +#define OVERMAP_WEAKNESS_NONE 0 +#define OVERMAP_WEAKNESS_FIRE 1 +#define OVERMAP_WEAKNESS_EMP 2 +#define OVERMAP_WEAKNESS_MINING 4 +#define OVERMAP_WEAKNESS_EXPLOSIVE 8 \ No newline at end of file diff --git a/code/__defines/shuttle.dm b/code/__defines/shuttle.dm index a27b4e21d81..d6729701a32 100644 --- a/code/__defines/shuttle.dm +++ b/code/__defines/shuttle.dm @@ -5,6 +5,11 @@ #define SHUTTLE_FLAGS_ZERO_G 4 #define SHUTTLE_FLAGS_ALL (~SHUTTLE_FLAGS_NONE) +//Overmap landable shuttles. +#define SHIP_STATUS_LANDED 1 +#define SHIP_STATUS_TRANSIT 2 +#define SHIP_STATUS_OVERMAP 3 + //Landmarks. #define SLANDMARK_FLAG_AUTOSET 1 // If set, will set base area and turf type to same as where it was spawned at #define SLANDMARK_FLAG_ZERO_G 2 // Zero-G shuttles moved here will lose gravity unless the area has ambient gravity. diff --git a/code/_helpers/game.dm b/code/_helpers/game.dm index 6dad2969e0d..6f1f9c60b2f 100644 --- a/code/_helpers/game.dm +++ b/code/_helpers/game.dm @@ -263,8 +263,6 @@ if (!objs[O]) objs[O] = TRUE -#define SIGN(X) ((X<0)?-1:1) - proc inLineOfSight(X1,Y1,X2,Y2,Z=1,PX1=16.5,PY1=16.5,PX2=16.5,PY2=16.5) var/turf/T @@ -295,7 +293,6 @@ proc if(T.opacity) return 0 return 1 -#undef SIGN proc/isInSight(var/atom/A, var/atom/B) var/turf/Aturf = get_turf(A) diff --git a/code/controllers/subsystems/processing/shuttle.dm b/code/controllers/subsystems/processing/shuttle.dm index 9fe5d7d5fa1..f623e32d563 100644 --- a/code/controllers/subsystems/processing/shuttle.dm +++ b/code/controllers/subsystems/processing/shuttle.dm @@ -7,6 +7,8 @@ var/datum/controller/subsystem/processing/shuttle/SSshuttle priority = SS_PRIORITY_SHUTTLE init_order = SS_INIT_MISC //Should be initialized after all maploading is over and atoms are initialized, to ensure that landmarks have been initialized. + var/overmap_halted = FALSE //Whether ships can move on the overmap; used for adminbus. + var/list/ships = list() //List of all ships. var/list/shuttles = list() //maps shuttle tags to shuttle datums, so that they can be looked up. var/list/process_shuttles = list() //simple list of shuttles, for processing var/list/registered_shuttle_landmarks = list() @@ -14,7 +16,10 @@ var/datum/controller/subsystem/processing/shuttle/SSshuttle var/list/docking_registry = list() //Docking controller tag -> docking controller program, mostly for init purposes. var/list/shuttle_areas = list() //All the areas of all shuttles. + var/list/landmarks_awaiting_sector = list() //Stores automatic landmarks that are waiting for a sector to finish loading. + var/list/landmarks_still_needed = list() //Stores landmark_tags that need to be assigned to the sector (landmark_tag = sector) when registered. var/list/shuttles_to_initialize = list() //A queue for shuttles to initialize at the appropriate time. + var/list/sectors_to_initialize //Used to find all sector objects at the appropriate time. var/block_queue = TRUE var/tmp/list/working_shuttles @@ -51,6 +56,7 @@ var/datum/controller/subsystem/processing/shuttle/SSshuttle if(block_queue) return initialize_shuttles() + initialize_sectors() /datum/controller/subsystem/processing/shuttle/proc/initialize_shuttles() var/list/shuttles_made = list() @@ -58,8 +64,14 @@ var/datum/controller/subsystem/processing/shuttle/SSshuttle var/shuttle = initialize_shuttle(shuttle_type) if(shuttle) shuttles_made += shuttle + hook_up_motherships(shuttles_made) shuttles_to_initialize = null +/datum/controller/subsystem/processing/shuttle/proc/initialize_sectors() + for(var/sector in sectors_to_initialize) + initialize_sector(sector) + sectors_to_initialize = null + /datum/controller/subsystem/processing/shuttle/proc/register_landmark(shuttle_landmark_tag, obj/effect/shuttle_landmark/shuttle_landmark) if (registered_shuttle_landmarks[shuttle_landmark_tag]) CRASH("Attempted to register shuttle landmark with tag [shuttle_landmark_tag], but it is already registered!") @@ -67,12 +79,78 @@ var/datum/controller/subsystem/processing/shuttle/SSshuttle registered_shuttle_landmarks[shuttle_landmark_tag] = shuttle_landmark last_landmark_registration_time = world.time + var/obj/effect/overmap/visitable/O = landmarks_still_needed[shuttle_landmark_tag] + if(O) //These need to be added to sectors, which we handle. + try_add_landmark_tag(shuttle_landmark_tag, O) + landmarks_still_needed -= shuttle_landmark_tag + else if(istype(shuttle_landmark, /obj/effect/shuttle_landmark/automatic)) //These find their sector automatically + O = map_sectors["[shuttle_landmark.z]"] + if(O) + O.add_landmark(shuttle_landmark, shuttle_landmark.shuttle_restricted) + else + landmarks_awaiting_sector += shuttle_landmark + /datum/controller/subsystem/processing/shuttle/proc/get_landmark(var/shuttle_landmark_tag) return registered_shuttle_landmarks[shuttle_landmark_tag] +//Checks if the given sector's landmarks have initialized; if so, registers them with the sector, if not, marks them for assignment after they come in. +//Also adds automatic landmarks that were waiting on their sector to spawn. +/datum/controller/subsystem/processing/shuttle/proc/initialize_sector(obj/effect/overmap/visitable/given_sector) + given_sector.populate_sector_objects() // This is a late init operation that sets up the sector's map_z and does non-overmap-related init tasks. + + for(var/landmark_tag in given_sector.initial_generic_waypoints) + if(!try_add_landmark_tag(landmark_tag, given_sector)) + landmarks_still_needed[landmark_tag] = given_sector + + for(var/shuttle_name in given_sector.initial_restricted_waypoints) + for(var/landmark_tag in given_sector.initial_restricted_waypoints[shuttle_name]) + if(!try_add_landmark_tag(landmark_tag, given_sector)) + landmarks_still_needed[landmark_tag] = given_sector + + var/landmarks_to_check = landmarks_awaiting_sector.Copy() + for(var/thing in landmarks_to_check) + var/obj/effect/shuttle_landmark/automatic/landmark = thing + if(landmark.z in given_sector.map_z) + given_sector.add_landmark(landmark, landmark.shuttle_restricted) + landmarks_awaiting_sector -= landmark + +/datum/controller/subsystem/processing/shuttle/proc/try_add_landmark_tag(landmark_tag, obj/effect/overmap/visitable/given_sector) + var/obj/effect/shuttle_landmark/landmark = get_landmark(landmark_tag) + if(!landmark) + return FALSE + + if(landmark.landmark_tag in given_sector.initial_generic_waypoints) + given_sector.add_landmark(landmark) + . = TRUE + for(var/shuttle_name in given_sector.initial_restricted_waypoints) + if(landmark.landmark_tag in given_sector.initial_restricted_waypoints[shuttle_name]) + given_sector.add_landmark(landmark, shuttle_name) + . = TRUE + /datum/controller/subsystem/processing/shuttle/proc/initialize_shuttle(var/shuttle_type) var/datum/shuttle/shuttle = shuttle_type if(initial(shuttle.category) != shuttle_type) shuttle = new shuttle() shuttle_areas |= shuttle.shuttle_area return shuttle + +/datum/controller/subsystem/processing/shuttle/proc/hook_up_motherships(shuttles_list) + for(var/datum/shuttle/S in shuttles_list) + if(S.mothershuttle && !S.motherdock) + var/datum/shuttle/mothership = shuttles[S.mothershuttle] + if(mothership) + S.motherdock = S.current_location.landmark_tag + mothership.shuttle_area |= S.shuttle_area + else + error("Shuttle [S] was unable to find mothership [mothership]!") + +/datum/controller/subsystem/processing/shuttle/proc/toggle_overmap(new_setting) + if(overmap_halted == new_setting) + return + overmap_halted = !overmap_halted + for(var/ship in ships) + var/obj/effect/overmap/visitable/ship/ship_effect = ship + overmap_halted ? ship_effect.halt() : ship_effect.unhalt() + +/datum/controller/subsystem/processing/shuttle/stat_entry() + ..("Shuttles:[shuttles.len], Ships:[ships.len], L:[registered_shuttle_landmarks.len][overmap_halted ? ", HALT" : ""]") diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm index 8940c29a571..8f874b29378 100644 --- a/code/game/atoms_movable.dm +++ b/code/game/atoms_movable.dm @@ -212,6 +212,10 @@ if(z in current_map.sealed_levels) return + if(current_map.use_overmap) + overmap_spacetravel(get_turf(src), src) + return + var/move_to_z = src.get_transit_zlevel() if(move_to_z) z = move_to_z diff --git a/code/game/objects/items/weapons/tanks/tanks.dm b/code/game/objects/items/weapons/tanks/tanks.dm index dcee2df81e6..2cf75d9ff4b 100644 --- a/code/game/objects/items/weapons/tanks/tanks.dm +++ b/code/game/objects/items/weapons/tanks/tanks.dm @@ -306,3 +306,7 @@ else if(integrity < 3) integrity++ + +/obj/item/tank/proc/remove_air_by_flag(flag, amount) + . = air_contents.remove_by_flag(flag, amount) + update_icon() \ No newline at end of file diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm index 6ca0b1ac7c2..d571f8b477a 100644 --- a/code/modules/mob/mob.dm +++ b/code/modules/mob/mob.dm @@ -260,6 +260,7 @@ /mob/proc/reset_view(atom/A) if (client) + A = A ? A : eyeobj if (istype(A, /atom/movable)) client.perspective = EYE_PERSPECTIVE client.eye = A diff --git a/code/modules/overmap/README.dm b/code/modules/overmap/README.dm new file mode 100644 index 00000000000..97d8272ccf4 --- /dev/null +++ b/code/modules/overmap/README.dm @@ -0,0 +1,87 @@ +/* +The overmap system allows adding new maps to the big 'galaxy' map. +There's overmap zlevel, that looks like a map. On it, token objects (overmap objects) are moved, representing ship movement etc. +No actual turfs are moved, you would need exploration shuttles or teleports to move atoms between different sectors/ships. +Unless stated otherwise, you just need to place any of things below somewhere on the map and they'll handle the rest. + +************************************************************* +# How to make new sector +************************************************************* +0. Map whatever. +1. Make /obj/effect/overmap/visitable/sector/[whatever] + If you want explorations shuttles be able to dock here, remember to set waypoints lists +2. Put /obj/effect/overmap/visitable/sector/[whatever] on the map. Even if it's multiz, only one is needed, on any z. +3. Done. + +************************************************************* +# How to make new ship +************************************************************* +0. Map whatever. +1. Make /obj/effect/overmap/visitable/ship/[whatever] + If you want explorations shuttles be able to dock here, remember to set waypoints lists +2. Put /obj/effect/overmap/visitable/ship/[whatever] on the map. If it's multiz, only one is needed, on any z. +3. Put Helm Console anywhere on the map. +4. Put Engines Control Console anywhere on the map. +5. Put some engines hooked up to gas supply anywhere on the map. +6. Done. + +************************************************************* +# Overmap object +************************************************************* +/obj/effect/overmap/visitable +### WHAT IT DOES +Lets overmap know this place should be represented on the map as a sector/ship. +If this zlevel (or any of connected ones for multiz) doesn't have this object, you won't be able to travel there by ovemap means. +### HOW TO USE +1. Create subtype for your ship/sector. Use /ship one for ships. +2. Put it anywhere on the ship/sector map. It will do the rest on its own during init. +If your thing is multiz, only one is needed per multiz sector/ship. + +If it's player's main base (e.g Exodus), set 'base' var to 1, so it adds itself to station_levels list. +If this place cannot be reached or left with EVA, set 'in_space' var to 0 +If you want exploration shuttles (look below) to be able to dock here, set up waypoints lists. +generic_waypoints is list of landmark_tags of waypoints any shttle should be able to visit. +restricted_waypoints is list of 'shuttle name = list(landmark_tags)' pairs for waypoints only those shuttles can visit + +************************************************************* +# Helm console +************************************************************* +/obj/machinery/computer/ship/helm +### WHAT IT DOES +Lets you steer ship around on overmap. +Lets you use autopilot. +### HOW TO USE +Just place it anywhere on the ship. + +************************************************************* +# Engines control console +************************************************************* +/obj/machinery/computer/ship/engines +### WHAT IT DOES +Lets use set thrust limits for engines of your ship. +Lets you shutdown/restart the engines. +Lets you check status of engines. +### HOW TO USE +Just place it anywhere on the ship. + +************************************************************* +# Thermal engines +************************************************************* +/obj/machinery/atmospherics/unary/engine +### WHAT IT DOES +Lets your ship move on the map at all. +### HOW TO USE +Put them on map, hook up to pipes with any gas. Heavier gas (CO2/plasma) + More pressure = more thrust. + +************************************************************* +# Exploration shuttle terminal +************************************************************* +/obj/machinery/computer/shuttle_control/explore +### WHAT IT DOES +Lets you control shuttles that can change destinations and visit other sectors/ships. +### HOW TO USE +1. Define starting shuttle landmark. +2. Define a /datum/shuttle/autodock/overmap for your shuttle. Same as normal shuttle, aside from 'range' var - how many squares on overmap it can travel on its own. +3. Place console anywhere on the ship/sector. Set shuttle_tag to shuttle's name. +4. Use. You can select destinations if you're in range (on same tile by defualt) on the map and sector has waypoints lists defined +*/ \ No newline at end of file diff --git a/code/modules/overmap/_defines.dm b/code/modules/overmap/_defines.dm new file mode 100644 index 00000000000..30c69c318f6 --- /dev/null +++ b/code/modules/overmap/_defines.dm @@ -0,0 +1,79 @@ +//How far from the edge of overmap zlevel could randomly placed objects spawn +#define OVERMAP_EDGE 2 +//Dimension of overmap (squares 4 lyfe) +var/global/list/map_sectors = list() + +/area/overmap/ + name = "System Map" + icon_state = "start" + requires_power = 0 + base_turf = /turf/unsimulated/map + +/turf/unsimulated/map + icon = 'icons/turf/space.dmi' + icon_state = "map" + permit_ao = FALSE + +/turf/unsimulated/map/edge + opacity = 1 + density = 1 + +/turf/unsimulated/map/Initialize(mapload) + . = ..() + name = "[x]-[y]" + var/list/numbers = list() + + if(x == 1 || x == current_map.overmap_size) + numbers += list("[round(y/10)]","[round(y%10)]") + if(y == 1 || y == current_map.overmap_size) + numbers += "-" + if(y == 1 || y == current_map.overmap_size) + numbers += list("[round(x/10)]","[round(x%10)]") + + for(var/i = 1 to numbers.len) + var/image/I = image('icons/effects/numbers.dmi',numbers[i]) + I.pixel_x = 5*i - 2 + I.pixel_y = world.icon_size/2 - 3 + if(y == 1) + I.pixel_y = 3 + I.pixel_x = 5*i + 4 + if(y == current_map.overmap_size) + I.pixel_y = world.icon_size - 9 + I.pixel_x = 5*i + 4 + if(x == 1) + I.pixel_x = 5*i - 2 + if(x == current_map.overmap_size) + I.pixel_x = 5*i + 2 + overlays += I + +//list used to track which zlevels are being 'moved' by the proc below +var/list/moving_levels = list() +//Proc to 'move' stars in spess +//yes it looks ugly, but it should only fire when state actually change. +//null direction stops movement +proc/toggle_move_stars(zlevel, direction) + if(!zlevel) + return + + var/gen_dir = null + if(direction & (NORTH|SOUTH)) + gen_dir += "ns" + else if(direction & (EAST|WEST)) + gen_dir += "ew" + if(!direction) + gen_dir = null + + if (moving_levels["[zlevel]"] != gen_dir) + moving_levels["[zlevel]"] = gen_dir + + var/list/spaceturfs = block(locate(1, 1, zlevel), locate(world.maxx, world.maxy, zlevel)) + for(var/turf/space/T in spaceturfs) + if(!gen_dir) + T.icon_state = "white" + else + T.icon_state = "speedspace_[gen_dir]_[rand(1,15)]" + for(var/atom/movable/AM in T) + if (AM.simulated && !AM.anchored) + AM.throw_at(get_step(T,reverse_direction(direction)), 5, 1) + CHECK_TICK + CHECK_TICK diff --git a/code/modules/overmap/overmap_object.dm b/code/modules/overmap/overmap_object.dm new file mode 100644 index 00000000000..b9be6472e6e --- /dev/null +++ b/code/modules/overmap/overmap_object.dm @@ -0,0 +1,29 @@ +/obj/effect/overmap + name = "map object" + icon = 'icons/obj/overmap.dmi' + icon_state = "object" + color = "#fffffe" + + var/known = 1 //shows up on nav computers automatically + var/scannable //if set to TRUE will show up on ship sensors for detailed scans + +//Overlay of how this object should look on other skyboxes +/obj/effect/overmap/proc/get_skybox_representation() + return + +/obj/effect/overmap/proc/get_scan_data(mob/user) + return desc + +/obj/effect/overmap/Initialize() + . = ..() + if(!current_map.use_overmap) + return INITIALIZE_HINT_QDEL + + if(known) + layer = EFFECTS_ABOVE_LIGHTING_LAYER + for(var/obj/machinery/computer/ship/helm/H in SSmachinery.all_machines) + H.get_known_sectors() + update_icon() + +/obj/effect/overmap/update_icon() + filters = filter(type="drop_shadow", color = color + "F0", size = 2, offset = 1, x = 0, y = 0) diff --git a/code/modules/overmap/overmap_shuttle.dm b/code/modules/overmap/overmap_shuttle.dm new file mode 100644 index 00000000000..5a514dcc62a --- /dev/null +++ b/code/modules/overmap/overmap_shuttle.dm @@ -0,0 +1,159 @@ +#define waypoint_sector(waypoint) map_sectors["[waypoint.z]"] + +/datum/shuttle/autodock/overmap + warmup_time = 10 + + var/range = 0 //how many overmap tiles can shuttle go, for picking destinations and returning. + var/fuel_consumption = 0 //Amount of moles of gas consumed per trip; If zero, then shuttle is magic and does not need fuel + var/list/obj/structure/fuel_port/fuel_ports //the fuel ports of the shuttle (but usually just one) + + category = /datum/shuttle/autodock/overmap + +/datum/shuttle/autodock/overmap/New(var/_name, var/obj/effect/shuttle_landmark/start_waypoint) + ..(_name, start_waypoint) + refresh_fuel_ports_list() + +/datum/shuttle/autodock/overmap/proc/refresh_fuel_ports_list() //loop through all + fuel_ports = list() + for(var/area/A in shuttle_area) + for(var/obj/structure/fuel_port/fuel_port_in_area in A) + fuel_port_in_area.parent_shuttle = src + fuel_ports += fuel_port_in_area + +/datum/shuttle/autodock/overmap/fuel_check() + if(!src.try_consume_fuel()) //insufficient fuel + for(var/area/A in shuttle_area) + for(var/mob/living/M in A) + M.show_message(SPAN_WARNING("You hear the shuttle engines sputter... perhaps it doesn't have enough fuel?"), 2, + SPAN_WARNING("The shuttle shakes but fails to take off."), 1) + return 0 //failure! + return 1 //sucess, continue with launch + +/datum/shuttle/autodock/overmap/proc/can_go() + if(!next_location) + return FALSE + if(moving_status == SHUTTLE_INTRANSIT) + return FALSE //already going somewhere, current_location may be an intransit location instead of in a sector + return get_dist(waypoint_sector(current_location), waypoint_sector(next_location)) <= range + +/datum/shuttle/autodock/overmap/can_launch() + return ..() && can_go() + +/datum/shuttle/autodock/overmap/can_force() + return ..() && can_go() + +/datum/shuttle/autodock/overmap/get_travel_time() + var/distance_mod = get_dist(waypoint_sector(current_location),waypoint_sector(next_location)) + return move_time * (1 + distance_mod) + +/datum/shuttle/autodock/overmap/proc/set_destination(var/obj/effect/shuttle_landmark/A) + if(A != current_location) + next_location = A + +/datum/shuttle/autodock/overmap/proc/get_possible_destinations() + var/list/res = list() + for (var/obj/effect/overmap/visitable/S in range(get_turf(waypoint_sector(current_location)), range)) + var/list/waypoints = S.get_waypoints(name) + for(var/obj/effect/shuttle_landmark/LZ in waypoints) + if(LZ.is_valid(src)) + res["[waypoints[LZ]] - [LZ.name]"] = LZ + return res + +/datum/shuttle/autodock/overmap/get_location_name() + if(moving_status == SHUTTLE_INTRANSIT) + return "In transit" + return "[waypoint_sector(current_location)] - [current_location]" + +/datum/shuttle/autodock/overmap/get_destination_name() + if(!next_location) + return "None" + return "[waypoint_sector(next_location)] - [next_location]" + +/datum/shuttle/autodock/overmap/proc/try_consume_fuel() //returns 1 if sucessful, returns 0 if error (like insufficient fuel) + if(!fuel_consumption) + return 1 //shuttles with zero fuel consumption are magic and can always launch + if(!fuel_ports.len) + return 0 //Nowhere to get fuel from + var/list/obj/item/tank/fuel_tanks = list() + for(var/obj/structure/FP in fuel_ports) //loop through fuel ports and assemble list of all fuel tanks + var/obj/item/tank/FT = locate() in FP + if(FT) + fuel_tanks += FT + if(!fuel_tanks.len) + return 0 //can't launch if you have no fuel TANKS in the ports + var/total_flammable_gas_moles = 0 + for(var/obj/item/tank/FT in fuel_tanks) + total_flammable_gas_moles += FT.air_contents.get_by_flag(XGM_GAS_FUEL) + if(total_flammable_gas_moles < fuel_consumption) //not enough fuel + return 0 + // We are going to succeed if we got to here, so start consuming that fuel + var/fuel_to_consume = fuel_consumption + for(var/obj/item/tank/FT in fuel_tanks) //loop through tanks, consume their fuel one by one + var/fuel_available = FT.air_contents.get_by_flag(XGM_GAS_FUEL) + if(!fuel_available) // Didn't even have fuel. + continue + if(fuel_available >= fuel_to_consume) + FT.remove_air_by_flag(XGM_GAS_FUEL, fuel_to_consume) + return 1 //ALL REQUIRED FUEL HAS BEEN CONSUMED, GO FOR LAUNCH! + else //this tank doesn't have enough to launch shuttle by itself, so remove all its fuel, then continue loop + fuel_to_consume -= fuel_available + FT.remove_air_by_flag(XGM_GAS_FUEL, fuel_available) + +/obj/structure/fuel_port + name = "fuel port" + desc = "The fuel input port of the shuttle. Holds one fuel tank. Use a crowbar to open and close it." + icon = 'icons/turf/shuttle.dmi' + icon_state = "fuel_port" + density = 0 + anchored = 1 + var/icon_closed = "fuel_port" + var/icon_empty = "fuel_port_empty" + var/icon_full = "fuel_port_full" + var/opened = 0 + var/parent_shuttle + +/obj/structure/fuel_port/Initialize() + . = ..() + new /obj/item/tank/phoron(src) + +/obj/structure/fuel_port/attack_hand(mob/user) + if(!opened) + to_chat(user, SPAN_WARNING("\The [src] is secured tightly. You'll need to pry it open with a crowbar.")) + return + else if(contents.len > 0) + user.put_in_hands(contents[1]) + update_icon() + +/obj/structure/fuel_port/update_icon() + . = ..() + if(opened) + if(contents.len > 0) + icon_state = icon_full + else + icon_state = icon_empty + else + icon_state = icon_closed + +/obj/structure/fuel_port/attackby(obj/item/W, mob/user) + if(iscrowbar(W)) + if(opened) + to_chat(user, SPAN_NOTICE("You close \the [src].")) + playsound(src.loc, 'sound/effects/closet_close.ogg', 25, 0, -3) + opened = 0 + else + to_chat(user, SPAN_NOTICE("You pry \the [src] open.")) + playsound(src.loc, 'sound/effects/closet_open.ogg', 15, 1, -3) + opened = 1 + else if(istype(W,/obj/item/tank)) + if(!opened) + to_chat(user, SPAN_NOTICE("\The [src] isn't open!")) + return + if(contents.len == 0) + user.unEquip(W, src) + update_icon() + +// Walls hide stuff inside them, but we want to be visible. +/obj/structure/fuel_port/hide() + return + +#undef waypoint_sector \ No newline at end of file diff --git a/code/modules/overmap/sectors.dm b/code/modules/overmap/sectors.dm new file mode 100644 index 00000000000..e09d56cf5e2 --- /dev/null +++ b/code/modules/overmap/sectors.dm @@ -0,0 +1,133 @@ +//=================================================================================== +//Overmap object representing zlevel(s) +//=================================================================================== +var/global/area/overmap/map_overmap // Global object used to locate the overmap area. + +/obj/effect/overmap/visitable + name = "map object" + scannable = TRUE + + var/list/map_z = list() + + var/list/initial_generic_waypoints //store landmark_tag of landmarks that should be added to the actual lists below on init. + var/list/initial_restricted_waypoints //For use with non-automatic landmarks (automatic ones add themselves). + + var/list/generic_waypoints = list() //waypoints that any shuttle can use + var/list/restricted_waypoints = list() //waypoints for specific shuttles + + var/start_x //Coordinates for self placing + var/start_y //will use random values if unset + + var/base = 0 //starting sector, counts as station_levels + var/in_space = 1 //can be accessed via lucky EVA + + var/has_distress_beacon + +/obj/effect/overmap/visitable/Initialize() + . = ..() + if(. == INITIALIZE_HINT_QDEL) + return + + find_z_levels() // This populates map_z and assigns z levels to the ship. + register_z_levels() // This makes external calls to update global z level information. + + if(!current_map.overmap_z) + build_overmap() + + start_x = start_x || rand(OVERMAP_EDGE, current_map.overmap_size - OVERMAP_EDGE) + start_y = start_y || rand(OVERMAP_EDGE, current_map.overmap_size - OVERMAP_EDGE) + + forceMove(locate(start_x, start_y, current_map.overmap_z)) + + testing("Located sector \"[name]\" at [start_x],[start_y], containing Z [english_list(map_z)]") + + LAZYADD(SSshuttle.sectors_to_initialize, src) //Queued for further init. Will populate the waypoint lists; waypoints not spawned yet will be added in as they spawn. + SSshuttle.clear_init_queue() + +//This is called later in the init order by SSshuttle to populate sector objects. Importantly for subtypes, shuttles will be created by then. +/obj/effect/overmap/visitable/proc/populate_sector_objects() + +/obj/effect/overmap/visitable/proc/get_areas() + return get_filtered_areas(list(/proc/area_belongs_to_zlevels = map_z)) + +/obj/effect/overmap/visitable/proc/find_z_levels() + map_z = GetConnectedZlevels(z) + +/obj/effect/overmap/visitable/proc/register_z_levels() + for(var/zlevel in map_z) + map_sectors["[zlevel]"] = src + + current_map.player_levels |= map_z + if(!in_space) + current_map.sealed_levels |= map_z + if(base) + current_map.station_levels |= map_z + current_map.contact_levels |= map_z + current_map.map_levels |= map_z + +//Helper for init. +/obj/effect/overmap/visitable/proc/check_ownership(obj/object) + if((object.z in map_z) && !(get_area(object) in SSshuttle.shuttle_areas)) + return 1 + +//If shuttle_name is false, will add to generic waypoints; otherwise will add to restricted. Does not do checks. +/obj/effect/overmap/visitable/proc/add_landmark(obj/effect/shuttle_landmark/landmark, shuttle_name) + landmark.sector_set(src, shuttle_name) + if(shuttle_name) + LAZYADD(restricted_waypoints[shuttle_name], landmark) + else + generic_waypoints += landmark + +/obj/effect/overmap/visitable/proc/remove_landmark(obj/effect/shuttle_landmark/landmark, shuttle_name) + if(shuttle_name) + var/list/shuttles = restricted_waypoints[shuttle_name] + LAZYREMOVE(shuttles, landmark) + else + generic_waypoints -= landmark + +/obj/effect/overmap/visitable/proc/get_waypoints(var/shuttle_name) + . = list() + for(var/obj/effect/overmap/visitable/contained in src) + . += contained.get_waypoints(shuttle_name) + for(var/thing in generic_waypoints) + .[thing] = name + if(shuttle_name in restricted_waypoints) + for(var/thing in restricted_waypoints[shuttle_name]) + .[thing] = name + +/obj/effect/overmap/visitable/proc/generate_skybox() + return + +/obj/effect/overmap/visitable/sector + name = "generic sector" + desc = "Sector with some stuff in it." + icon_state = "sector" + anchored = 1 + +// Because of the way these are spawned, they will potentially have their invisibility adjusted by the turfs they are mapped on +// prior to being moved to the overmap. This blocks that. Use set_invisibility to adjust invisibility as needed instead. +/obj/effect/overmap/visitable/sector/hide() + +/proc/build_overmap() + if(!current_map.use_overmap) + return 1 + + testing("Building overmap...") + world.maxz++ + current_map.overmap_z = world.maxz + + testing("Putting overmap on [current_map.overmap_z]") + var/area/overmap/A = new + global.map_overmap = A + for (var/square in block(locate(1,1,current_map.overmap_z), locate(current_map.overmap_size,current_map.overmap_size,current_map.overmap_z))) + var/turf/T = square + if(T.x == current_map.overmap_size || T.y == current_map.overmap_size) + T = T.ChangeTurf(/turf/unsimulated/map/edge) + else + T = T.ChangeTurf(/turf/unsimulated/map) + ChangeArea(T, A) + + current_map.sealed_levels |= current_map.overmap_z + + testing("Overmap build complete.") + return 1 diff --git a/code/modules/overmap/ships/computers/engine_control.dm b/code/modules/overmap/ships/computers/engine_control.dm new file mode 100644 index 00000000000..bf0dc4a417a --- /dev/null +++ b/code/modules/overmap/ships/computers/engine_control.dm @@ -0,0 +1,94 @@ +//Engine control and monitoring console + +/obj/machinery/computer/ship/engines + name = "engine control console" + icon_screen = "enginecontrol" + var/display_state = "status" + +/obj/machinery/computer/ship/engines/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1) + if(!linked) + display_reconnect_dialog(user, "ship control systems") + return + + var/data[0] + data["state"] = display_state + data["global_state"] = linked.engines_state + data["global_limit"] = round(linked.thrust_limit*100) + var/total_thrust = 0 + + var/list/enginfo[0] + for(var/datum/ship_engine/E in linked.engines) + var/list/rdata[0] + rdata["eng_type"] = E.name + rdata["eng_on"] = E.is_on() + rdata["eng_thrust"] = E.get_thrust() + rdata["eng_thrust_limiter"] = round(E.get_thrust_limit()*100) + rdata["eng_status"] = E.get_status() + rdata["eng_reference"] = "\ref[E]" + total_thrust += E.get_thrust() + enginfo.Add(list(rdata)) + + data["engines_info"] = enginfo + data["total_thrust"] = total_thrust + + ui = SSnanoui.try_update_ui(user, src, ui_key, ui, data, force_open) + if (!ui) + ui = new(user, src, ui_key, "engines_control.tmpl", "[linked.name] Engines Control", 390, 530) + ui.set_initial_data(data) + ui.open() + ui.set_auto_update(1) + +/obj/machinery/computer/ship/engines/Topic(href, href_list) + if(..()) + return TOPIC_HANDLED + + if(href_list["state"]) + display_state = href_list["state"] + return TOPIC_REFRESH + + if(href_list["global_toggle"]) + linked.engines_state = !linked.engines_state + for(var/datum/ship_engine/E in linked.engines) + if(linked.engines_state == !E.is_on()) + E.toggle() + return TOPIC_REFRESH + + if(href_list["set_global_limit"]) + var/newlim = input("Input new thrust limit (0..100%)", "Thrust limit", linked.thrust_limit*100) as num + if(!CanInteract(usr, physical_state)) + return TOPIC_NOACTION + linked.thrust_limit = Clamp(newlim/100, 0, 1) + for(var/datum/ship_engine/E in linked.engines) + E.set_thrust_limit(linked.thrust_limit) + return TOPIC_REFRESH + + if(href_list["global_limit"]) + linked.thrust_limit = Clamp(linked.thrust_limit + text2num(href_list["global_limit"]), 0, 1) + for(var/datum/ship_engine/E in linked.engines) + E.set_thrust_limit(linked.thrust_limit) + return TOPIC_REFRESH + + if(href_list["engine"]) + if(href_list["set_limit"]) + var/datum/ship_engine/E = locate(href_list["engine"]) + var/newlim = input("Input new thrust limit (0..100)", "Thrust limit", E.get_thrust_limit()) as num + if(!CanInteract(usr, physical_state)) + return + var/limit = Clamp(newlim/100, 0, 1) + if(istype(E)) + E.set_thrust_limit(limit) + return TOPIC_REFRESH + if(href_list["limit"]) + var/datum/ship_engine/E = locate(href_list["engine"]) + var/limit = Clamp(E.get_thrust_limit() + text2num(href_list["limit"]), 0, 1) + if(istype(E)) + E.set_thrust_limit(limit) + return TOPIC_REFRESH + + if(href_list["toggle"]) + var/datum/ship_engine/E = locate(href_list["engine"]) + if(istype(E)) + E.toggle() + return TOPIC_REFRESH + return TOPIC_REFRESH + return TOPIC_NOACTION \ No newline at end of file diff --git a/code/modules/overmap/ships/computers/helm.dm b/code/modules/overmap/ships/computers/helm.dm new file mode 100644 index 00000000000..e1e86b2554f --- /dev/null +++ b/code/modules/overmap/ships/computers/helm.dm @@ -0,0 +1,263 @@ +/datum/computer_file/data/waypoint + var/list/fields = list() + +/obj/machinery/computer/ship/helm + name = "helm control console" + icon_screen = "helm" + light_color = "#7faaff" + var/autopilot = 0 + var/list/known_sectors = list() + var/dx //desitnation + var/dy //coordinates + var/speedlimit = 1/(20 SECONDS) //top speed for autopilot, 5 + var/accellimit = 0.001 //manual limiter for acceleration + +/obj/machinery/computer/ship/helm/Initialize() + . = ..() + get_known_sectors() + +/obj/machinery/computer/ship/helm/proc/get_known_sectors() + var/area/overmap/map = global.map_overmap + if(!map) + return + for(var/obj/effect/overmap/visitable/sector/S in map) + if (S.known) + var/datum/computer_file/data/waypoint/R = new() + R.fields["name"] = S.name + R.fields["x"] = S.x + R.fields["y"] = S.y + known_sectors[S.name] = R + +/obj/machinery/computer/ship/helm/process() + ..() + if (autopilot && dx && dy) + var/turf/T = locate(dx,dy,current_map.overmap_z) + if(linked.loc == T) + if(linked.is_still()) + autopilot = 0 + else + linked.decelerate() + else + var/brake_path = linked.get_brake_path() + var/direction = get_dir(linked.loc, T) + var/acceleration = min(linked.get_acceleration(), accellimit) + var/speed = linked.get_speed() + var/heading = linked.get_heading() + + // Destination is current grid or speedlimit is exceeded + if ((get_dist(linked.loc, T) <= brake_path) || speed > speedlimit) + linked.decelerate() + // Heading does not match direction + else if (heading & ~direction) + linked.accelerate(turn(heading & ~direction, 180), accellimit) + // All other cases, move toward direction + else if (speed + acceleration <= speedlimit) + linked.accelerate(direction, accellimit) + return + +/obj/machinery/computer/ship/helm/relaymove(var/mob/user, direction) + if(viewing_overmap(user) && linked) + linked.relaymove(user, direction, accellimit) + return 1 + +/obj/machinery/computer/ship/helm/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1) + var/data[0] + + if(!linked) + display_reconnect_dialog(user, "helm") + else + var/turf/T = get_turf(linked) + var/obj/effect/overmap/visitable/sector/current_sector = locate() in T + + data["sector"] = current_sector ? current_sector.name : "Deep Space" + data["sector_info"] = current_sector ? current_sector.desc : "Not Available" + data["landed"] = linked.get_landed_info() + data["s_x"] = linked.x + data["s_y"] = linked.y + data["dest"] = dy && dx + data["d_x"] = dx + data["d_y"] = dy + data["speedlimit"] = speedlimit ? speedlimit*1000 : "Halted" + data["accel"] = min(round(linked.get_acceleration()*1000, 0.01),accellimit*1000) + data["heading"] = linked.get_heading() ? dir2angle(linked.get_heading()) : 0 + data["autopilot"] = autopilot + data["manual_control"] = viewing_overmap(user) + data["canburn"] = linked.can_burn() + data["accellimit"] = accellimit*1000 + + var/speed = round(linked.get_speed()*1000, 0.01) + if(linked.get_speed() < SHIP_SPEED_SLOW) + speed = "[speed]" + if(linked.get_speed() > SHIP_SPEED_FAST) + speed = "[speed]" + data["speed"] = speed + + if(linked.get_speed()) + data["ETAnext"] = "[round(linked.ETA()/10)] seconds" + else + data["ETAnext"] = "N/A" + + var/list/locations[0] + for (var/key in known_sectors) + var/datum/computer_file/data/waypoint/R = known_sectors[key] + var/list/rdata[0] + rdata["name"] = R.fields["name"] + rdata["x"] = R.fields["x"] + rdata["y"] = R.fields["y"] + rdata["reference"] = "\ref[R]" + locations.Add(list(rdata)) + + data["locations"] = locations + + ui = SSnanoui.try_update_ui(user, src, ui_key, ui, data, force_open) + if (!ui) + ui = new(user, src, ui_key, "helm.tmpl", "[linked.name] Helm Control", 565, 545) + ui.set_initial_data(data) + ui.open() + ui.set_auto_update(1) + +/obj/machinery/computer/ship/helm/Topic(href, href_list) + if(..()) + return TOPIC_HANDLED + + if(!linked) + return TOPIC_HANDLED + + if (href_list["add"]) + var/datum/computer_file/data/waypoint/R = new() + var/sec_name = input("Input naviation entry name", "New navigation entry", "Sector #[known_sectors.len]") as text + if(!CanInteract(usr, physical_state)) + return TOPIC_NOACTION + if(!sec_name) + sec_name = "Sector #[known_sectors.len]" + R.fields["name"] = sec_name + if(sec_name in known_sectors) + to_chat(usr, "Sector with that name already exists, please input a different name.") + return TOPIC_REFRESH + switch(href_list["add"]) + if("current") + R.fields["x"] = linked.x + R.fields["y"] = linked.y + if("new") + var/newx = input("Input new entry x coordinate", "Coordinate input", linked.x) as num + if(!CanInteract(usr, physical_state)) + return TOPIC_REFRESH + var/newy = input("Input new entry y coordinate", "Coordinate input", linked.y) as num + if(!CanInteract(usr, physical_state)) + return TOPIC_NOACTION + R.fields["x"] = Clamp(newx, 1, world.maxx) + R.fields["y"] = Clamp(newy, 1, world.maxy) + known_sectors[sec_name] = R + + if (href_list["remove"]) + var/datum/computer_file/data/waypoint/R = locate(href_list["remove"]) + if(R) + known_sectors.Remove(R.fields["name"]) + qdel(R) + + if (href_list["setx"]) + var/newx = input("Input new destiniation x coordinate", "Coordinate input", dx) as num|null + if(!CanInteract(usr, physical_state)) + return + if (newx) + dx = Clamp(newx, 1, world.maxx) + + if (href_list["sety"]) + var/newy = input("Input new destiniation y coordinate", "Coordinate input", dy) as num|null + if(!CanInteract(usr, physical_state)) + return + if (newy) + dy = Clamp(newy, 1, world.maxy) + + if (href_list["x"] && href_list["y"]) + dx = text2num(href_list["x"]) + dy = text2num(href_list["y"]) + + if (href_list["reset"]) + dx = 0 + dy = 0 + + if (href_list["speedlimit"]) + var/newlimit = input("Input new speed limit for autopilot (0 to brake)", "Autopilot speed limit", speedlimit*1000) as num|null + if(newlimit) + speedlimit = Clamp(newlimit/1000, 0, 100) + if (href_list["accellimit"]) + var/newlimit = input("Input new acceleration limit", "Acceleration limit", accellimit*1000) as num|null + if(newlimit) + accellimit = max(newlimit/1000, 0) + + if (href_list["move"]) + var/ndir = text2num(href_list["move"]) + linked.relaymove(usr, ndir, accellimit) + + if (href_list["brake"]) + linked.decelerate() + + if (href_list["apilot"]) + autopilot = !autopilot + + if (href_list["manual"]) + viewing_overmap(usr) ? unlook(usr) : look(usr) + + add_fingerprint(usr) + updateUsrDialog() + + +/obj/machinery/computer/ship/navigation + name = "navigation console" + icon_screen = "nav" + +/obj/machinery/computer/ship/navigation/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1) + if(!linked) + display_reconnect_dialog(user, "Navigation") + return + + var/data[0] + + + var/turf/T = get_turf(linked) + var/obj/effect/overmap/visitable/sector/current_sector = locate() in T + + data["sector"] = current_sector ? current_sector.name : "Deep Space" + data["sector_info"] = current_sector ? current_sector.desc : "Not Available" + data["s_x"] = linked.x + data["s_y"] = linked.y + data["speed"] = round(linked.get_speed()*1000, 0.01) + data["accel"] = round(linked.get_acceleration()*1000, 0.01) + data["heading"] = linked.get_heading() ? dir2angle(linked.get_heading()) : 0 + data["viewing"] = viewing_overmap(user) + + if(linked.get_speed()) + data["ETAnext"] = "[round(linked.ETA()/10)] seconds" + else + data["ETAnext"] = "N/A" + + ui = SSnanoui.try_update_ui(user, src, ui_key, ui, data, force_open) + if (!ui) + ui = new(user, src, ui_key, "nav.tmpl", "[linked.name] Navigation Screen", 380, 530) + ui.set_initial_data(data) + ui.open() + ui.set_auto_update(1) + +/obj/machinery/computer/ship/navigation/Topic(href, href_list) + if(..()) + return TOPIC_HANDLED + + if (!linked) + return TOPIC_NOACTION + + if (href_list["viewing"]) + viewing_overmap(usr) ? unlook(usr) : look(usr) + return TOPIC_REFRESH + +/obj/machinery/computer/ship/navigation/telescreen //little hacky but it's only used on one ship so it should be okay + icon_state = "tele_nav" + density = 0 + +/obj/machinery/computer/ship/navigation/telescreen/update_icon() + if(stat & (NOPOWER|BROKEN)) + icon_state = "tele_off" + set_light(0) + else + icon_state = "tele_nav" + set_light(2, 2, "#33FAAA") diff --git a/code/modules/overmap/ships/computers/sensors.dm b/code/modules/overmap/ships/computers/sensors.dm new file mode 100644 index 00000000000..d66b49c2f81 --- /dev/null +++ b/code/modules/overmap/ships/computers/sensors.dm @@ -0,0 +1,220 @@ +/obj/machinery/computer/ship/sensors + name = "sensors console" + icon_screen = "sensors" + light_color = "#77fff8" + extra_view = 4 + var/obj/machinery/shipsensors/sensors + +/obj/machinery/computer/ship/sensors/attempt_hook_up(obj/effect/overmap/visitable/ship/sector) + . = ..() + if(!.) + return + find_sensors() + +/obj/machinery/computer/ship/sensors/proc/find_sensors() + if(!linked) + return + for(var/obj/machinery/shipsensors/S in SSmachinery.all_machines) + if(linked.check_ownership(S)) + sensors = S + break + +/obj/machinery/computer/ship/sensors/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1) + if(!linked) + display_reconnect_dialog(user, "sensors") + return + + var/data[0] + + data["viewing"] = viewing_overmap(user) + if(sensors) + data["on"] = sensors.use_power + data["range"] = sensors.range + data["health"] = sensors.health + data["max_health"] = sensors.max_health + data["heat"] = sensors.heat + data["critical_heat"] = sensors.critical_heat + if(sensors.health == 0) + data["status"] = "DESTROYED" + else if(!sensors.powered()) + data["status"] = "NO POWER" + else if(!sensors.in_vacuum()) + data["status"] = "VACUUM SEAL BROKEN" + else + data["status"] = "OK" + var/list/contacts = list() + for(var/obj/effect/overmap/O in view(7,linked)) + if(linked == O) + continue + if(!O.scannable) + continue + var/bearing = round(90 - Atan2(O.x - linked.x, O.y - linked.y),5) + if(bearing < 0) + bearing += 360 + contacts.Add(list(list("name"=O.name, "ref"="\ref[O]", "bearing"=bearing))) + if(contacts.len) + data["contacts"] = contacts + else + data["status"] = "MISSING" + data["range"] = "N/A" + data["on"] = 0 + + ui = SSnanoui.try_update_ui(user, src, ui_key, ui, data, force_open) + if (!ui) + ui = new(user, src, ui_key, "shipsensors.tmpl", "[linked.name] Sensors Control", 420, 530, src) + ui.set_initial_data(data) + ui.open() + ui.set_auto_update(1) + +/obj/machinery/computer/ship/sensors/Topic(href, href_list) + if (!linked) + return TOPIC_NOACTION + + if (href_list["viewing"]) + if(usr && !isAI(usr)) + viewing_overmap(usr) ? unlook(usr) : look(usr) + return TOPIC_REFRESH + + if (href_list["link"]) + find_sensors() + return TOPIC_REFRESH + + if(sensors) + if (href_list["range"]) + var/nrange = input("Set new sensors range", "Sensor range", sensors.range) as num|null + if(!CanInteract(usr, physical_state)) + return TOPIC_NOACTION + if (nrange) + sensors.set_range(Clamp(nrange, 1, world.view)) + return TOPIC_REFRESH + if (href_list["toggle"]) + sensors.toggle() + return TOPIC_REFRESH + + if (href_list["scan"]) + var/obj/effect/overmap/O = locate(href_list["scan"]) + if(istype(O) && !QDELETED(O) && (O in view(7,linked))) + playsound(loc, "sound/machines/dotprinter.ogg", 30, 1) + new/obj/item/paper/(get_turf(src), O.get_scan_data(usr), "paper (Sensor Scan - [O])") + return TOPIC_HANDLED + +/obj/machinery/computer/ship/sensors/process() + ..() + if(!linked) + return + if(sensors && sensors.use_power && sensors.powered()) + var/sensor_range = round(sensors.range*1.5) + 1 + linked.set_light(1, sensor_range, sensor_range+1) + else + linked.set_light(0) + +/obj/machinery/shipsensors + name = "sensors suite" + desc = "Long range gravity scanner with various other sensors, used to detect irregularities in surrounding space. Can only run in vacuum to protect delicate quantum BS elements." + icon = 'icons/obj/stationobjs.dmi' + icon_state = "sensors" + anchored = 1 + var/max_health = 200 + var/health = 200 + var/critical_heat = 50 // sparks and takes damage when active & above this heat + var/heat_reduction = 1.5 // mitigates this much heat per tick + var/heat = 0 + var/range = 1 + idle_power_usage = 5000 + +/obj/machinery/shipsensors/attackby(obj/item/W, mob/user) + var/damage = max_health - health + if(damage && iswelder(W)) + + var/obj/item/weldingtool/WT = W + + if(!WT.isOn()) + return + + if(WT.remove_fuel(0,user)) + to_chat(user, "You start repairing the damage to [src].") + playsound(src, 'sound/items/Welder.ogg', 100, 1) + if(do_after(user, max(5, damage / 5), src) && WT && WT.isOn()) + to_chat(user, "You finish repairing the damage to [src].") + take_damage(-damage) + else + to_chat(user, "You need more welding fuel to complete this task.") + return + return + ..() + +/obj/machinery/shipsensors/proc/in_vacuum() + var/turf/T=get_turf(src) + if(istype(T)) + var/datum/gas_mixture/environment = T.return_air() + if(environment && environment.return_pressure() > MINIMUM_PRESSURE_DIFFERENCE_TO_SUSPEND) + return 0 + return 1 + +/obj/machinery/shipsensors/update_icon() + if(use_power) + icon_state = "sensors" + else + icon_state = "sensors_off" + +/obj/machinery/shipsensors/examine(mob/user) + . = ..() + if(health <= 0) + to_chat(user, "\The [src] is wrecked.") + else if(health < max_health * 0.25) + to_chat(user, "\The [src] looks like it's about to break!") + else if(health < max_health * 0.5) + to_chat(user, "\The [src] looks seriously damaged!") + else if(health < max_health * 0.75) + to_chat(user, "\The [src] shows signs of damage!") + +/obj/machinery/shipsensors/bullet_act(var/obj/item/projectile/Proj) + take_damage(Proj.get_structure_damage()) + ..() + +/obj/machinery/shipsensors/proc/toggle() + if(!use_power && (health == 0 || !in_vacuum())) + return // No turning on if broken or misplaced. + if(!use_power) //need some juice to kickstart + use_power(idle_power_usage*5) + update_use_power(!use_power) + queue_icon_update() + +/obj/machinery/shipsensors/process() + if(use_power) //can't run in non-vacuum + if(!in_vacuum()) + toggle() + if(heat > critical_heat) + src.visible_message("\The [src] violently spews out sparks!") + spark(src, 3, alldirs) + + take_damage(rand(10,50)) + toggle() + heat += idle_power_usage/15000 + + if (heat > 0) + heat = max(0, heat - heat_reduction) + +/obj/machinery/shipsensors/power_change() + . = ..() + if(use_power && !powered()) + toggle() + +/obj/machinery/shipsensors/proc/set_range(nrange) + range = nrange + active_power_usage = (1500 * (range**2)) //Exponential increase, also affects speed of overheating + +/obj/machinery/shipsensors/emp_act(severity) + if(!use_power) + return + take_damage(20/severity) + toggle() + +/obj/machinery/shipsensors/proc/take_damage(value) + health = min(max(health - value, 0),max_health) + if(use_power && health == 0) + toggle() + +/obj/machinery/shipsensors/weak + heat_reduction = 0.2 + desc = "Miniturized gravity scanner with various other sensors, used to detect irregularities in surrounding space. Can only run in vacuum to protect delicate quantum BS elements." \ No newline at end of file diff --git a/code/modules/overmap/ships/computers/ship.dm b/code/modules/overmap/ships/computers/ship.dm new file mode 100644 index 00000000000..f6f85171463 --- /dev/null +++ b/code/modules/overmap/ships/computers/ship.dm @@ -0,0 +1,97 @@ +/* +While these computers can be placed anywhere, they will only function if placed on either a non-space, non-shuttle turf +with an /obj/effect/overmap/visitable/ship present elsewhere on that z level, or else placed in a shuttle area with an /obj/effect/overmap/visitable/ship +somewhere on that shuttle. Subtypes of these can be then used to perform ship overmap movement functions. +*/ +/obj/machinery/computer/ship + var/obj/effect/overmap/visitable/ship/linked + var/list/viewers // Weakrefs to mobs in direct-view mode. + var/extra_view = 0 // how much the view is increased by when the mob is in overmap mode. + +// A late init operation called in SSshuttle, used to attach the thing to the right ship. +/obj/machinery/computer/ship/proc/attempt_hook_up(obj/effect/overmap/visitable/ship/sector) + if(!istype(sector)) + return + if(sector.check_ownership(src)) + linked = sector + return 1 + +/obj/machinery/computer/ship/proc/sync_linked() + var/obj/effect/overmap/visitable/ship/sector = map_sectors["[z]"] + if(!sector) + return + return attempt_hook_up_recursive(sector) + +/obj/machinery/computer/ship/proc/attempt_hook_up_recursive(obj/effect/overmap/visitable/ship/sector) + if(attempt_hook_up(sector)) + return sector + for(var/obj/effect/overmap/visitable/ship/candidate in sector) + if((. = .(candidate))) + return + +/obj/machinery/computer/ship/proc/display_reconnect_dialog(var/mob/user, var/flavor) + var/datum/browser/popup = new (user, "[src]", "[src]") + popup.set_content("
Error
Unable to connect to [flavor].
Reconnect
") + popup.open() + +/obj/machinery/computer/ship/attack_hand(mob/user) + if(use_check_and_message(user)) + return + + user.set_machine(src) + ui_interact(user) + +/obj/machinery/computer/ship/Topic(href, href_list) + if(href_list["sync"]) + sync_linked() + return TOPIC_REFRESH + if(href_list["close"]) + unlook(usr) + usr.unset_machine() + return TOPIC_HANDLED + return TOPIC_NOACTION + +// Management of mob view displacement. look to shift view to the ship on the overmap; unlook to shift back. + +/obj/machinery/computer/ship/proc/look(var/mob/user) + if(linked) + user.reset_view(linked) + if(user.client) + user.client.view = world.view + extra_view + moved_event.register(user, src, /obj/machinery/computer/ship/proc/unlook) + LAZYDISTINCTADD(viewers, WEAKREF(user)) + +/obj/machinery/computer/ship/proc/unlook(var/mob/user) + user.reset_view() + if(user.client) + user.client.view = world.view + moved_event.unregister(user, src, /obj/machinery/computer/ship/proc/unlook) + LAZYREMOVE(viewers, WEAKREF(user)) + +/obj/machinery/computer/ship/proc/viewing_overmap(mob/user) + return (WEAKREF(user) in viewers) + +/obj/machinery/computer/ship/CouldNotUseTopic(mob/user) + . = ..() + unlook(user) + +/obj/machinery/computer/ship/CouldUseTopic(mob/user) + . = ..() + if(viewing_overmap(user)) + look(user) + +/obj/machinery/computer/ship/check_eye(var/mob/user) + if (!get_dist(user, src) > 1 || user.blinded || !linked ) + unlook(user) + return -1 + else + return 0 + +/obj/machinery/computer/ship/sensors/Destroy() + sensors = null + if(LAZYLEN(viewers)) + for(var/datum/weakref/W in viewers) + var/M = W.resolve() + if(M) + unlook(M) + . = ..() \ No newline at end of file diff --git a/code/modules/overmap/ships/computers/shuttle.dm b/code/modules/overmap/ships/computers/shuttle.dm new file mode 100644 index 00000000000..c8a58d1d96f --- /dev/null +++ b/code/modules/overmap/ships/computers/shuttle.dm @@ -0,0 +1,42 @@ +//Shuttle controller computer for shuttles going between sectors +/obj/machinery/computer/shuttle_control/explore + name = "general shuttle control console" + ui_template = "shuttle_control_console_exploration.tmpl" + +/obj/machinery/computer/shuttle_control/explore/get_ui_data(var/datum/shuttle/autodock/overmap/shuttle) + . = ..() + if(istype(shuttle)) + var/total_gas = 0 + for(var/obj/structure/fuel_port/FP in shuttle.fuel_ports) //loop through fuel ports + var/obj/item/tank/fuel_tank = locate() in FP + if(fuel_tank) + total_gas += fuel_tank.air_contents.total_moles + + var/fuel_span = "good" + if(total_gas < shuttle.fuel_consumption * 2) + fuel_span = "bad" + + . += list( + "destination_name" = shuttle.get_destination_name(), + "can_pick" = shuttle.moving_status == SHUTTLE_IDLE, + "fuel_usage" = shuttle.fuel_consumption * 100, + "remaining_fuel" = round(total_gas, 0.01) * 100, + "fuel_span" = fuel_span + ) + +/obj/machinery/computer/shuttle_control/explore/handle_topic_href(var/datum/shuttle/autodock/overmap/shuttle, var/list/href_list) + . = ..() + if(. != null) + return + + if(href_list["pick"]) + var/list/possible_d = shuttle.get_possible_destinations() + var/D + if(possible_d.len) + D = input("Choose shuttle destination", "Shuttle Destination") as null|anything in possible_d + else + to_chat(usr,"No valid landing sites in range.") + possible_d = shuttle.get_possible_destinations() + if(CanInteract(usr, physical_state) && (D in possible_d)) + shuttle.set_destination(possible_d[D]) + return TOPIC_REFRESH diff --git a/code/modules/overmap/ships/engines/engine.dm b/code/modules/overmap/ships/engines/engine.dm new file mode 100644 index 00000000000..59e6edee961 --- /dev/null +++ b/code/modules/overmap/ships/engines/engine.dm @@ -0,0 +1,45 @@ +//Engine component object + +var/list/ship_engines = list() +/datum/ship_engine + var/name = "ship engine" + var/obj/machinery/holder //actual engine object + +/datum/ship_engine/New(var/obj/machinery/_holder) + ..() + holder = _holder + ship_engines += src + +/datum/ship_engine/proc/can_burn() + return 0 + +//Tries to fire the engine. Returns thrust +/datum/ship_engine/proc/burn() + return 0 + +//Returns status string for this engine +/datum/ship_engine/proc/get_status() + return "All systems nominal" + +/datum/ship_engine/proc/get_thrust() + return 1 + +//Sets thrust limiter, a number between 0 and 1 +/datum/ship_engine/proc/set_thrust_limit(var/new_limit) + return 1 + +/datum/ship_engine/proc/get_thrust_limit() + return 1 + +/datum/ship_engine/proc/is_on() + return 1 + +/datum/ship_engine/proc/toggle() + return 1 + +/datum/ship_engine/Destroy() + ship_engines -= src + for(var/obj/effect/overmap/visitable/ship/S in SSshuttle.ships) + S.engines -= src + holder = null + . = ..() \ No newline at end of file diff --git a/code/modules/overmap/ships/engines/gas_thruster.dm b/code/modules/overmap/ships/engines/gas_thruster.dm new file mode 100644 index 00000000000..1a9c915d02e --- /dev/null +++ b/code/modules/overmap/ships/engines/gas_thruster.dm @@ -0,0 +1,211 @@ +//Gas nozzle engine +/datum/ship_engine/gas_thruster + name = "gas thruster" + var/obj/machinery/atmospherics/unary/engine/nozzle + +/datum/ship_engine/gas_thruster/New(var/obj/machinery/_holder) + ..() + nozzle = _holder + +/datum/ship_engine/gas_thruster/Destroy() + nozzle = null + . = ..() + +/datum/ship_engine/gas_thruster/get_status() + return nozzle.get_status() + +/datum/ship_engine/gas_thruster/get_thrust() + return nozzle.get_thrust() + +/datum/ship_engine/gas_thruster/burn() + return nozzle.burn() + +/datum/ship_engine/gas_thruster/set_thrust_limit(var/new_limit) + nozzle.thrust_limit = new_limit + +/datum/ship_engine/gas_thruster/get_thrust_limit() + return nozzle.thrust_limit + +/datum/ship_engine/gas_thruster/is_on() + if(nozzle.use_power && nozzle.operable()) + if(nozzle.next_on > world.time) + return -1 + else + return 1 + return 0 + +/datum/ship_engine/gas_thruster/toggle() + if(nozzle.use_power) + nozzle.update_use_power(0) + else + if(nozzle.blockage) + if(nozzle.check_blockage()) + return + nozzle.update_use_power(1) + if(nozzle.stat & NOPOWER)//try again + nozzle.power_change() + if(nozzle.is_on())//if everything is in working order, start booting! + nozzle.next_on = world.time + nozzle.boot_time + +/datum/ship_engine/gas_thruster/can_burn() + return nozzle.is_on() && nozzle.check_fuel() + +//Actual thermal nozzle engine object + +/obj/machinery/atmospherics/unary/engine + name = "rocket nozzle" + desc = "Simple rocket nozzle, expelling gas at hypersonic velocities to propell the ship." + icon = 'icons/obj/ship_engine.dmi' + icon_state = "nozzle" + opacity = 1 + density = 1 + atmos_canpass = CANPASS_NEVER + + use_power = 0 + power_channel = EQUIP + idle_power_usage = 21600 //6 Wh per tick for default 2 capacitor. Gives them a reason to turn it off, really to nerf backup battery + + var/datum/ship_engine/gas_thruster/controller + var/thrust_limit = 1 //Value between 1 and 0 to limit the resulting thrust + var/volume_per_burn = 15 //20 litres(with bin) + var/charge_per_burn = 36000 //10Wh for default 2 capacitor, chews through that battery power! Makes a trade off of fuel efficient vs energy efficient + var/boot_time = 35 + var/next_on + var/blockage + +/obj/machinery/atmospherics/unary/engine/CanPass(atom/movable/mover, turf/target, height=0, air_group=0) + return 0 + +/obj/machinery/atmospherics/unary/engine/atmos_init() + ..() + if(node) + return + + var/node_connect = dir + + for(var/obj/machinery/atmospherics/target in get_step(src,node_connect)) + if(target.initialize_directions & get_dir(target,src)) + if (check_connect_types(target,src)) + node = target + break + + update_icon() + update_underlays() + +/obj/machinery/atmospherics/unary/engine/Initialize() + . = ..() + controller = new(src) + update_nearby_tiles(need_rebuild=1) + + for(var/ship in SSshuttle.ships) + var/obj/effect/overmap/visitable/ship/S = ship + if(S.check_ownership(src)) + S.engines |= controller + if(dir != S.fore_dir) + stat |= BROKEN + break + +/obj/machinery/atmospherics/unary/engine/Destroy() + QDEL_NULL(controller) + update_nearby_tiles() + . = ..() + +/obj/machinery/atmospherics/unary/engine/proc/get_status() + . = list() + .+= "Location: [get_area(src)]." + if(stat & NOPOWER) + .+= "Insufficient power to operate." + if(!check_fuel()) + .+= "Insufficient fuel for a burn." + if(stat & BROKEN) + .+= "Inoperable engine configuration." + if(blockage) + .+= "Obstruction of airflow detected." + + .+= "Propellant total mass: [round(air_contents.get_mass(),0.01)] kg." + .+= "Propellant used per burn: [round(air_contents.get_mass() * volume_per_burn * thrust_limit / air_contents.volume,0.01)] kg." + .+= "Propellant pressure: [round(air_contents.return_pressure()/1000,0.1)] MPa." + . = jointext(.,"
") + +/obj/machinery/atmospherics/unary/engine/power_change() + . = ..() + if(stat & NOPOWER) + update_use_power(0) + +/obj/machinery/atmospherics/unary/engine/proc/is_on() + return use_power && operable() && (next_on < world.time) + +/obj/machinery/atmospherics/unary/engine/proc/check_fuel() + return air_contents.total_moles > 5 // minimum fuel usage is five moles, for EXTREMELY hot mix or super low pressure + +/obj/machinery/atmospherics/unary/engine/proc/get_thrust() + if(!is_on() || !check_fuel()) + return 0 + var/used_part = volume_per_burn * thrust_limit / air_contents.volume + . = calculate_thrust(air_contents, used_part) + return + +/obj/machinery/atmospherics/unary/engine/proc/check_blockage() + blockage = FALSE + var/exhaust_dir = reverse_direction(dir) + var/turf/A = get_step(src, exhaust_dir) + var/turf/B = A + while(isturf(A) && !(isspace(A) || isopenspace(A))) + if((B.c_airblock(A)) & AIR_BLOCKED) + blockage = TRUE + break + B = A + A = get_step(A, exhaust_dir) + return blockage + +/obj/machinery/atmospherics/unary/engine/proc/burn() + if(!is_on()) + return 0 + if(!check_fuel() || (0 < use_power(charge_per_burn)) || check_blockage()) + audible_message(src,"[src] coughs once and goes silent!") + update_use_power(0) + return 0 + + var/datum/gas_mixture/removed = air_contents.remove_ratio(volume_per_burn * thrust_limit / air_contents.volume) + if(!removed) + return 0 + . = calculate_thrust(removed) + playsound(loc, 'sound/machines/thruster.ogg', 100 * thrust_limit, 0, world.view * 4, 0.1, is_global = TRUE) + if(network) + network.update = 1 + + var/exhaust_dir = reverse_direction(dir) + var/turf/T = get_step(src,exhaust_dir) + if(T) + T.assume_air(removed) + new/obj/effect/engine_exhaust(T, exhaust_dir, air_contents.check_combustability() && air_contents.temperature >= PHORON_MINIMUM_BURN_TEMPERATURE) + +/obj/machinery/atmospherics/unary/engine/proc/calculate_thrust(datum/gas_mixture/propellant, used_part = 1) + return round(sqrt(propellant.get_mass() * used_part * sqrt(air_contents.return_pressure()/200)),0.1) + +//Exhaust effect +/obj/effect/engine_exhaust + name = "engine exhaust" + icon = 'icons/effects/effects.dmi' + icon_state = "smoke" + light_color = "#ed9200" + anchored = 1 + +/obj/effect/engine_exhaust/New(var/turf/nloc, var/ndir, var/flame) + ..(nloc) + if(flame) + icon_state = "exhaust" + nloc.hotspot_expose(1000,125) + set_light(0.5, 1, 4) + set_dir(ndir) + spawn(20) + qdel(src) + +/obj/item/circuitboard/unary_atmos/engine//why don't we move this elsewhere? + name = T_BOARD("gas thruster") + icon_state = "mcontroller" + build_path = /obj/machinery/atmospherics/unary/engine + origin_tech = list(TECH_POWER = 1, TECH_ENGINEERING = 2) + req_components = list( + /obj/item/stack/cable_coil = 30, + /obj/item/pipe = 2) diff --git a/code/modules/overmap/ships/landable.dm b/code/modules/overmap/ships/landable.dm new file mode 100644 index 00000000000..20b7ebf5948 --- /dev/null +++ b/code/modules/overmap/ships/landable.dm @@ -0,0 +1,163 @@ +// These come with shuttle functionality. Need to be assigned a (unique) shuttle datum name. +// Mapping location doesn't matter, so long as on a map loaded at the same time as the shuttle areas. +// Multiz shuttles currently not supported. Non-autodock shuttles currently not supported. + +/obj/effect/overmap/visitable/ship/landable + var/shuttle // Name of associated shuttle. Must be autodock. + var/obj/effect/shuttle_landmark/ship/landmark // Record our open space landmark for easy reference. + var/multiz = 0 // Index of multi-z levels, starts at 0 + var/status = SHIP_STATUS_LANDED + icon_state = "shuttle" + moving_state = "shuttle_moving" + +/obj/effect/overmap/visitable/ship/landable/can_burn() + if(status != SHIP_STATUS_OVERMAP) + return 0 + return ..() + +/obj/effect/overmap/visitable/ship/landable/burn() + if(status != SHIP_STATUS_OVERMAP) + return 0 + return ..() + +/obj/effect/overmap/visitable/ship/landable/check_ownership(obj/object) + var/datum/shuttle/shuttle_datum = SSshuttle.shuttles[shuttle] + if(!shuttle_datum) + return + var/list/areas = shuttle_datum.find_childfree_areas() + if(get_area(object) in areas) + return 1 + +// We autobuild our z levels. +/obj/effect/overmap/visitable/ship/landable/find_z_levels() + for(var/i = 0 to multiz) + world.maxz++ + map_z += world.maxz + + var/turf/center_loc = locate(round(world.maxx/2), round(world.maxy/2), world.maxz) + landmark = new (center_loc, shuttle) + add_landmark(landmark, shuttle) + + var/visitor_dir = fore_dir + for(var/landmark_name in list("FORE", "PORT", "AFT", "STARBOARD")) + var/turf/visitor_turf = get_ranged_target_turf(center_loc, visitor_dir, round(min(world.maxx/4, world.maxy/4))) + var/obj/effect/shuttle_landmark/visiting_shuttle/visitor_landmark = new (visitor_turf, landmark, landmark_name) + add_landmark(visitor_landmark) + visitor_dir = turn(visitor_dir, 90) + + if(multiz) + new /obj/effect/landmark/map_data(center_loc, (multiz + 1)) + +/obj/effect/overmap/visitable/ship/landable/get_areas() + var/datum/shuttle/shuttle_datum = SSshuttle.shuttles[shuttle] + if(!shuttle_datum) + return list() + return shuttle_datum.find_childfree_areas() + +/obj/effect/overmap/visitable/ship/landable/populate_sector_objects() + ..() + var/datum/shuttle/shuttle_datum = SSshuttle.shuttles[shuttle] + on_landing(landmark, shuttle_datum.current_location) // We "land" at round start to properly place ourselves on the overmap. + +/obj/effect/shuttle_landmark/ship + name = "Open Space" + landmark_tag = "ship" + flags = SLANDMARK_FLAG_AUTOSET | SLANDMARK_FLAG_ZERO_G + var/shuttle_name + var/list/visitors // landmark -> visiting shuttle stationed there + +/obj/effect/shuttle_landmark/ship/Initialize(mapload, shuttle_name) + landmark_tag += "_[shuttle_name]" + src.shuttle_name = shuttle_name + . = ..() + +/obj/effect/shuttle_landmark/ship/Destroy() + var/obj/effect/overmap/visitable/ship/landable/ship = map_sectors["[z]"] + if(istype(ship) && ship.landmark == src) + ship.landmark = null + . = ..() + +/obj/effect/shuttle_landmark/ship/cannot_depart(datum/shuttle/shuttle) + if(LAZYLEN(visitors)) + return "Grappled by other shuttle; cannot manouver." + +/obj/effect/shuttle_landmark/visiting_shuttle + flags = SLANDMARK_FLAG_AUTOSET | SLANDMARK_FLAG_ZERO_G + var/obj/effect/shuttle_landmark/ship/core_landmark + +/obj/effect/shuttle_landmark/visiting_shuttle/Initialize(mapload, obj/effect/shuttle_landmark/ship/master, _name) + core_landmark = master + name = _name + landmark_tag = master.shuttle_name + _name + destroyed_event.register(master, src, /proc/qdel) + . = ..() + +/obj/effect/shuttle_landmark/visiting_shuttle/Destroy() + destroyed_event.unregister(core_landmark, src) + LAZYREMOVE(core_landmark.visitors, src) + core_landmark = null + . = ..() + +/obj/effect/shuttle_landmark/visiting_shuttle/is_valid(datum/shuttle/shuttle) + . = ..() + if(!.) + return + var/datum/shuttle/boss_shuttle = SSshuttle.shuttles[core_landmark.shuttle_name] + if(boss_shuttle.current_location != core_landmark) + return FALSE // Only available when our governing shuttle is in space. + if(shuttle == boss_shuttle) // Boss shuttle only lands on main landmark + return FALSE + +/obj/effect/shuttle_landmark/visiting_shuttle/shuttle_arrived(datum/shuttle/shuttle) + LAZYSET(core_landmark.visitors, src, shuttle) + +/obj/effect/shuttle_landmark/visiting_shuttle/proc/shuttle_left(datum/shuttle/shuttle, obj/effect/shuttle_landmark/old_landmark, obj/effect/shuttle_landmark/new_landmark) + if(old_landmark == src) + LAZYREMOVE(core_landmark.visitors, src) + +/obj/effect/overmap/visitable/ship/landable/proc/on_shuttle_jump(datum/shuttle/given_shuttle, obj/effect/shuttle_landmark/from, obj/effect/shuttle_landmark/into) + if(given_shuttle != SSshuttle.shuttles[shuttle]) + return + var/datum/shuttle/autodock/auto = given_shuttle + if(into == auto.landmark_transition) + status = SHIP_STATUS_TRANSIT + on_takeoff(from, into) + return + if(into == landmark) + status = SHIP_STATUS_OVERMAP + on_takeoff(from, into) + return + status = SHIP_STATUS_LANDED + on_landing(from, into) + +/obj/effect/overmap/visitable/ship/landable/proc/on_landing(obj/effect/shuttle_landmark/from, obj/effect/shuttle_landmark/into) + var/obj/effect/overmap/visitable/target = map_sectors["[into.z]"] + var/datum/shuttle/shuttle_datum = SSshuttle.shuttles[shuttle] + if(into.landmark_tag == shuttle_datum.motherdock) // If our motherdock is a landable ship, it won't be found properly here so we need to find it manually. + for(var/obj/effect/overmap/visitable/ship/landable/landable in SSshuttle.ships) + if(landable.shuttle == shuttle_datum.mothershuttle) + target = landable + break + if(!target || target == src) + return + forceMove(target) + halt() + +/obj/effect/overmap/visitable/ship/landable/proc/on_takeoff(obj/effect/shuttle_landmark/from, obj/effect/shuttle_landmark/into) + if(!isturf(loc)) + forceMove(get_turf(loc)) + unhalt() + +/obj/effect/overmap/visitable/ship/landable/get_landed_info() + switch(status) + if(SHIP_STATUS_LANDED) + var/obj/effect/overmap/visitable/location = loc + if(istype(loc, /obj/effect/overmap/visitable/sector)) + return "Landed on \the [location.name]. Use secondary thrust to get clear before activating primary engines." + if(istype(loc, /obj/effect/overmap/visitable/ship)) + return "Docked with \the [location.name]. Use secondary thrust to get clear before activating primary engines." + return "Docked with an unknown object." + if(SHIP_STATUS_TRANSIT) + return "Maneuvering under secondary thrust." + if(SHIP_STATUS_OVERMAP) + return "In open space." \ No newline at end of file diff --git a/code/modules/overmap/ships/ship.dm b/code/modules/overmap/ships/ship.dm new file mode 100644 index 00000000000..67e950f0791 --- /dev/null +++ b/code/modules/overmap/ships/ship.dm @@ -0,0 +1,223 @@ +#define SHIP_MOVE_RESOLUTION 0.00001 +#define MOVING(speed) abs(speed) >= min_speed +#define SANITIZE_SPEED(speed) SIGN(speed) * Clamp(abs(speed), 0, max_speed) +#define CHANGE_SPEED_BY(speed_var, v_diff) \ + v_diff = SANITIZE_SPEED(v_diff);\ + if(!MOVING(speed_var + v_diff)) \ + {speed_var = 0};\ + else \ + {speed_var = SANITIZE_SPEED((speed_var + v_diff)/(1 + speed_var*v_diff/(max_speed ** 2)))} +// Uses Lorentzian dynamics to avoid going too fast. + +/obj/effect/overmap/visitable/ship + name = "generic ship" + desc = "Space faring vessel." + icon_state = "ship" + var/moving_state = "ship_moving" + + var/vessel_mass = 10000 //tonnes, arbitrary number, affects acceleration provided by engines + var/vessel_size = SHIP_SIZE_LARGE //arbitrary number, affects how likely are we to evade meteors + var/max_speed = 1/(1 SECOND) //"speed of light" for the ship, in turfs/tick. + var/min_speed = 1/(2 MINUTES) // Below this, we round speed to 0 to avoid math errors. + + var/list/speed = list(0,0) //speed in x,y direction + var/last_burn = 0 //worldtime when ship last acceleated + var/burn_delay = 1 SECOND //how often ship can do burns + var/list/last_movement = list(0,0) //worldtime when ship last moved in x,y direction + var/fore_dir = NORTH //what dir ship flies towards for purpose of moving stars effect procs + + var/list/engines = list() + var/engines_state = 0 //global on/off toggle for all engines + var/thrust_limit = 1 //global thrust limit for all engines, 0..1 + var/halted = 0 //admin halt or other stop. + +/obj/effect/overmap/visitable/ship/Initialize() + . = ..() + min_speed = round(min_speed, SHIP_MOVE_RESOLUTION) + max_speed = round(max_speed, SHIP_MOVE_RESOLUTION) + SSshuttle.ships += src + START_PROCESSING(SSprocessing, src) + +/obj/effect/overmap/visitable/ship/Destroy() + STOP_PROCESSING(SSprocessing, src) + SSshuttle.ships -= src + . = ..() + +/obj/effect/overmap/visitable/ship/relaymove(mob/user, direction, accel_limit) + accelerate(direction, accel_limit) + +/obj/effect/overmap/visitable/ship/proc/is_still() + return !MOVING(speed[1]) && !MOVING(speed[2]) + +/obj/effect/overmap/visitable/ship/get_scan_data(mob/user) + . = ..() + if(!is_still()) + . += "
Heading: [dir2angle(get_heading())], speed [get_speed() * 1000]" + +//Projected acceleration based on information from engines +/obj/effect/overmap/visitable/ship/proc/get_acceleration() + return round(get_total_thrust()/get_vessel_mass(), SHIP_MOVE_RESOLUTION) + +//Does actual burn and returns the resulting acceleration +/obj/effect/overmap/visitable/ship/proc/get_burn_acceleration() + return round(burn() / get_vessel_mass(), SHIP_MOVE_RESOLUTION) + +/obj/effect/overmap/visitable/ship/proc/get_vessel_mass() + . = vessel_mass + for(var/obj/effect/overmap/visitable/ship/ship in src) + . += ship.get_vessel_mass() + +/obj/effect/overmap/visitable/ship/proc/get_speed() + return round(sqrt(speed[1] ** 2 + speed[2] ** 2), SHIP_MOVE_RESOLUTION) + +/obj/effect/overmap/visitable/ship/proc/get_heading() + var/res = 0 + if(MOVING(speed[1])) + if(speed[1] > 0) + res |= EAST + else + res |= WEST + if(MOVING(speed[2])) + if(speed[2] > 0) + res |= NORTH + else + res |= SOUTH + return res + +/obj/effect/overmap/visitable/ship/proc/adjust_speed(n_x, n_y) + CHANGE_SPEED_BY(speed[1], n_x) + CHANGE_SPEED_BY(speed[2], n_y) + for(var/zz in map_z) + if(is_still()) + toggle_move_stars(zz) + else + toggle_move_stars(zz, fore_dir) + update_icon() + +/obj/effect/overmap/visitable/ship/proc/get_brake_path() + if(!get_acceleration()) + return INFINITY + if(is_still()) + return 0 + if(!burn_delay) + return 0 + if(!get_speed()) + return 0 + var/num_burns = get_speed()/get_acceleration() + 2 //some padding in case acceleration drops form fuel usage + var/burns_per_grid = 1/ (burn_delay * get_speed()) + return round(num_burns/burns_per_grid) + +/obj/effect/overmap/visitable/ship/proc/decelerate() + if(((speed[1]) || (speed[2])) && can_burn()) + if (speed[1]) + adjust_speed(-SIGN(speed[1]) * min(get_burn_acceleration(),abs(speed[1])), 0) + if (speed[2]) + adjust_speed(0, -SIGN(speed[2]) * min(get_burn_acceleration(),abs(speed[2]))) + last_burn = world.time + +/obj/effect/overmap/visitable/ship/proc/accelerate(direction, accel_limit) + if(can_burn()) + last_burn = world.time + var/acceleration = min(get_burn_acceleration(), accel_limit) + if(direction & EAST) + adjust_speed(acceleration, 0) + if(direction & WEST) + adjust_speed(-acceleration, 0) + if(direction & NORTH) + adjust_speed(0, acceleration) + if(direction & SOUTH) + adjust_speed(0, -acceleration) + +/obj/effect/overmap/visitable/ship/process() + if(!halted && !is_still()) + var/list/deltas = list(0,0) + for(var/i=1, i<=2, i++) + if(MOVING(speed[i]) && world.time > last_movement[i] + 1/abs(speed[i])) + deltas[i] = SIGN(speed[i]) + last_movement[i] = world.time + var/turf/newloc = locate(x + deltas[1], y + deltas[2], z) + if(newloc) + Move(newloc) + handle_wraparound() + update_icon() + +/obj/effect/overmap/visitable/ship/update_icon() + if(!is_still()) + icon_state = moving_state + dir = get_heading() + else + icon_state = initial(icon_state) + ..() + +/obj/effect/overmap/visitable/ship/proc/burn() + for(var/datum/ship_engine/E in engines) + . += E.burn() + +/obj/effect/overmap/visitable/ship/proc/get_total_thrust() + for(var/datum/ship_engine/E in engines) + . += E.get_thrust() + +/obj/effect/overmap/visitable/ship/proc/can_burn() + if(halted) + return 0 + if (world.time < last_burn + burn_delay) + return 0 + for(var/datum/ship_engine/E in engines) + . |= E.can_burn() + +//deciseconds to next step +/obj/effect/overmap/visitable/ship/proc/ETA() + . = INFINITY + for(var/i=1, i<=2, i++) + if(MOVING(speed[i])) + . = min(last_movement[i] - world.time + 1/abs(speed[i]), .) + . = max(.,0) + +/obj/effect/overmap/visitable/ship/proc/handle_wraparound() + var/nx = x + var/ny = y + var/low_edge = 1 + var/high_edge = current_map.overmap_size - 1 + + if((dir & WEST) && x == low_edge) + nx = high_edge + else if((dir & EAST) && x == high_edge) + nx = low_edge + if((dir & SOUTH) && y == low_edge) + ny = high_edge + else if((dir & NORTH) && y == high_edge) + ny = low_edge + if((x == nx) && (y == ny)) + return //we're not flying off anywhere + + var/turf/T = locate(nx,ny,z) + if(T) + forceMove(T) + +/obj/effect/overmap/visitable/ship/proc/halt() + adjust_speed(-speed[1], -speed[2]) + halted = 1 + +/obj/effect/overmap/visitable/ship/proc/unhalt() + if(!SSshuttle.overmap_halted) + halted = 0 + +/obj/effect/overmap/visitable/ship/Bump(var/atom/A) + if(istype(A,/turf/unsimulated/map/edge)) + handle_wraparound() + ..() + +/obj/effect/overmap/visitable/ship/populate_sector_objects() + ..() + for(var/obj/machinery/computer/ship/S in SSmachinery.all_machines) + S.attempt_hook_up(src) + for(var/datum/ship_engine/E in ship_engines) + if(check_ownership(E.holder)) + engines |= E + +/obj/effect/overmap/visitable/ship/proc/get_landed_info() + return "This ship cannot land." + +#undef MOVING +#undef SANITIZE_SPEED +#undef CHANGE_SPEED_BY \ No newline at end of file diff --git a/code/modules/overmap/spacetravel.dm b/code/modules/overmap/spacetravel.dm new file mode 100644 index 00000000000..48ae90b7bc1 --- /dev/null +++ b/code/modules/overmap/spacetravel.dm @@ -0,0 +1,115 @@ +//list used to cache empty zlevels to avoid nedless map bloat +var/list/cached_space = list() + +//Space stragglers go here + +/obj/effect/overmap/visitable/sector/temporary + name = "Deep Space" + invisibility = 101 + known = 0 + +/obj/effect/overmap/visitable/sector/temporary/New(var/nx, var/ny, var/nz) + loc = locate(nx, ny, current_map.overmap_z) + x = nx + y = ny + map_z += nz + map_sectors["[nz]"] = src + testing("Temporary sector at [x],[y] was created, corresponding zlevel is [nz].") + +/obj/effect/overmap/visitable/sector/temporary/Destroy() + map_sectors["[map_z]"] = null + testing("Temporary sector at [x],[y] was deleted.") + return ..() + +/obj/effect/overmap/visitable/sector/temporary/proc/can_die(var/mob/observer) + testing("Checking if sector at [map_z[1]] can die.") + for(var/mob/M in player_list) + if(M != observer && (M.z in map_z)) + testing("There are people on it.") + return 0 + return 1 + +proc/get_deepspace(x,y) + var/obj/effect/overmap/visitable/sector/temporary/res = locate(x,y,current_map.overmap_z) + if(istype(res)) + return res + else if(cached_space.len) + res = cached_space[cached_space.len] + cached_space -= res + res.x = x + res.y = y + return res + else + return new /obj/effect/overmap/visitable/sector/temporary(x, y, current_map.get_empty_zlevel()) + +/atom/movable/proc/lost_in_space() + for(var/atom/movable/AM in contents) + if(!AM.lost_in_space()) + return FALSE + return TRUE + +/mob/lost_in_space() + return isnull(client) + +/mob/living/carbon/human/lost_in_space() + return isnull(client) && stat == DEAD + +proc/overmap_spacetravel(var/turf/space/T, var/atom/movable/A) + if (!T || !A) + return + + var/obj/effect/overmap/visitable/M = map_sectors["[T.z]"] + if (!M) + return + + if(A.lost_in_space()) + if(!QDELETED(A)) + qdel(A) + return + + var/nx = 1 + var/ny = 1 + var/nz = 1 + + if(T.x <= TRANSITIONEDGE) + nx = world.maxx - TRANSITIONEDGE - 2 + ny = rand(TRANSITIONEDGE + 2, world.maxy - TRANSITIONEDGE - 2) + + else if (A.x >= (world.maxx - TRANSITIONEDGE - 1)) + nx = TRANSITIONEDGE + 2 + ny = rand(TRANSITIONEDGE + 2, world.maxy - TRANSITIONEDGE - 2) + + else if (T.y <= TRANSITIONEDGE) + ny = world.maxy - TRANSITIONEDGE -2 + nx = rand(TRANSITIONEDGE + 2, world.maxx - TRANSITIONEDGE - 2) + + else if (A.y >= (world.maxy - TRANSITIONEDGE - 1)) + ny = TRANSITIONEDGE + 2 + nx = rand(TRANSITIONEDGE + 2, world.maxx - TRANSITIONEDGE - 2) + + testing("[A] spacemoving from [M] ([M.x], [M.y]).") + + var/turf/map = locate(M.x,M.y,current_map.overmap_z) + var/obj/effect/overmap/visitable/TM + for(var/obj/effect/overmap/visitable/O in map) + if(O != M && O.in_space && prob(50)) + TM = O + break + if(!TM) + TM = get_deepspace(M.x,M.y) + nz = pick(TM.map_z) + + var/turf/dest = locate(nx,ny,nz) + if(dest) + A.forceMove(dest) + if(ismob(A)) + var/mob/D = A + if(D.pulling) + D.pulling.forceMove(dest) + + if(istype(M, /obj/effect/overmap/visitable/sector/temporary)) + var/obj/effect/overmap/visitable/sector/temporary/source = M + if (source.can_die()) + testing("Caching [M] for future use") + source.forceMove(null) + cached_space += source diff --git a/code/modules/shuttles/landmarks.dm b/code/modules/shuttles/landmarks.dm index b1df5a936fe..74075b16640 100644 --- a/code/modules/shuttles/landmarks.dm +++ b/code/modules/shuttles/landmarks.dm @@ -47,6 +47,20 @@ if(!istype(docking_controller)) log_debug("Could not find docking controller for shuttle waypoint '[name]', docking tag was '[docking_tag]'.") +/obj/effect/shuttle_landmark/forceMove() + var/obj/effect/overmap/visitable/map_origin = map_sectors["[z]"] + . = ..() + var/obj/effect/overmap/visitable/map_destination = map_sectors["[z]"] + if(map_origin != map_destination) + if(map_origin) + map_origin.remove_landmark(src, shuttle_restricted) + if(map_destination) + map_destination.add_landmark(src, shuttle_restricted) + +//Called when the landmark is added to an overmap sector. +/obj/effect/shuttle_landmark/proc/sector_set(var/obj/effect/overmap/visitable/O, shuttle_name) + shuttle_restricted = shuttle_name + /obj/effect/shuttle_landmark/proc/is_valid(var/datum/shuttle/shuttle) if(shuttle.current_location == src) return FALSE @@ -95,6 +109,10 @@ ..() return INITIALIZE_HINT_LATELOAD +/obj/effect/shuttle_landmark/automatic/sector_set(var/obj/effect/overmap/visitable/O) + ..() + name = "[O.name] - [initial(name)] ([x],[y])" + /obj/effect/shuttle_landmark/automatic/clearing/LateInitialize() ..() for(var/turf/T in range(radius, src)) diff --git a/code/modules/shuttles/shuttle.dm b/code/modules/shuttles/shuttle.dm index 1b7b134f13c..8df4e16c89e 100644 --- a/code/modules/shuttles/shuttle.dm +++ b/code/modules/shuttles/shuttle.dm @@ -26,6 +26,9 @@ var/logging_home_tag //Whether in-game logs will be generated whenever the shuttle leaves/returns to the landmark with this landmark_tag. var/logging_access //Controls who has write access to log-related stuff; should correlate with pilot access. + var/mothershuttle //tag of mothershuttle + var/motherdock //tag of mothershuttle landmark, defaults to starting location + /datum/shuttle/New(_name, var/obj/effect/shuttle_landmark/initial_location) ..() if(_name) @@ -209,6 +212,8 @@ if(HasAbove(current_location.z)) for(var/area/A in shuttle_area) for(var/turf/TD in A.contents) + TD.update_above() + TD.update_icon() var/turf/TA = GetAbove(TD) if(istype(TA, get_base_turf_by_area(TA)) || istype(TA, /turf/simulated/open)) if(get_area(TA) in shuttle_area) @@ -232,10 +237,31 @@ NewPN.add_cable(C) propagate_network(C,C.powernet) + if(mothershuttle) + var/datum/shuttle/mothership = SSshuttle.shuttles[mothershuttle] + if(mothership) + if(current_location.landmark_tag == motherdock) + mothership.shuttle_area |= shuttle_area + else + mothership.shuttle_area -= shuttle_area + //returns 1 if the shuttle has a valid arrive time /datum/shuttle/proc/has_arrive_time() return (moving_status == SHUTTLE_INTRANSIT) +/datum/shuttle/proc/find_children() + . = list() + for(var/shuttle_name in SSshuttle.shuttles) + var/datum/shuttle/shuttle = SSshuttle.shuttles[shuttle_name] + if(shuttle.mothershuttle == name) + . += shuttle + +//Returns those areas that are not actually child shuttles. +/datum/shuttle/proc/find_childfree_areas() + . = shuttle_area.Copy() + for(var/datum/shuttle/child in find_children()) + . -= child.shuttle_area + /datum/shuttle/autodock/proc/get_location_name() if(moving_status == SHUTTLE_INTRANSIT) return "In transit" diff --git a/code/modules/shuttles/shuttle_autodock.dm b/code/modules/shuttles/shuttle_autodock.dm index bc906c28d1e..21677d7bfb6 100644 --- a/code/modules/shuttles/shuttle_autodock.dm +++ b/code/modules/shuttles/shuttle_autodock.dm @@ -119,9 +119,16 @@ next_location = null in_use = null //release lock +/datum/shuttle/autodock/proc/get_travel_time() + return move_time + /datum/shuttle/autodock/proc/process_launch() - if (move_time && landmark_transition) - . = long_jump(next_location, landmark_transition, move_time) + if(!next_location.is_valid(src) || current_location.cannot_depart(src)) + process_state = IDLE_STATE + in_use = null + return + if (get_travel_time() && landmark_transition) + . = long_jump(next_location, landmark_transition, get_travel_time()) else . = short_jump(next_location) process_state = WAIT_ARRIVE diff --git a/code/unit_tests/overmap_tests.dm b/code/unit_tests/overmap_tests.dm new file mode 100644 index 00000000000..5681e084234 --- /dev/null +++ b/code/unit_tests/overmap_tests.dm @@ -0,0 +1,18 @@ +// 513 does not allow white or no color as a filter color +/datum/unit_test/overmap_effects_shall_have_non_white_color + name = "OVERMAP: Shall have non-white color" + +/datum/unit_test/overmap_effects_shall_have_non_white_color/start_test() + var/list/invalid_overmap_types = list() + for(var/omt in subtypesof(/obj/effect/overmap)) + var/obj/overmap = omt + var/color = initial(overmap.color) + if(!color || color == COLOR_WHITE) + invalid_overmap_types += omt + + if(invalid_overmap_types.len) + fail("Following /obj/effect/overmap types types have invalid colors: [english_list(invalid_overmap_types)]") + else + pass("All /obj/effect/overmap types have a valid color") + + return TRUE \ No newline at end of file diff --git a/icons/effects/numbers.dmi b/icons/effects/numbers.dmi new file mode 100644 index 00000000000..fcf9463ccd2 Binary files /dev/null and b/icons/effects/numbers.dmi differ diff --git a/icons/obj/computer.dmi b/icons/obj/computer.dmi index e516e7cb61d..16569466ab6 100644 Binary files a/icons/obj/computer.dmi and b/icons/obj/computer.dmi differ diff --git a/icons/obj/overmap.dmi b/icons/obj/overmap.dmi new file mode 100644 index 00000000000..002377325f2 Binary files /dev/null and b/icons/obj/overmap.dmi differ diff --git a/icons/obj/stationobjs.dmi b/icons/obj/stationobjs.dmi index 3abda412010..b9570e57922 100644 Binary files a/icons/obj/stationobjs.dmi and b/icons/obj/stationobjs.dmi differ diff --git a/icons/turf/space.dmi b/icons/turf/space.dmi index 63fae987d86..55dec4a40a4 100644 Binary files a/icons/turf/space.dmi and b/icons/turf/space.dmi differ diff --git a/maps/_common/mapsystem/map.dm b/maps/_common/mapsystem/map.dm index 38e2fbb90b6..4dde40d85b3 100644 --- a/maps/_common/mapsystem/map.dm +++ b/maps/_common/mapsystem/map.dm @@ -10,6 +10,7 @@ var/list/player_levels = list() // Z-levels a character can typically reach var/list/sealed_levels = list() // Z-levels that don't allow random transit at edge var/list/restricted_levels = list() // Z-levels that dont allow ghosts to randomly move around + var/list/empty_levels = null // Empty Z-levels that may be used for various things (currently used by bluespace jump) var/list/map_levels // Z-levels available to various consoles, such as the crew monitor. Defaults to station_levels if unset. @@ -66,6 +67,10 @@ var/lobby_icon // The icon which contains the lobby image(s) var/list/lobby_screens = list("title") // The list of lobby screen to pick() from. If left unset the first icon state is always selected. + var/use_overmap = FALSE //If overmap should be used (including overmap space travel override) + var/overmap_size = 20 //Dimensions of overmap zlevel if overmap is used. + var/overmap_z = 0 //If 0 will generate overmap zlevel on init. Otherwise will populate the zlevel provided. + var/overmap_event_areas = 0 //How many event "clouds" will be generated var/list/map_shuttles = list() // A list of all our shuttles. /datum/map/New() @@ -92,6 +97,12 @@ return current_z_level return text2num(pickweight(candidates)) +/datum/map/proc/get_empty_zlevel() + if(empty_levels == null) + world.maxz++ + empty_levels = list(world.maxz) + return pick(empty_levels) + /datum/map/proc/setup_shuttles() // Called right after SSatlas finishes loading the map & multiz is setup. diff --git a/nano/templates/helm.tmpl b/nano/templates/helm.tmpl index c72d0921b8f..2d371b11540 100644 --- a/nano/templates/helm.tmpl +++ b/nano/templates/helm.tmpl @@ -1,114 +1,159 @@ -
-

Sector information

-
- {{:data.sector}} -
- Coordinates: {{:data.s_x}} : {{:data.s_y}} -
- Additional information: {{:data.sector_info}} -
-
-
-

Flight data

-
+ +
+
+ Flight data
-
- Speed: +
+ ETA to next grid:
- {{:data.speed}} + {{:data.ETAnext}}
-
- Acceleration: +
+ Speed:
- {{:data.accel}} + {{:data.speed}} Gm/h
-
- Heading: +
+ Acceleration:
- {{:data.heading}} + {{:data.accel}} Gm/h +
+
+
+
+ Heading: +
+
+ {{:data.heading}}°
-
+
+
+ Acceleration limiter: +
+
+ {{:helper.link(data.accellimit, null, { 'accellimit' : 1}, null, null)}} Gm/h +
+
+
- - -

Manual control

-
+ +
+
+ Manual control
-
-
- {{:helper.link('', 'triangle-1-nw', { 'move' : 9 }, null, null)}} - {{:helper.link('', 'triangle-1-n', { 'move' : 1 }, null, null)}} - {{:helper.link('', 'triangle-1-ne', { 'move' : 5 }, null, null)}} -
-
- {{:helper.link('', 'triangle-1-w', { 'move' : 8 }, null, null)}} - {{:helper.link('', 'circle-close', { 'brake' : 1 }, null, null)}} - {{:helper.link('', 'triangle-1-e', { 'move' : 4 }, null, null)}} -
-
- {{:helper.link('', 'triangle-1-sw', { 'move' : 10 }, null, null)}} - {{:helper.link('', 'triangle-1-s', { 'move' : 2 }, null, null)}} - {{:helper.link('', 'triangle-1-se', { 'move' : 6 }, null, null)}} -
+
+ {{:helper.link('', 'triangle-1-nw', { 'move' : 9 }, data.canburn ? null : 'disabled', null)}} + {{:helper.link('', 'triangle-1-n', { 'move' : 1 }, data.canburn ? null : 'disabled', null)}} + {{:helper.link('', 'triangle-1-ne', { 'move' : 5 }, data.canburn ? null : 'disabled', null)}} +
+
+ {{:helper.link('', 'triangle-1-w', { 'move' : 8 }, data.canburn ? null : 'disabled', null)}} + {{:helper.link('', 'circle-close', { 'brake' : 1 }, data.canburn ? null : 'disabled', null)}} + {{:helper.link('', 'triangle-1-e', { 'move' : 4 }, data.canburn ? null : 'disabled', null)}} +
+
+ {{:helper.link('', 'triangle-1-sw', { 'move' : 10 }, data.canburn ? null : 'disabled', null)}} + {{:helper.link('', 'triangle-1-s', { 'move' : 2 }, data.canburn ? null : 'disabled', null)}} + {{:helper.link('', 'triangle-1-se', { 'move' : 6 }, data.canburn ? null : 'disabled', null)}}
-
-
- Direct control - {{:helper.link(data.manual_control ? 'Engaged' : 'Disengaged', 'shuffle', { 'manual' : 1 }, data.manual_control ? 'selected' : null, null)}} -
+
+ Direct control +
+ {{:helper.link(data.manual_control ? 'Engaged' : 'Disengaged', 'shuffle', { 'manual' : 1 }, null, data.manual_control ? 'selected' : null)}}
+
- -
-
-

Autopilot

-
-
+ +
+
+ Autopilot +
+
+ Target: +
+
+ {{if data.dest}} + {{:helper.link(data.d_x, null, { 'setx' : 1 }, null, null)}} {{:helper.link(data.d_y, null, { 'sety' : 1 }, null, null)}} + {{else}} + {{:helper.link('None', null, { 'sety' : 1, 'setx' : 1 }, null, null)}} + {{/if}} +
+
+
+
+ Speed limit: +
+
+ {{:helper.link(data.speedlimit, null, { 'speedlimit' : 1 }, null, null)}} Gm/h +
+
+
{{:helper.link(data.autopilot ? 'Engaged' : 'Disengaged', 'gear', { 'apilot' : 1 }, data.dest ? null : 'disabled', data.autopilot ? 'selected' : null)}}
-
-
-
- Target coordinates -
-
- {{if data.dest}} - {{:helper.link(data.d_x, null, { 'setx' : 1 }, null, null)}} {{:helper.link(data.d_y, null, { 'sety' : 1 }, null, null)}} - {{else}} - {{:helper.link('None', null, { 'sety' : 1, 'setx' : 1 }, null, null)}} - {{/if}} -
-
- -

Navigation data

-
- {{:helper.link('Save current position', 'disk', { 'add' : 'current' }, null)}} - {{:helper.link('Add new entry', 'document', { 'add' : 'new' }, null)}} +
- -
- {{if data.locations}} + +
+

Navigation data

+
+
+ Location: +
+
+ {{:data.sector}} +
+
+
+
+ Coordinates: +
+
+ {{:data.s_x}} : {{:data.s_y}} +
+
+
+
+ Scan data: +
+
+ {{:data.sector_info}} +
+
+
+
+ Status: +
+
+ {{:data.landed}} +
+
+
+ {{:helper.link('Save current position', 'disk', { 'add' : 'current' }, null)}} + {{:helper.link('Add new entry', 'document', { 'add' : 'new' }, null)}} +
+
+ +
NameCoordinatesActions {{for data.locations}} -
- {{:value.name}}: - {{:value.x}} : {{:value.y}} -
-
- {{:helper.link('Plot course', 'arrowreturnthick-1-e', { 'x' : value.x, 'y' : value.y }, null, null)}} - {{:helper.link('Remove entry', 'close', { 'remove' : value.reference }, null, null)}} -
+
{{:value.name}} + {{:value.x}} : {{:value.y}} + {{:helper.link('Plot course', 'arrowreturnthick-1-e', { 'x' : value.x, 'y' : value.y }, null, null)}} + {{:helper.link('Remove', 'close', { 'remove' : value.reference }, null, null)}} {{/for}} - {{/if}} - +
+
+
+ + \ No newline at end of file diff --git a/nano/templates/nav.tmpl b/nano/templates/nav.tmpl new file mode 100644 index 00000000000..6b5f5a96280 --- /dev/null +++ b/nano/templates/nav.tmpl @@ -0,0 +1,59 @@ +

Navigation

+
+
+
+
+ Map view + {{:helper.link(data.viewing ? 'Engaged' : 'Disengaged', 'shuffle', { 'viewing' : 1 }, null, data.viewing ? 'selected' : null)}} +
+
+
+
+
+

Sector information

+
+ {{:data.sector}} +
+ Coordinates: {{:data.s_x}} : {{:data.s_y}} +
+ Additional information: {{:data.sector_info}} +
+
+ +
+

Flight data

+
+
+
+ ETA to next grid: +
+
+ {{:data.ETAnext}} +
+
+
+
+ Speed: +
+
+ {{:data.speed}} Gm/h +
+
+
+
+ Acceleration: +
+
+ {{:data.accel}} Gm/h +
+
+
+
+ Heading: +
+
+ {{:data.heading}}° +
+
+
+
\ No newline at end of file diff --git a/nano/templates/shipsensors.tmpl b/nano/templates/shipsensors.tmpl new file mode 100644 index 00000000000..5931fd01b24 --- /dev/null +++ b/nano/templates/shipsensors.tmpl @@ -0,0 +1,90 @@ +

Sensors control console

+
+ {{:helper.link(data.on ? 'Switch off' : 'Switch on', 'gear', { 'toggle' : 1 }, data.status != 'MISSING' ? null : 'disabled', data.on ? 'selected' : null)}} +
+
+ Status: +
+
+ {{:data.status}} +
+
+
+
+ Range: +
+
+ {{:helper.link(data.range, null, { 'range' : 1 }, null, null)}} +
+
+
+
+
+
+ Integrity: +
+
+ {{if data.health < (data.max_health * 0.25)}} + {{:helper.displayBar(data.health, 0, data.max_health, 'bad')}} +
{{:data.health}}/{{:data.max_health}} + {{else data.health < data.max_health *.75}} + {{:helper.displayBar(data.health, 0, data.max_health, 'average')}} +
{{:data.health}}/{{:data.max_health}} + {{else}} + {{:helper.displayBar(data.health, 0, data.max_health, 'good')}} +
{{:data.health}}/{{:data.max_health}} + {{/if}} +
+
+
+
+ Temperature: +
+
+ {{if data.heat < (data.critical_heat * 0.5)}} + {{:helper.displayBar(data.heat, 0, data.critical_heat, 'good')}} + {{else data.heat < (data.critical_heat * 0.75)}} + {{:helper.displayBar(data.heat, 0, data.critical_heat, 'average')}} + {{else}} + {{:helper.displayBar(data.heat, 0, data.critical_heat, 'bad')}} + {{/if}} +
+
+ {{if data.heat < (data.critical_heat * 0.5)}} + Temperature low. + {{else data.heat < (data.critical_heat * 0.75)}} + Sensor temperature high! + {{else}} + TEMPERATURE CRITICAL: Disable or reduce power immediately! + {{/if}} +
+
+
+
+
+
+ Sector map view + {{:helper.link(data.viewing ? 'Engaged' : 'Disengaged', 'shuffle', { 'viewing' : 1 }, null, data.viewing ? 'selected' : null)}} +
+
+
+

Sensor contacts

+
+{{if data.contacts}} + + {{for data.contacts}} + +
+
+ + + + {{/for}} +
{{:helper.link('Scan', 'search' ,{ 'scan' : value.ref }, null, null)}}{{:value.name}}, bearing {{:value.bearing}}
+{{/if}} +
+{{if data.status == 'MISSING'}} +
+ {{:helper.link('Link up with the sensor suite', 'gear', { 'link' : 1 }, data.status == 'MISSING' ? null : 'disabled', null)}} +
+{{/if}} diff --git a/nano/templates/shuttle_control_console_exploration.tmpl b/nano/templates/shuttle_control_console_exploration.tmpl new file mode 100644 index 00000000000..2b0596e4f55 --- /dev/null +++ b/nano/templates/shuttle_control_console_exploration.tmpl @@ -0,0 +1,89 @@ +

Shuttle Status

+
+
+ {{:data.shuttle_status}} +
+
+
+
+
+ Engines: +
+
+ {{if data.shuttle_state == "idle"}} + IDLE + {{else data.shuttle_state == "warmup"}} + STARTING IGNITION + {{else data.shuttle_state == "in_transit"}} + ENGAGED + {{else}} + ERROR + {{/if}} +
+
+
+{{if data.has_docking}} +
+
+
+ Docking Status: +
+
+ {{if data.docking_status == "docked"}} + DOCKED + {{else data.docking_status == "docking"}} + {{if !data.docking_override}} + DOCKING + {{else}} + DOCKING-MANUAL + {{/if}} + {{else data.docking_status == "undocking"}} + {{if !data.docking_override}} + UNDOCKING + {{else}} + UNDOCKING-MANUAL + {{/if}} + {{else data.docking_status == "undocked"}} + UNDOCKED + {{else}} + ERROR + {{/if}} +
+
+
+{{/if}} +
+
+ Current Destination: +
+ {{:data.destination_name}} +
+ {{:helper.link('Choose Destination', 'arrowreturn-1-s', {'pick' : '1'}, data.can_pick ? null : 'disabled' , null)}} +
+
+{{if data.fuel_usage}} +
+
+ Est. Delta-V Budget: +
+
+ {{:data.remaining_fuel}} m/s +
+
+ Avg. Delta-V Per Maneuver: +
+
+ {{:data.fuel_usage}} m/s +
+
+{{/if}} +

Shuttle Control

+
+
+
+ {{:helper.link('Launch Shuttle', 'arrowthickstop-1-e', {'move' : '1'}, data.can_launch ? null : 'disabled' , null)}} + {{:helper.link('Cancel Launch', 'cancel', {'cancel' : '1'}, data.can_cancel ? null : 'disabled' , null)}} + {{:helper.link('Force Launch', 'alert', {'force' : '1'}, data.can_force ? null : 'disabled' , data.can_force ? 'redButton' : null)}} +
+
+
\ No newline at end of file diff --git a/sound/machines/thruster.ogg b/sound/machines/thruster.ogg new file mode 100644 index 00000000000..7564bde67a0 Binary files /dev/null and b/sound/machines/thruster.ogg differ