From c9086829ec10c615b042a867471e759b7433b9bb Mon Sep 17 00:00:00 2001 From: Aronai Sieyes Date: Fri, 10 Apr 2020 19:34:09 -0400 Subject: [PATCH 1/7] Convert some telecomms constants into defines --- .../components/binary_devices/dp_vent_pump.dm | 2 +- .../components/binary_devices/passive_gate.dm | 2 +- .../components/binary_devices/pump.dm | 2 +- .../components/unary/outlet_injector.dm | 2 +- code/ATMOSPHERICS/components/unary/vent_pump.dm | 2 +- .../components/unary/vent_scrubber.dm | 2 +- code/__defines/machinery.dm | 15 +++++++++++++++ code/game/machinery/air_alarm.dm | 4 ++-- code/game/machinery/atmo_control.dm | 14 +++++++------- code/game/machinery/atmoalter/meter.dm | 2 +- code/game/machinery/computer/communications.dm | 2 +- code/game/machinery/computer/prisonshuttle.dm | 2 +- code/game/machinery/computer/supply.dm | 2 +- code/game/machinery/doors/airlock_control.dm | 8 ++++---- code/game/machinery/magnet.dm | 4 ++-- code/game/objects/items/devices/PDA/cart.dm | 2 +- code/game/objects/items/devices/PDA/radio.dm | 2 +- .../items/devices/communicator/cartridge.dm | 2 +- code/game/objects/items/devices/gps.dm | 2 +- .../file_system/programs/command/comm.dm | 2 +- 20 files changed, 45 insertions(+), 30 deletions(-) diff --git a/code/ATMOSPHERICS/components/binary_devices/dp_vent_pump.dm b/code/ATMOSPHERICS/components/binary_devices/dp_vent_pump.dm index 2f276160fe..809cc987eb 100644 --- a/code/ATMOSPHERICS/components/binary_devices/dp_vent_pump.dm +++ b/code/ATMOSPHERICS/components/binary_devices/dp_vent_pump.dm @@ -176,7 +176,7 @@ return 0 var/datum/signal/signal = new - signal.transmission_method = 1 //radio signal + signal.transmission_method = TRANSMISSION_RADIO //radio signal signal.source = src signal.data = list( diff --git a/code/ATMOSPHERICS/components/binary_devices/passive_gate.dm b/code/ATMOSPHERICS/components/binary_devices/passive_gate.dm index 191e6291ea..1c501e87f0 100644 --- a/code/ATMOSPHERICS/components/binary_devices/passive_gate.dm +++ b/code/ATMOSPHERICS/components/binary_devices/passive_gate.dm @@ -151,7 +151,7 @@ return 0 var/datum/signal/signal = new - signal.transmission_method = 1 //radio signal + signal.transmission_method = TRANSMISSION_RADIO //radio signal signal.source = src signal.data = list( diff --git a/code/ATMOSPHERICS/components/binary_devices/pump.dm b/code/ATMOSPHERICS/components/binary_devices/pump.dm index e2483fdbef..0df0217497 100644 --- a/code/ATMOSPHERICS/components/binary_devices/pump.dm +++ b/code/ATMOSPHERICS/components/binary_devices/pump.dm @@ -128,7 +128,7 @@ Thus, the two variables affect pump operation are set in New(): return 0 var/datum/signal/signal = new - signal.transmission_method = 1 //radio signal + signal.transmission_method = TRANSMISSION_RADIO //radio signal signal.source = src signal.data = list( diff --git a/code/ATMOSPHERICS/components/unary/outlet_injector.dm b/code/ATMOSPHERICS/components/unary/outlet_injector.dm index 3c342c3cdb..5eeab2c8e4 100644 --- a/code/ATMOSPHERICS/components/unary/outlet_injector.dm +++ b/code/ATMOSPHERICS/components/unary/outlet_injector.dm @@ -107,7 +107,7 @@ return 0 var/datum/signal/signal = new - signal.transmission_method = 1 //radio signal + signal.transmission_method = TRANSMISSION_RADIO //radio signal signal.source = src signal.data = list( diff --git a/code/ATMOSPHERICS/components/unary/vent_pump.dm b/code/ATMOSPHERICS/components/unary/vent_pump.dm index af8020f9e1..6675eef38b 100644 --- a/code/ATMOSPHERICS/components/unary/vent_pump.dm +++ b/code/ATMOSPHERICS/components/unary/vent_pump.dm @@ -247,7 +247,7 @@ return 0 var/datum/signal/signal = new - signal.transmission_method = 1 //radio signal + signal.transmission_method = TRANSMISSION_RADIO //radio signal signal.source = src signal.data = list( diff --git a/code/ATMOSPHERICS/components/unary/vent_scrubber.dm b/code/ATMOSPHERICS/components/unary/vent_scrubber.dm index ff97f259a5..1c1c696b98 100644 --- a/code/ATMOSPHERICS/components/unary/vent_scrubber.dm +++ b/code/ATMOSPHERICS/components/unary/vent_scrubber.dm @@ -93,7 +93,7 @@ return 0 var/datum/signal/signal = new - signal.transmission_method = 1 //radio signal + signal.transmission_method = TRANSMISSION_RADIO //radio signal signal.source = src signal.data = list( "area" = area_uid, diff --git a/code/__defines/machinery.dm b/code/__defines/machinery.dm index d01ff407a2..6039f9f65b 100644 --- a/code/__defines/machinery.dm +++ b/code/__defines/machinery.dm @@ -74,6 +74,21 @@ var/global/defer_powernet_rebuild = 0 // True if net rebuild will be called // Those networks can only be accessed by pre-existing terminals. AIs and new terminals can't use them. var/list/restricted_camera_networks = list(NETWORK_ERT,NETWORK_MERCENARY,"Secret", NETWORK_COMMUNICATORS) +#define TRANSMISSION_WIRE 0 //Is this ever used? I don't think it is. +#define TRANSMISSION_RADIO 1 //Radio transmissions (like airlock controller to pump) +#define TRANSMISSION_SUBSPACE 2 //Like headsets +#define TRANSMISSION_BLUESPACE 3 //Point-to-point links + +#define SIGNAL_NORMAL 0 //Normal subspace signals +#define SIGNAL_SIMPLE 1 //Normal inter-machinery(?) signals +#define SIGNAL_FAKE 2 //Untrackable signals +#define SIGNAL_TEST 4 //Unlogged signals + +#define DATA_NORMAL 0 //Normal data +#define DATA_INTERCOM 1 //Intercoms only +#define DATA_LOCAL 2 //Intercoms and SBRs +#define DATA_ANTAG 3 //Antag interception +#define DATA_FAKE 4 //Not from a real mob //singularity defines #define STAGE_ONE 1 diff --git a/code/game/machinery/air_alarm.dm b/code/game/machinery/air_alarm.dm index 9e01eb8108..3d76b02e0f 100644 --- a/code/game/machinery/air_alarm.dm +++ b/code/game/machinery/air_alarm.dm @@ -383,7 +383,7 @@ return 0 var/datum/signal/signal = new - signal.transmission_method = 1 //radio signal + signal.transmission_method = TRANSMISSION_RADIO //radio signal signal.source = src signal.data = command @@ -445,7 +445,7 @@ var/datum/signal/alert_signal = new alert_signal.source = src - alert_signal.transmission_method = 1 + alert_signal.transmission_method = TRANSMISSION_RADIO alert_signal.data["zone"] = alarm_area.name alert_signal.data["type"] = "Atmospheric" diff --git a/code/game/machinery/atmo_control.dm b/code/game/machinery/atmo_control.dm index ccab21e889..5ebcfeef43 100644 --- a/code/game/machinery/atmo_control.dm +++ b/code/game/machinery/atmo_control.dm @@ -29,7 +29,7 @@ /obj/machinery/air_sensor/process() if(on) var/datum/signal/signal = new - signal.transmission_method = 1 //radio signal + signal.transmission_method = TRANSMISSION_RADIO //radio signal signal.data["tag"] = id_tag signal.data["timestamp"] = world.time @@ -212,7 +212,7 @@ obj/machinery/computer/general_air_control/Destroy() if(!radio_connection) return 0 var/datum/signal/signal = new - signal.transmission_method = 1 //radio signal + signal.transmission_method = TRANSMISSION_RADIO //radio signal signal.source = src if(href_list["in_refresh_status"]) input_info = null @@ -322,7 +322,7 @@ obj/machinery/computer/general_air_control/Destroy() if(!radio_connection) return 0 var/datum/signal/signal = new - signal.transmission_method = 1 //radio signal + signal.transmission_method = TRANSMISSION_RADIO //radio signal signal.source = src if(href_list["in_refresh_status"]) input_info = null @@ -383,7 +383,7 @@ obj/machinery/computer/general_air_control/Destroy() injecting = 1 var/datum/signal/signal = new - signal.transmission_method = 1 //radio signal + signal.transmission_method = TRANSMISSION_RADIO //radio signal signal.source = src signal.data = list( @@ -445,7 +445,7 @@ obj/machinery/computer/general_air_control/Destroy() return 0 var/datum/signal/signal = new - signal.transmission_method = 1 //radio signal + signal.transmission_method = TRANSMISSION_RADIO //radio signal signal.source = src signal.data = list( "tag" = device_tag, @@ -463,7 +463,7 @@ obj/machinery/computer/general_air_control/Destroy() return 0 var/datum/signal/signal = new - signal.transmission_method = 1 //radio signal + signal.transmission_method = TRANSMISSION_RADIO //radio signal signal.source = src signal.data = list( "tag" = device_tag, @@ -478,7 +478,7 @@ obj/machinery/computer/general_air_control/Destroy() return 0 var/datum/signal/signal = new - signal.transmission_method = 1 //radio signal + signal.transmission_method = TRANSMISSION_RADIO //radio signal signal.source = src signal.data = list( "tag" = device_tag, diff --git a/code/game/machinery/atmoalter/meter.dm b/code/game/machinery/atmoalter/meter.dm index 84b18c31ea..82ce3d66aa 100644 --- a/code/game/machinery/atmoalter/meter.dm +++ b/code/game/machinery/atmoalter/meter.dm @@ -67,7 +67,7 @@ var/datum/signal/signal = new signal.source = src - signal.transmission_method = 1 + signal.transmission_method = TRANSMISSION_RADIO signal.data = list( "tag" = id, "device" = "AM", diff --git a/code/game/machinery/computer/communications.dm b/code/game/machinery/computer/communications.dm index 6d25dd0b5c..a0f6e13385 100644 --- a/code/game/machinery/computer/communications.dm +++ b/code/game/machinery/computer/communications.dm @@ -544,7 +544,7 @@ var/datum/signal/status_signal = new status_signal.source = src - status_signal.transmission_method = 1 + status_signal.transmission_method = TRANSMISSION_RADIO status_signal.data["command"] = command switch(command) diff --git a/code/game/machinery/computer/prisonshuttle.dm b/code/game/machinery/computer/prisonshuttle.dm index 04f0c61c52..1d58a61df1 100644 --- a/code/game/machinery/computer/prisonshuttle.dm +++ b/code/game/machinery/computer/prisonshuttle.dm @@ -121,7 +121,7 @@ var/prison_shuttle_timeleft = 0 if(!frequency) return var/datum/signal/status_signal = new status_signal.source = src - status_signal.transmission_method = 1 + status_signal.transmission_method = TRANSMISSION_RADIO status_signal.data["command"] = command frequency.post_signal(src, status_signal) return diff --git a/code/game/machinery/computer/supply.dm b/code/game/machinery/computer/supply.dm index 05b4e65292..a310b33f78 100644 --- a/code/game/machinery/computer/supply.dm +++ b/code/game/machinery/computer/supply.dm @@ -421,7 +421,7 @@ var/datum/signal/status_signal = new status_signal.source = src - status_signal.transmission_method = 1 + status_signal.transmission_method = TRANSMISSION_RADIO status_signal.data["command"] = command frequency.post_signal(src, status_signal) diff --git a/code/game/machinery/doors/airlock_control.dm b/code/game/machinery/doors/airlock_control.dm index e5ff8bab16..ca7092708d 100644 --- a/code/game/machinery/doors/airlock_control.dm +++ b/code/game/machinery/doors/airlock_control.dm @@ -91,7 +91,7 @@ obj/machinery/door/airlock/proc/command_completed(var/command) obj/machinery/door/airlock/proc/send_status(var/bumped = 0) if(radio_connection) var/datum/signal/signal = new - signal.transmission_method = 1 //radio signal + signal.transmission_method = TRANSMISSION_RADIO //radio signal signal.data["tag"] = id_tag signal.data["timestamp"] = world.time @@ -172,7 +172,7 @@ obj/machinery/airlock_sensor/update_icon() obj/machinery/airlock_sensor/attack_hand(mob/user) var/datum/signal/signal = new - signal.transmission_method = 1 //radio signal + signal.transmission_method = TRANSMISSION_RADIO //radio signal signal.data["tag"] = master_tag signal.data["command"] = command @@ -186,7 +186,7 @@ obj/machinery/airlock_sensor/process() if(abs(pressure - previousPressure) > 0.001 || previousPressure == null) var/datum/signal/signal = new - signal.transmission_method = 1 //radio signal + signal.transmission_method = TRANSMISSION_RADIO //radio signal signal.data["tag"] = id_tag signal.data["timestamp"] = world.time signal.data["pressure"] = num2text(pressure) @@ -263,7 +263,7 @@ obj/machinery/access_button/attack_hand(mob/user) else if(radio_connection) var/datum/signal/signal = new - signal.transmission_method = 1 //radio signal + signal.transmission_method = TRANSMISSION_RADIO //radio signal signal.data["tag"] = master_tag signal.data["command"] = command diff --git a/code/game/machinery/magnet.dm b/code/game/machinery/magnet.dm index dc8e06e508..26a7936e1c 100644 --- a/code/game/machinery/magnet.dm +++ b/code/game/machinery/magnet.dm @@ -275,7 +275,7 @@ // Prepare signal beforehand, because this is a radio operation var/datum/signal/signal = new - signal.transmission_method = 1 // radio transmission + signal.transmission_method = TRANSMISSION_RADIO // radio transmission signal.source = src signal.frequency = frequency signal.data["code"] = code @@ -341,7 +341,7 @@ // Prepare the radio signal var/datum/signal/signal = new - signal.transmission_method = 1 // radio transmission + signal.transmission_method = TRANSMISSION_RADIO // radio transmission signal.source = src signal.frequency = frequency signal.data["code"] = code diff --git a/code/game/objects/items/devices/PDA/cart.dm b/code/game/objects/items/devices/PDA/cart.dm index 2a662ea5ff..1ca1ae6506 100644 --- a/code/game/objects/items/devices/PDA/cart.dm +++ b/code/game/objects/items/devices/PDA/cart.dm @@ -256,7 +256,7 @@ var/list/civilian_cartridges = list( var/datum/signal/status_signal = new status_signal.source = src - status_signal.transmission_method = 1 + status_signal.transmission_method = TRANSMISSION_RADIO status_signal.data["command"] = command switch(command) diff --git a/code/game/objects/items/devices/PDA/radio.dm b/code/game/objects/items/devices/PDA/radio.dm index 56ec64d8a7..133798e4b1 100644 --- a/code/game/objects/items/devices/PDA/radio.dm +++ b/code/game/objects/items/devices/PDA/radio.dm @@ -22,7 +22,7 @@ var/datum/signal/signal = new() signal.source = src - signal.transmission_method = 1 + signal.transmission_method = TRANSMISSION_RADIO signal.data[key] = value if(key2) signal.data[key2] = value2 diff --git a/code/game/objects/items/devices/communicator/cartridge.dm b/code/game/objects/items/devices/communicator/cartridge.dm index b1c3bd41f2..120df593e9 100644 --- a/code/game/objects/items/devices/communicator/cartridge.dm +++ b/code/game/objects/items/devices/communicator/cartridge.dm @@ -329,7 +329,7 @@ var/datum/signal/status_signal = new status_signal.source = src - status_signal.transmission_method = 1 + status_signal.transmission_method = TRANSMISSION_RADIO status_signal.data["command"] = command switch(command) diff --git a/code/game/objects/items/devices/gps.dm b/code/game/objects/items/devices/gps.dm index 0cac902796..562ed56990 100644 --- a/code/game/objects/items/devices/gps.dm +++ b/code/game/objects/items/devices/gps.dm @@ -83,7 +83,7 @@ var/list/GPS_list = list() dat["curr_z_name"] = using_map.get_zlevel_name(curr.z) dat["gps_list"] = list() dat["z_level_detection"] = using_map.get_map_levels(curr.z, long_range) - + for(var/obj/item/device/gps/G in GPS_list - src) if(!G.tracking || G.emped || G.hide_signal) continue diff --git a/code/modules/modular_computers/file_system/programs/command/comm.dm b/code/modules/modular_computers/file_system/programs/command/comm.dm index 71e7179566..fc2efa8820 100644 --- a/code/modules/modular_computers/file_system/programs/command/comm.dm +++ b/code/modules/modular_computers/file_system/programs/command/comm.dm @@ -268,7 +268,7 @@ var/datum/signal/status_signal = new status_signal.source = src - status_signal.transmission_method = 1 + status_signal.transmission_method = TRANSMISSION_RADIO status_signal.data["command"] = command switch(command) From dcf88640ee85cc94e6515e98c6b4d9f0103e5c2f Mon Sep 17 00:00:00 2001 From: Aronai Sieyes Date: Sat, 11 Apr 2020 12:55:26 -0400 Subject: [PATCH 2/7] Adds an overmap helper to find what sector you're in --- code/controllers/subsystems/shuttles.dm | 2 +- code/controllers/subsystems/skybox.dm | 2 +- code/game/turfs/turf.dm | 2 +- code/modules/overmap/_defines.dm | 145 ------------------- code/modules/overmap/helpers.dm | 5 + code/modules/overmap/overmap_shuttle.dm | 2 +- code/modules/overmap/ships/computers/ship.dm | 2 +- code/modules/overmap/ships/landable.dm | 4 +- code/modules/overmap/spacetravel.dm | 2 +- code/modules/overmap/turfs.dm | 2 +- code/modules/shuttles/landmarks.dm | 6 +- code/modules/shuttles/shuttle_autodock.dm | 2 +- code/modules/shuttles/shuttle_console.dm | 2 +- polaris.dme | 1 + 14 files changed, 20 insertions(+), 159 deletions(-) delete mode 100644 code/modules/overmap/_defines.dm create mode 100644 code/modules/overmap/helpers.dm diff --git a/code/controllers/subsystems/shuttles.dm b/code/controllers/subsystems/shuttles.dm index 907701f0bf..0bce940385 100644 --- a/code/controllers/subsystems/shuttles.dm +++ b/code/controllers/subsystems/shuttles.dm @@ -104,7 +104,7 @@ SUBSYSTEM_DEF(shuttles) 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]"] + O = get_overmap_sector(get_z(shuttle_landmark)) O ? O.add_landmark(shuttle_landmark, shuttle_landmark.shuttle_restricted) : (landmarks_awaiting_sector += shuttle_landmark) /datum/controller/subsystem/shuttles/proc/get_landmark(var/shuttle_landmark_tag) diff --git a/code/controllers/subsystems/skybox.dm b/code/controllers/subsystems/skybox.dm index 404525b439..356458517d 100644 --- a/code/controllers/subsystems/skybox.dm +++ b/code/controllers/subsystems/skybox.dm @@ -87,7 +87,7 @@ SUBSYSTEM_DEF(skybox) res.overlays += base if(global.using_map.use_overmap && settings.use_overmap_details) - var/obj/effect/overmap/visitable/O = map_sectors["[z]"] + var/obj/effect/overmap/visitable/O = get_overmap_sector(z) if(istype(O)) var/image/overmap = image(settings.icon) overmap.overlays += O.generate_skybox() diff --git a/code/game/turfs/turf.dm b/code/game/turfs/turf.dm index 75ba6dae11..78c764f789 100644 --- a/code/game/turfs/turf.dm +++ b/code/game/turfs/turf.dm @@ -248,7 +248,7 @@ var/const/enterloopsanity = 100 /turf/proc/inertial_drift(atom/movable/A as mob|obj) if(!(A.last_move)) return - if((istype(A, /mob/) && src.x > 2 && src.x < (world.maxx - 1) && src.y > 2 && src.y < (world.maxy-1))) + if((istype(A, /mob/) && src.x > 1 && src.x < (world.maxx) && src.y > 1 && src.y < (world.maxy))) var/mob/M = A if(M.Process_Spacemove(1)) M.inertia_dir = 0 diff --git a/code/modules/overmap/_defines.dm b/code/modules/overmap/_defines.dm deleted file mode 100644 index 6cfde46793..0000000000 --- a/code/modules/overmap/_defines.dm +++ /dev/null @@ -1,145 +0,0 @@ -//How far from the edge of overmap zlevel could randomly placed objects spawn -#define OVERMAP_EDGE 2 - -#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 - -//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" - initialized = FALSE // TODO - Fix unsimulated turf initialization so this override is not necessary! - -/turf/unsimulated/map/edge - opacity = 1 - density = 1 - -/turf/unsimulated/map/Initialize() - . = ..() - name = "[x]-[y]" - var/list/numbers = list() - - if(x == 1 || x == global.using_map.overmap_size) - numbers += list("[round(y/10)]","[round(y%10)]") - if(y == 1 || y == global.using_map.overmap_size) - numbers += "-" - if(y == 1 || y == global.using_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 == global.using_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 == global.using_map.overmap_size) - I.pixel_x = 5*i + 2 - add_overlay(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 - - if (moving_levels["[zlevel]"] != direction) - moving_levels["[zlevel]"] = direction - - var/list/spaceturfs = block(locate(1, 1, zlevel), locate(world.maxx, world.maxy, zlevel)) - for(var/turf/space/T in spaceturfs) - T.toggle_transit(direction) - CHECK_TICK -/* -//list used to cache empty zlevels to avoid nedless map bloat -var/list/cached_space = list() - -proc/overmap_spacetravel(var/turf/space/T, var/atom/movable/A) - var/obj/effect/map/M = map_sectors["[T.z]"] - if (!M) - return - var/mapx = M.x - var/mapy = M.y - var/nx = 1 - var/ny = 1 - var/nz = M.map_z - - if(T.x <= TRANSITIONEDGE) - nx = world.maxx - TRANSITIONEDGE - 2 - ny = rand(TRANSITIONEDGE + 2, world.maxy - TRANSITIONEDGE - 2) - mapx = max(1, mapx-1) - - else if (A.x >= (world.maxx - TRANSITIONEDGE - 1)) - nx = TRANSITIONEDGE + 2 - ny = rand(TRANSITIONEDGE + 2, world.maxy - TRANSITIONEDGE - 2) - mapx = min(world.maxx, mapx+1) - - else if (T.y <= TRANSITIONEDGE) - ny = world.maxy - TRANSITIONEDGE -2 - nx = rand(TRANSITIONEDGE + 2, world.maxx - TRANSITIONEDGE - 2) - mapy = max(1, mapy-1) - - else if (A.y >= (world.maxy - TRANSITIONEDGE - 1)) - ny = TRANSITIONEDGE + 2 - nx = rand(TRANSITIONEDGE + 2, world.maxx - TRANSITIONEDGE - 2) - mapy = min(world.maxy, mapy+1) - - testing("[A] moving from [M] ([M.x], [M.y]) to ([mapx],[mapy]).") - - var/turf/map = locate(mapx,mapy,OVERMAP_ZLEVEL) - var/obj/effect/map/TM = locate() in map - if(TM) - nz = TM.map_z - testing("Destination: [TM]") - else - if(cached_space.len) - var/obj/effect/map/sector/temporary/cache = cached_space[cached_space.len] - cached_space -= cache - nz = cache.map_z - cache.x = mapx - cache.y = mapy - testing("Destination: *cached* [TM]") - else - world.maxz++ - nz = world.maxz - TM = new /obj/effect/map/sector/temporary(mapx, mapy, nz) - testing("Destination: *new* [TM]") - - var/turf/dest = locate(nx,ny,nz) - if(dest) - A.loc = dest - - if(istype(M, /obj/effect/map/sector/temporary)) - var/obj/effect/map/sector/temporary/source = M - if (source.can_die()) - testing("Catching [M] for future use") - source.loc = null - cached_space += source -*/ \ No newline at end of file diff --git a/code/modules/overmap/helpers.dm b/code/modules/overmap/helpers.dm new file mode 100644 index 0000000000..e07be07ac0 --- /dev/null +++ b/code/modules/overmap/helpers.dm @@ -0,0 +1,5 @@ +/proc/get_overmap_sector(var/z) + if(using_map.use_overmap) + return map_sectors["[z]"] + else + return null diff --git a/code/modules/overmap/overmap_shuttle.dm b/code/modules/overmap/overmap_shuttle.dm index c3195c99de..b614137db4 100644 --- a/code/modules/overmap/overmap_shuttle.dm +++ b/code/modules/overmap/overmap_shuttle.dm @@ -1,4 +1,4 @@ -#define waypoint_sector(waypoint) map_sectors["[waypoint.z]"] +#define waypoint_sector(waypoint) get_overmap_sector(get_z(waypoint)) /datum/shuttle/autodock/overmap warmup_time = 10 diff --git a/code/modules/overmap/ships/computers/ship.dm b/code/modules/overmap/ships/computers/ship.dm index da05a9908b..6e6304f1f8 100644 --- a/code/modules/overmap/ships/computers/ship.dm +++ b/code/modules/overmap/ships/computers/ship.dm @@ -17,7 +17,7 @@ somewhere on that shuttle. Subtypes of these can be then used to perform ship ov return 1 /obj/machinery/computer/ship/proc/sync_linked(var/user = null) - var/obj/effect/overmap/visitable/ship/sector = map_sectors["[z]"] + var/obj/effect/overmap/visitable/ship/sector = get_overmap_sector(z) if(!sector) return . = attempt_hook_up_recursive(sector) diff --git a/code/modules/overmap/ships/landable.dm b/code/modules/overmap/ships/landable.dm index 967fdeb571..0a28eefd8d 100644 --- a/code/modules/overmap/ships/landable.dm +++ b/code/modules/overmap/ships/landable.dm @@ -80,7 +80,7 @@ . = ..() /obj/effect/shuttle_landmark/ship/Destroy() - var/obj/effect/overmap/visitable/ship/landable/ship = map_sectors["[z]"] + var/obj/effect/overmap/visitable/ship/landable/ship = get_overmap_sector(z) if(istype(ship) && ship.landmark == src) ship.landmark = null . = ..() @@ -141,7 +141,7 @@ 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/obj/effect/overmap/visitable/target = get_overmap_sector(get_z(into)) var/datum/shuttle/shuttle_datum = SSshuttles.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 SSshuttles.ships) diff --git a/code/modules/overmap/spacetravel.dm b/code/modules/overmap/spacetravel.dm index adb7e80269..aec5c06f75 100644 --- a/code/modules/overmap/spacetravel.dm +++ b/code/modules/overmap/spacetravel.dm @@ -57,7 +57,7 @@ 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]"] + var/obj/effect/overmap/visitable/M = get_overmap_sector(T.z) if (!M) return diff --git a/code/modules/overmap/turfs.dm b/code/modules/overmap/turfs.dm index 3508203a6e..fc9f0b16eb 100644 --- a/code/modules/overmap/turfs.dm +++ b/code/modules/overmap/turfs.dm @@ -1,7 +1,7 @@ //Dimension of overmap (squares 4 lyfe) var/global/list/map_sectors = list() -/area/overmap/ +/area/overmap name = "System Map" icon_state = "start" requires_power = 0 diff --git a/code/modules/shuttles/landmarks.dm b/code/modules/shuttles/landmarks.dm index 99761dfe25..38f2d41f25 100644 --- a/code/modules/shuttles/landmarks.dm +++ b/code/modules/shuttles/landmarks.dm @@ -55,14 +55,14 @@ if(!istype(docking_controller)) log_error("Could not find docking controller for shuttle waypoint '[name]', docking tag was '[docking_tag]'.") if(using_map.use_overmap) - var/obj/effect/overmap/visitable/location = map_sectors["[z]"] + var/obj/effect/overmap/visitable/location = get_overmap_sector(z) if(location && location.docking_codes) docking_controller.docking_codes = location.docking_codes /obj/effect/shuttle_landmark/forceMove() - var/obj/effect/overmap/visitable/map_origin = map_sectors["[z]"] + var/obj/effect/overmap/visitable/map_origin = get_overmap_sector(z) . = ..() - var/obj/effect/overmap/visitable/map_destination = map_sectors["[z]"] + var/obj/effect/overmap/visitable/map_destination = get_overmap_sector(z) if(map_origin != map_destination) if(map_origin) map_origin.remove_landmark(src, shuttle_restricted) diff --git a/code/modules/shuttles/shuttle_autodock.dm b/code/modules/shuttles/shuttle_autodock.dm index b9b1801205..4181af5b15 100644 --- a/code/modules/shuttles/shuttle_autodock.dm +++ b/code/modules/shuttles/shuttle_autodock.dm @@ -30,7 +30,7 @@ if(active_docking_controller) set_docking_codes(active_docking_controller.docking_codes) else if(global.using_map.use_overmap) - var/obj/effect/overmap/visitable/location = map_sectors["[current_location.z]"] + var/obj/effect/overmap/visitable/location = get_overmap_sector(get_z(current_location)) if(location && location.docking_codes) set_docking_codes(location.docking_codes) dock() diff --git a/code/modules/shuttles/shuttle_console.dm b/code/modules/shuttles/shuttle_console.dm index e07166799b..6d277edec1 100644 --- a/code/modules/shuttles/shuttle_console.dm +++ b/code/modules/shuttles/shuttle_console.dm @@ -166,7 +166,7 @@ GLOBAL_LIST_BOILERPLATE(papers_dockingcode, /obj/item/weapon/paper/dockingcodes) var/dockingcodes = null var/z_to_check = codes_from_z ? codes_from_z : z if(using_map.use_overmap) - var/obj/effect/overmap/visitable/location = map_sectors["[z_to_check]"] + var/obj/effect/overmap/visitable/location = get_overmap_sector(z_to_check) if(location && location.docking_codes) dockingcodes = location.docking_codes diff --git a/polaris.dme b/polaris.dme index 1b6cc35def..cb0f0b1a79 100644 --- a/polaris.dme +++ b/polaris.dme @@ -2435,6 +2435,7 @@ #include "code\modules\organs\subtypes\unseverable.dm" #include "code\modules\organs\subtypes\vox.dm" #include "code\modules\organs\subtypes\xenos.dm" +#include "code\modules\overmap\helpers.dm" #include "code\modules\overmap\overmap_object.dm" #include "code\modules\overmap\overmap_shuttle.dm" #include "code\modules\overmap\sectors.dm" From 65cfc5bd39507f063297b670a056b9c261a8eade Mon Sep 17 00:00:00 2001 From: Aronai Sieyes Date: Sat, 11 Apr 2020 12:56:57 -0400 Subject: [PATCH 3/7] Refactor telecomms to send messages to logically adjacent zlevels --- code/_helpers/game.dm | 2 +- code/defines/procs/announce.dm | 36 ++- code/game/machinery/cryopod.dm | 3 +- code/game/machinery/telecomms/broadcaster.dm | 200 ++++++++++---- .../telecomms/machine_interactions.dm | 32 +++ .../machinery/telecomms/telecomunications.dm | 51 +++- .../game/objects/items/devices/radio/radio.dm | 245 ++++++++++-------- .../objects/items/devices/radio/radiopack.dm | 153 +++++++++++ code/modules/admin/verbs/randomverbs.dm | 2 +- code/modules/mob/new_player/new_player.dm | 9 +- .../hardware/network_card.dm | 31 ++- icons/mob/back.dmi | Bin 104533 -> 105146 bytes icons/obj/radio.dmi | Bin 19757 -> 20967 bytes maps/~map_system/maps.dm | 44 +++- polaris.dme | 1 + 15 files changed, 598 insertions(+), 211 deletions(-) create mode 100644 code/game/objects/items/devices/radio/radiopack.dm diff --git a/code/_helpers/game.dm b/code/_helpers/game.dm index f35061b3c0..7be9093596 100644 --- a/code/_helpers/game.dm +++ b/code/_helpers/game.dm @@ -246,7 +246,7 @@ var/turf/speaker = get_turf(R) if(speaker) for(var/turf/T in hear(R.canhear_range,speaker)) - speaker_coverage[T] = T + speaker_coverage[T] = R // Try to find all the players who can hear the message diff --git a/code/defines/procs/announce.dm b/code/defines/procs/announce.dm index 0b4aafda94..ec9327b2dc 100644 --- a/code/defines/procs/announce.dm +++ b/code/defines/procs/announce.dm @@ -30,7 +30,7 @@ title = "Security Announcement" announcement_type = "Security Announcement" -/datum/announcement/proc/Announce(var/message as text, var/new_title = "", var/new_sound = null, var/do_newscast = newscast, var/msg_sanitized = 0) +/datum/announcement/proc/Announce(var/message as text, var/new_title = "", var/new_sound = null, var/do_newscast = newscast, var/msg_sanitized = 0, var/zlevel) if(!message) return var/message_title = new_title ? new_title : title @@ -40,13 +40,17 @@ message = sanitize(message, extra = 0) message_title = sanitizeSafe(message_title) - Message(message, message_title) + var/list/zlevels + if(zlevel) + zlevels = using_map.get_map_levels(zlevel, TRUE) + + Message(message, message_title, zlevels) if(do_newscast) NewsCast(message, message_title) - Sound(message_sound) + Sound(message_sound, zlevels) Log(message, message_title) -datum/announcement/proc/Message(message as text, message_title as text) +datum/announcement/proc/Message(message as text, message_title as text, var/list/zlevels) for(var/mob/M in player_list) if(!istype(M,/mob/new_player) && !isdeaf(M)) to_chat(M, "

[title]

") @@ -54,6 +58,7 @@ datum/announcement/proc/Message(message as text, message_title as text) if (announcer) to_chat(M, " -[html_encode(announcer)]") +// You'll need to update these to_world usages if you want to make these z-level specific ~Aro datum/announcement/minor/Message(message as text, message_title as text) to_world("[message]") @@ -64,7 +69,7 @@ datum/announcement/priority/Message(message as text, message_title as text) to_world(" -[html_encode(announcer)]") to_world("
") -datum/announcement/priority/command/Message(message as text, message_title as text) +datum/announcement/priority/command/Message(message as text, message_title as text, var/list/zlevels) var/command command += "

[command_name()] Update

" if (message_title) @@ -73,6 +78,8 @@ datum/announcement/priority/command/Message(message as text, message_title as te command += "
[message]
" command += "
" for(var/mob/M in player_list) + if(zlevels && !(get_z(M) in zlevels)) + continue if(!istype(M,/mob/new_player) && !isdeaf(M)) to_chat(M, command) @@ -92,15 +99,18 @@ datum/announcement/proc/NewsCast(message as text, message_title as text) news.can_be_redacted = 0 announce_newscaster_news(news) -datum/announcement/proc/PlaySound(var/message_sound) +datum/announcement/proc/PlaySound(var/message_sound, var/list/zlevels) if(!message_sound) return + for(var/mob/M in player_list) + if(zlevels && !(M.z in zlevels)) + continue if(!istype(M,/mob/new_player) && !isdeaf(M)) M << message_sound -datum/announcement/proc/Sound(var/message_sound) - PlaySound(message_sound) +datum/announcement/proc/Sound(var/message_sound, var/list/zlevels) + PlaySound(message_sound, zlevels) datum/announcement/priority/Sound(var/message_sound) if(message_sound) @@ -124,12 +134,12 @@ datum/announcement/proc/Log(message as text, message_title as text) /proc/ion_storm_announcement() command_announcement.Announce("It has come to our attention that \the [station_name()] passed through an ion storm. Please monitor all electronic equipment for malfunctions.", "Anomaly Alert") -/proc/AnnounceArrival(var/mob/living/carbon/human/character, var/rank, var/join_message) +/proc/AnnounceArrival(var/mob/living/carbon/human/character, var/rank, var/join_message, var/channel = "Common", var/zlevel) if (ticker.current_state == GAME_STATE_PLAYING) + var/list/zlevels = zlevel ? using_map.get_map_levels(zlevel, TRUE) : null if(character.mind.role_alt_title) rank = character.mind.role_alt_title - AnnounceArrivalSimple(character.real_name, rank, join_message) + AnnounceArrivalSimple(character.real_name, rank, join_message, channel, zlevels) - -/proc/AnnounceArrivalSimple(var/name, var/rank = "visitor", var/join_message = "will arrive to the station shortly by shuttle", new_sound = 'sound/misc/notice3.ogg') - global_announcer.autosay("[name], [rank], [join_message].", "Arrivals Announcement Computer") +/proc/AnnounceArrivalSimple(var/name, var/rank = "visitor", var/join_message = "will arrive at the station shortly", var/channel = "Common", var/list/zlevels) + global_announcer.autosay("[name], [rank], [join_message].", "Arrivals Announcement Computer", channel, zlevels) \ No newline at end of file diff --git a/code/game/machinery/cryopod.dm b/code/game/machinery/cryopod.dm index cad7e07c24..aa21a2d1d5 100644 --- a/code/game/machinery/cryopod.dm +++ b/code/game/machinery/cryopod.dm @@ -215,6 +215,7 @@ var/on_enter_occupant_message = "You feel cool air surround you. You go numb as your senses turn inward." var/on_store_visible_message_1 = "hums and hisses as it moves" //We need two variables because byond doesn't let us have variables inside strings at compile-time. var/on_store_visible_message_2 = "into storage." + var/announce_channel = "Common" var/allow_occupant_types = list(/mob/living/carbon/human) var/disallow_occupant_types = list() @@ -467,7 +468,7 @@ control_computer._admin_logs += "[key_name(to_despawn)] ([to_despawn.mind.role_alt_title]) at [stationtime2text()]" log_and_message_admins("[key_name(to_despawn)] ([to_despawn.mind.role_alt_title]) entered cryostorage.") - announce.autosay("[to_despawn.real_name], [to_despawn.mind.role_alt_title], [on_store_message]", "[on_store_name]") + announce.autosay("[to_despawn.real_name], [to_despawn.mind.role_alt_title], [on_store_message]", "[on_store_name]", announce_channel, using_map.get_map_levels(z, TRUE)) //visible_message("\The [initial(name)] hums and hisses as it moves [to_despawn.real_name] into storage.", 3) visible_message("\The [initial(name)] [on_store_visible_message_1] [to_despawn.real_name] [on_store_visible_message_2].", 3) diff --git a/code/game/machinery/telecomms/broadcaster.dm b/code/game/machinery/telecomms/broadcaster.dm index f22bf58a9f..8a4c88b51a 100644 --- a/code/game/machinery/telecomms/broadcaster.dm +++ b/code/game/machinery/telecomms/broadcaster.dm @@ -23,6 +23,12 @@ var/message_delay = 0 // To make sure restarting the recentmessages list is kept produces_heat = 0 delay = 7 circuit = /obj/item/weapon/circuitboard/telecomms/broadcaster + //Vars only used if you're using the overmap + var/overmap_range = 0 + var/overmap_range_min = 0 + var/overmap_range_max = 5 + //Linked bluespace radios + var/list/linked_radios_weakrefs = list() /obj/machinery/telecomms/processor/Initialize() . = ..() @@ -34,6 +40,11 @@ var/message_delay = 0 // To make sure restarting the recentmessages list is kept component_parts += new /obj/item/weapon/stock_parts/micro_laser/high(src) component_parts += new /obj/item/stack/cable_coil(src, 1) +/obj/machinery/telecomms/broadcaster/proc/link_radio(var/obj/item/device/radio/R) + if(!istype(R)) + return + linked_radios_weakrefs |= weakref(R) + /obj/machinery/telecomms/broadcaster/receive_information(datum/signal/signal, obj/machinery/telecomms/machine_from) // Don't broadcast rejected signals if(signal.data["reject"]) @@ -58,46 +69,50 @@ var/message_delay = 0 // To make sure restarting the recentmessages list is kept if(signal.data["slow"] > 0) sleep(signal.data["slow"]) // simulate the network lag if necessary - signal.data["level"] |= listening_level + signal.data["level"] |= using_map.get_map_levels(listening_level, TRUE, overmap_range) + + var/list/forced_radios + for(var/weakref/wr in linked_radios_weakrefs) + var/obj/item/device/radio/R = wr.resolve() + if(istype(R)) + LAZYDISTINCTADD(forced_radios, R) /** #### - Normal Broadcast - #### **/ - - if(signal.data["type"] == 0) - + if(signal.data["type"] == SIGNAL_NORMAL) /* ###### Broadcast a message using signal.data ###### */ Broadcast_Message(signal.data["connection"], signal.data["mob"], signal.data["vmask"], signal.data["vmessage"], signal.data["radio"], signal.data["message"], signal.data["name"], signal.data["job"], - signal.data["realname"], signal.data["vname"],, + signal.data["realname"], signal.data["vname"], DATA_NORMAL, signal.data["compression"], signal.data["level"], signal.frequency, - signal.data["verb"]) - + signal.data["verb"], forced_radios) /** #### - Simple Broadcast - #### **/ - if(signal.data["type"] == 1) + if(signal.data["type"] == SIGNAL_SIMPLE) /* ###### Broadcast a message using signal.data ###### */ Broadcast_SimpleMessage(signal.data["name"], signal.frequency, - signal.data["message"],null, null, - signal.data["compression"], listening_level) + signal.data["message"], DATA_NORMAL, null, + signal.data["compression"], listening_level, forced_radios) /** #### - Artificial Broadcast - #### **/ // (Imitates a mob) - if(signal.data["type"] == 2) + if(signal.data["type"] == SIGNAL_FAKE) /* ###### Broadcast a message using signal.data ###### */ - // Parameter "data" as 4: AI can't track this person/mob + // Parameter "data" as DATA_FAKE: AI can't track this person/mob Broadcast_Message(signal.data["connection"], signal.data["mob"], signal.data["vmask"], signal.data["vmessage"], signal.data["radio"], signal.data["message"], signal.data["name"], signal.data["job"], - signal.data["realname"], signal.data["vname"], 4, signal.data["compression"], signal.data["level"], signal.frequency, - signal.data["verb"]) + signal.data["realname"], signal.data["vname"], DATA_FAKE, + signal.data["compression"], signal.data["level"], signal.frequency, + signal.data["verb"], forced_radios) if(!message_delay) message_delay = 1 @@ -118,6 +133,7 @@ var/message_delay = 0 // To make sure restarting the recentmessages list is kept /* Basically just an empty shell for receiving and broadcasting radio messages. Not very flexible, but it gets the job done. + NOTE: This AIO device listens on *every* zlevel (it does not even check) */ /obj/machinery/telecomms/allinone @@ -126,15 +142,98 @@ var/message_delay = 0 // To make sure restarting the recentmessages list is kept icon_state = "comm_server" desc = "A compact machine used for portable subspace telecommuniations processing." density = 1 + use_power = USE_POWER_IDLE + idle_power_usage = 20 anchored = 1 - use_power = USE_POWER_OFF - idle_power_usage = 0 machinetype = 6 produces_heat = 0 var/intercept = 0 // if nonzero, broadcasts all messages to syndicate channel + var/overmap_range = 0 //Same turf + + var/list/linked_radios_weakrefs = list() + +/obj/machinery/telecomms/allinone/proc/link_radio(var/obj/item/device/radio/R) + if(!istype(R)) + return + linked_radios_weakrefs |= weakref(R) /obj/machinery/telecomms/allinone/receive_signal(datum/signal/signal) + // Has to be on to receive messages + if(!on) + return + + // Why did you use this subtype? + if(!using_map.use_overmap) + return + + // Someone else handling it? + if(signal.data["done"]) + return + + // Where are we able to hear from (and talk to, since we're AIO) anyway? + var/map_levels = using_map.get_map_levels(z, TRUE, overmap_range) + + //Bluespace can skip this check + if(signal.transmission_method != TRANSMISSION_BLUESPACE) + var/list/signal_levels = list() + signal_levels += signal.data["level"] //If it's text/number, it'll be the only entry, if it's a list, it'll get combined + var/list/overlap = map_levels & signal_levels //Returns a list of similar levels + if(!overlap.len) + return + + if(is_freq_listening(signal)) // detect subspace signals + + signal.data["done"] = 1 // mark the signal as being broadcasted since we're a broadcaster + signal.data["compression"] = 0 // decompress since we're a processor + + // Search for the original signal and mark it as done as well + var/datum/signal/original = signal.data["original"] + if(original) + original.data["done"] = 1 + + // For some reason level is both used as a list and not a list, and now it needs to be a list. + signal.data["level"] = map_levels + + if(signal.data["slow"] > 0) + sleep(signal.data["slow"]) // simulate the network lag if necessary + + /* ###### Broadcast a message using signal.data ###### */ + + var/datum/radio_frequency/connection = signal.data["connection"] + + var/list/forced_radios + for(var/weakref/wr in linked_radios_weakrefs) + var/obj/item/device/radio/R = wr.resolve() + if(istype(R)) + LAZYDISTINCTADD(forced_radios, R) + + Broadcast_Message( + signal.data["connection"], + signal.data["mob"], + signal.data["vmask"], + signal.data["vmessage"], + signal.data["radio"], + signal.data["message"], + signal.data["name"], + signal.data["job"], + signal.data["realname"], + signal.data["vname"], + DATA_NORMAL, + signal.data["compression"], + signal.data["level"], + connection.frequency, + signal.data["verb"], + signal.data["language"], + forced_radios + ) + +//Antag version with unlimited range (doesn't even check) and uses no power, to enable antag comms to work anywhere. +/obj/machinery/telecomms/allinone/antag + use_power = USE_POWER_OFF + idle_power_usage = 0 + +/obj/machinery/telecomms/allinone/antag/receive_signal(datum/signal/signal) if(!on) // has to be on to receive messages return @@ -159,23 +258,29 @@ var/message_delay = 0 // To make sure restarting the recentmessages list is kept var/datum/radio_frequency/connection = signal.data["connection"] + var/list/forced_radios + for(var/weakref/wr in linked_radios_weakrefs) + var/obj/item/device/radio/R = wr.resolve() + if(istype(R)) + LAZYDISTINCTADD(forced_radios, R) + if(connection.frequency in ANTAG_FREQS) // if antag broadcast, just Broadcast_Message(signal.data["connection"], signal.data["mob"], signal.data["vmask"], signal.data["vmessage"], signal.data["radio"], signal.data["message"], signal.data["name"], signal.data["job"], - signal.data["realname"], signal.data["vname"],, signal.data["compression"], list(0), connection.frequency, - signal.data["verb"]) + signal.data["realname"], signal.data["vname"], DATA_NORMAL, + signal.data["compression"], list(0), connection.frequency, + signal.data["verb"], forced_radios) else if(intercept) Broadcast_Message(signal.data["connection"], signal.data["mob"], signal.data["vmask"], signal.data["vmessage"], signal.data["radio"], signal.data["message"], signal.data["name"], signal.data["job"], - signal.data["realname"], signal.data["vname"], 3, signal.data["compression"], list(0), connection.frequency, - signal.data["verb"]) - - + signal.data["realname"], signal.data["vname"], DATA_ANTAG, + signal.data["compression"], list(0), connection.frequency, + signal.data["verb"], forced_radios) /** @@ -237,8 +342,8 @@ var/message_delay = 0 // To make sure restarting the recentmessages list is kept /proc/Broadcast_Message(var/datum/radio_frequency/connection, var/mob/M, var/vmask, var/list/vmessage_pieces, var/obj/item/device/radio/radio, var/list/message_pieces, var/name, var/job, var/realname, var/vname, - var/data, var/compression, var/list/level, var/freq, var/verbage = "says") - + var/data, var/compression, var/list/level, var/freq, var/verbage = "says", + var/list/forced_radios) /* ###### Prepare the radio connection ###### */ @@ -246,17 +351,22 @@ var/message_delay = 0 // To make sure restarting the recentmessages list is kept var/list/obj/item/device/radio/radios = list() + for(var/obj/item/device/radio/R in forced_radios) + //Cursory check to ensure they are 'on' and stuff + if(R.receive_range(display_freq, list(0))) + radios |= R + // --- Broadcast only to intercom devices --- - if(data == 1) + if(data == DATA_INTERCOM) for (var/obj/item/device/radio/intercom/R in connection.devices["[RADIO_CHAT]"]) if(R.receive_range(display_freq, level) > -1) - radios += R + radios |= R // --- Broadcast only to intercoms and station-bounced radios --- - else if(data == 2) + else if(data == DATA_LOCAL) for (var/obj/item/device/radio/R in connection.devices["[RADIO_CHAT]"]) @@ -264,16 +374,16 @@ var/message_delay = 0 // To make sure restarting the recentmessages list is kept continue if(R.receive_range(display_freq, level) > -1) - radios += R + radios |= R // --- Broadcast to antag radios! --- - else if(data == 3) + else if(data == DATA_ANTAG) for(var/antag_freq in ANTAG_FREQS) var/datum/radio_frequency/antag_connection = radio_controller.return_frequency(antag_freq) for (var/obj/item/device/radio/R in antag_connection.devices["[RADIO_CHAT]"]) if(R.receive_range(antag_freq, level) > -1) - radios += R + radios |= R // --- Broadcast to ALL radio devices --- @@ -281,7 +391,7 @@ var/message_delay = 0 // To make sure restarting the recentmessages list is kept for (var/obj/item/device/radio/R in connection.devices["[RADIO_CHAT]"]) if(R.receive_range(display_freq, level) > -1) - radios += R + radios |= R // Get a list of mobs who can hear from the radios we collected. var/list/receive = get_mobs_in_radio_ranges(radios) @@ -307,7 +417,7 @@ var/message_delay = 0 // To make sure restarting the recentmessages list is kept continue // Ghosts hearing all radio chat don't want to hear syndicate intercepts, they're duplicates - if(data == 3 && istype(R, /mob/observer/dead) && R.is_preference_enabled(/datum/client_preference/ghost_radio)) + if(data == DATA_ANTAG && istype(R, /mob/observer/dead) && R.is_preference_enabled(/datum/client_preference/ghost_radio)) continue // --- Check for compression --- @@ -346,7 +456,7 @@ var/message_delay = 0 // To make sure restarting the recentmessages list is kept var/freq_text = get_frequency_name(display_freq) var/part_b_extra = "" - if(data == 3) // intercepted radio message + if(data == DATA_ANTAG) // intercepted radio message part_b_extra = " (Intercepted)" var/part_a = "[bicon(radio)]\[[freq_text]\][part_b_extra] " // goes in the actual output @@ -429,7 +539,7 @@ var/message_delay = 0 // To make sure restarting the recentmessages list is kept return 1 -/proc/Broadcast_SimpleMessage(var/source, var/frequency, list/message_pieces, var/data, var/mob/M, var/compression, var/level) +/proc/Broadcast_SimpleMessage(var/source, var/frequency, list/message_pieces, var/data, var/mob/M, var/compression, var/level, var/list/forced_radios) var/text = multilingual_to_message(message_pieces) /* ###### Prepare the radio connection ###### */ @@ -443,10 +553,12 @@ var/message_delay = 0 // To make sure restarting the recentmessages list is kept var/list/receive = list() + for(var/obj/item/device/radio/R in forced_radios) + receive |= R.send_hear(display_freq) // --- Broadcast only to intercom devices --- - if(data == 1) + if(data == DATA_INTERCOM) for (var/obj/item/device/radio/intercom/R in connection.devices["[RADIO_CHAT]"]) var/turf/position = get_turf(R) if(position && position.z == level) @@ -455,7 +567,7 @@ var/message_delay = 0 // To make sure restarting the recentmessages list is kept // --- Broadcast only to intercoms and station-bounced radios --- - else if(data == 2) + else if(data == DATA_LOCAL) for (var/obj/item/device/radio/R in connection.devices["[RADIO_CHAT]"]) if(istype(R, /obj/item/device/radio/headset)) @@ -467,7 +579,7 @@ var/message_delay = 0 // To make sure restarting the recentmessages list is kept // --- Broadcast to antag radios! --- - else if(data == 3) + else if(data == DATA_ANTAG) for(var/freq in ANTAG_FREQS) var/datum/radio_frequency/antag_connection = radio_controller.return_frequency(freq) for (var/obj/item/device/radio/R in antag_connection.devices["[RADIO_CHAT]"]) @@ -532,7 +644,7 @@ var/message_delay = 0 // To make sure restarting the recentmessages list is kept // --- Some more pre-message formatting --- var/part_b_extra = "" - if(data == 3) // intercepted radio message + if(data == DATA_ANTAG) // intercepted radio message part_b_extra = " (Intercepted)" // Create a radio headset for the sole purpose of using its icon @@ -608,15 +720,15 @@ var/message_delay = 0 // To make sure restarting the recentmessages list is kept /atom/proc/test_telecomms() var/datum/signal/signal = src.telecomms_process() - var/turf/position = get_turf(src) - return (position.z in signal.data["level"] && signal.data["done"]) + var/pos_z = get_z(src) + return (pos_z in signal.data["level"] && signal.data["done"]) /atom/proc/telecomms_process(var/do_sleep = 1) // First, we want to generate a new radio signal var/datum/signal/signal = new - signal.transmission_method = 2 // 2 would be a subspace transmission. - var/turf/pos = get_turf(src) + signal.transmission_method = TRANSMISSION_SUBSPACE + var/pos_z = get_z(src) // --- Finally, tag the actual signal with the appropriate values --- signal.data = list( @@ -624,10 +736,10 @@ var/message_delay = 0 // To make sure restarting the recentmessages list is kept "message" = "TEST", "compression" = rand(45, 50), // If the signal is compressed, compress our message too. "traffic" = 0, // dictates the total traffic sum that the signal went through - "type" = 4, // determines what type of radio input it is: test broadcast + "type" = SIGNAL_TEST, // determines what type of radio input it is: test broadcast "reject" = 0, "done" = 0, - "level" = pos.z // The level it is being broadcasted at. + "level" = pos_z // The level it is being broadcasted at. ) signal.frequency = PUB_FREQ// Common channel diff --git a/code/game/machinery/telecomms/machine_interactions.dm b/code/game/machinery/telecomms/machine_interactions.dm index 3e3d89643a..802c75bb42 100644 --- a/code/game/machinery/telecomms/machine_interactions.dm +++ b/code/game/machinery/telecomms/machine_interactions.dm @@ -219,6 +219,38 @@ temp = "-% Frequency changing deactivated %-" +// BROADCASTER +/obj/machinery/telecomms/broadcaster/Options_Menu() + // Note the machine 'displays' 1 higher than overmap_range to save users from the abstraction that range '0' is valid and everything on the same turf. + var/dat = "
Broadcast Range (affects power usage)
- [overmap_range+1] gigameter\s +" + return dat + +/obj/machinery/telecomms/broadcaster/Options_Topic(href, href_list) + if(href_list["range_down"]) + if(overmap_range > overmap_range_min) + overmap_range-- + idle_power_usage = initial(idle_power_usage)**(overmap_range+1) + if(href_list["range_up"]) + if(overmap_range < overmap_range_max) + overmap_range++ + idle_power_usage = initial(idle_power_usage)**(overmap_range+1) + +// RECEIVER +/obj/machinery/telecomms/receiver/Options_Menu() + // Note the machine 'displays' 1 higher than overmap_range to save users from the abstraction that range '0' is valid and everything on the same turf. + var/dat = "
Receive Range (affects power usage)
- [overmap_range+1] gigameter\s +" + return dat + +/obj/machinery/telecomms/receiver/Options_Topic(href, href_list) + if(href_list["range_down"]) + if(overmap_range > overmap_range_min) + overmap_range-- + idle_power_usage = initial(idle_power_usage)**(overmap_range+1) + if(href_list["range_up"]) + if(overmap_range < overmap_range_max) + overmap_range++ + idle_power_usage = initial(idle_power_usage)**(overmap_range+1) + /obj/machinery/telecomms/Topic(href, href_list) if(!issilicon(usr)) diff --git a/code/game/machinery/telecomms/telecomunications.dm b/code/game/machinery/telecomms/telecomunications.dm index ff073359de..55a06fd234 100644 --- a/code/game/machinery/telecomms/telecomunications.dm +++ b/code/game/machinery/telecomms/telecomunications.dm @@ -68,7 +68,7 @@ var/global/list/obj/machinery/telecomms/telecomms_list = list() var/datum/signal/copy if(copysig) copy = new - copy.transmission_method = 2 + copy.transmission_method = TRANSMISSION_SUBSPACE copy.frequency = signal.frequency copy.data = signal.data.Copy() @@ -141,9 +141,9 @@ var/global/list/obj/machinery/telecomms/telecomms_list = list() // Used in auto linking /obj/machinery/telecomms/proc/add_link(var/obj/machinery/telecomms/T) - var/turf/position = get_turf(src) - var/turf/T_position = get_turf(T) - if((position.z == T_position.z) || (src.long_range_link && T.long_range_link)) + var/pos_z = get_z(src) + var/tpos_z = get_z(T) + if((pos_z == tpos_z) || (src.long_range_link && T.long_range_link)) for(var/x in autolinkers) if(T.autolinkers.Find(x)) if(src != T) @@ -256,6 +256,12 @@ var/global/list/obj/machinery/telecomms/telecomms_list = list() machinetype = 1 produces_heat = 0 circuit = /obj/item/weapon/circuitboard/telecomms/receiver + //Vars only used if you're using the overmap + var/overmap_range = 0 + var/overmap_range_min = 0 + var/overmap_range_max = 5 + + var/list/linked_radios_weakrefs = list() /obj/machinery/telecomms/receiver/Initialize() . = ..() @@ -267,8 +273,12 @@ var/global/list/obj/machinery/telecomms/telecomms_list = list() component_parts += new /obj/item/weapon/stock_parts/micro_laser(src) RefreshParts() -/obj/machinery/telecomms/receiver/receive_signal(datum/signal/signal) +/obj/machinery/telecomms/receiver/proc/link_radio(var/obj/item/device/radio/R) + if(!istype(R)) + return + linked_radios_weakrefs |= weakref(R) +/obj/machinery/telecomms/receiver/receive_signal(datum/signal/signal) if(!on) // has to be on to receive messages return if(!signal) @@ -276,7 +286,7 @@ var/global/list/obj/machinery/telecomms/telecomms_list = list() if(!check_receive_level(signal)) return - if(signal.transmission_method == 2) + if(signal.transmission_method == TRANSMISSION_SUBSPACE) if(is_freq_listening(signal)) // detect subspace signals @@ -288,14 +298,31 @@ var/global/list/obj/machinery/telecomms/telecomms_list = list() relay_information(signal, "/obj/machinery/telecomms/bus") // Send it to a bus instead, if it's linked to one /obj/machinery/telecomms/receiver/proc/check_receive_level(datum/signal/signal) + // If it's a direct message from a bluespace radio, we eat it and convert it into a subspace signal locally + if(signal.transmission_method == TRANSMISSION_BLUESPACE) + var/obj/item/device/radio/R = signal.data["radio"] - if(signal.data["level"] != listening_level) + //Who're you? + if(!(weakref(R) in linked_radios_weakrefs)) + signal.data["reject"] = 1 + return 0 + + //We'll resend this for you + signal.data["level"] = z + signal.transmission_method = TRANSMISSION_SUBSPACE + return 1 + + //Where can we hear? + var/list/listening_levels = using_map.get_map_levels(listening_level, TRUE, overmap_range) + + // We couldn't 'hear' it, maybe a relay linked to our hub can 'hear' it + if(!(signal.data["level"] in listening_levels)) for(var/obj/machinery/telecomms/hub/H in links) - var/list/connected_levels = list() + var/list/relayed_levels = list() for(var/obj/machinery/telecomms/relay/R in H.links) if(R.can_receive(signal)) - connected_levels |= R.listening_level - if(signal.data["level"] in connected_levels) + relayed_levels |= R.listening_level + if(signal.data["level"] in relayed_levels) return 1 return 0 return 1 @@ -405,7 +432,7 @@ var/global/list/obj/machinery/telecomms/telecomms_list = list() // Add our level and send it back if(can_send(signal)) - signal.data["level"] |= listening_level + signal.data["level"] |= using_map.get_map_levels(listening_level) // Checks to see if it can send/receive. @@ -601,7 +628,7 @@ var/global/list/obj/machinery/telecomms/telecomms_list = list() totaltraffic += traffic // add current traffic to total traffic //Is this a test signal? Bypass logging - if(signal.data["type"] != 4) + if(signal.data["type"] != SIGNAL_TEST) // If signal has a message and appropriate frequency diff --git a/code/game/objects/items/devices/radio/radio.dm b/code/game/objects/items/devices/radio/radio.dm index 80a2b5e4a8..706d72455f 100644 --- a/code/game/objects/items/devices/radio/radio.dm +++ b/code/game/objects/items/devices/radio/radio.dm @@ -49,6 +49,13 @@ var/global/list/default_medbay_channels = list( w_class = ITEMSIZE_SMALL show_messages = 1 + // Bluespace radios talk directly to telecomms equipment + var/bluespace_radio = FALSE + var/weakref/bs_tx_weakref //Maybe misleading, this is the device to TRANSMIT TO + // For mappers or subtypes, to start them prelinked to these devices + var/bs_tx_preload_id + var/bs_rx_preload_id + matter = list("glass" = 25,DEFAULT_WALL_MATERIAL = 75) var/const/FREQ_LISTENING = 1 var/list/internal_channels @@ -87,6 +94,43 @@ var/global/list/default_medbay_channels = list( for (var/ch_name in channels) secure_radio_connections[ch_name] = radio_controller.add_object(src, radiochannels[ch_name], RADIO_CHAT) + if(bluespace_radio) + if(bs_tx_preload_id) + //Try to find a receiver + for(var/obj/machinery/telecomms/receiver/RX in telecomms_list) + if(RX.id == bs_tx_preload_id) //Again, bs_tx is the thing to TRANSMIT TO, so a receiver. + bs_tx_weakref = weakref(RX) + RX.link_radio(src) + break + //Hmm, howabout an AIO machine + if(!bs_tx_weakref) + for(var/obj/machinery/telecomms/allinone/AIO in telecomms_list) + if(AIO.id == bs_tx_preload_id) + bs_tx_weakref = weakref(AIO) + AIO.link_radio(src) + break + if(!bs_tx_weakref) + testing("A radio [src] at [x],[y],[z] specified bluespace prelink IDs, but the machines with corresponding IDs ([bs_tx_preload_id], [bs_rx_preload_id]) couldn't be found.") + + if(bs_rx_preload_id) + var/found = 0 + //Try to find a transmitter + for(var/obj/machinery/telecomms/broadcaster/TX in telecomms_list) + if(TX.id == bs_rx_preload_id) //Again, bs_rx is the thing to RECEIVE FROM, so a transmitter. + TX.link_radio(src) + found = 1 + break + //Hmm, howabout an AIO machine + if(!found) + for(var/obj/machinery/telecomms/allinone/AIO in telecomms_list) + if(AIO.id == bs_rx_preload_id) + AIO.link_radio(src) + found = 1 + break + if(!found) + testing("A radio [src] at [x],[y],[z] specified bluespace prelink IDs, but the machines with corresponding IDs ([bs_tx_preload_id], [bs_rx_preload_id]) couldn't be found.") + + /obj/item/device/radio/attack_self(mob/user as mob) user.set_machine(src) interact(user) @@ -237,11 +281,10 @@ var/global/list/default_medbay_channels = list( if(.) SSnanoui.update_uis(src) -/obj/item/device/radio/proc/autosay(var/message, var/from, var/channel) //BS12 EDIT +/obj/item/device/radio/proc/autosay(var/message, var/from, var/channel, var/list/zlevels) //BS12 EDIT var/datum/radio_frequency/connection = null if(channel && channels && channels.len > 0) if(channel == "department") - //to_world("DEBUG: channel=\"[channel]\" switching to \"[channels[1]]\"") channel = channels[1] connection = secure_radio_connections[channel] else @@ -250,12 +293,15 @@ var/global/list/default_medbay_channels = list( if(!istype(connection)) return + if(!LAZYLEN(zlevels)) + zlevels = list(0) + var/static/mob/living/silicon/ai/announcer/A = new /mob/living/silicon/ai/announcer(src, null, null, 1) A.SetName(from) Broadcast_Message(connection, A, 0, "*garbled automated announcement*", src, message_to_multilingual(message), from, "Automated Announcement", from, "synthesized voice", - 4, 0, list(0), connection.frequency, "states") + DATA_FAKE, 0, zlevels, connection.frequency, "states") // Interprets the message mode when talking into a radio, possibly returning a connection datum /obj/item/device/radio/proc/handle_message_mode(mob/living/M as mob, list/message_pieces, message_mode) @@ -314,7 +360,7 @@ var/global/list/default_medbay_channels = list( return FALSE var/datum/radio_frequency/connection = message_mode - var/turf/position = get_turf(src) + var/pos_z = get_z(src) //#### Tagging the signal with all appropriate identity values ####// @@ -363,11 +409,62 @@ var/global/list/default_medbay_channels = list( jobname = "Unknown" voicemask = 1 + // First, we want to generate a new radio signal + var/datum/signal/signal = new + // --- Finally, tag the actual signal with the appropriate values --- + signal.data = list( + // Identity-associated tags: + "mob" = M, // store a reference to the mob + "mobtype" = M.type, // the mob's type + "realname" = real_name, // the mob's real name + "name" = displayname, // the mob's display name + "job" = jobname, // the mob's job + "key" = mobkey, // the mob's key + "vmessage" = pick(M.speak_emote), // the message to display if the voice wasn't understood + "vname" = M.voice_name, // the name to display if the voice wasn't understood + "vmask" = voicemask, // 1 if the mob is using a voice gas mask - /* ###### Radio headsets can only broadcast through subspace ###### */ + // We store things that would otherwise be kept in the actual mob + // so that they can be logged even AFTER the mob is deleted or something - if(subspace_transmission) + // Other tags: + "compression" = rand(45,50), // compressed radio signal + "message" = message_pieces, // the actual sent message + "connection" = connection, // the radio connection to use + "radio" = src, // stores the radio used for transmission + "slow" = 0, // how much to sleep() before broadcasting - simulates net lag + "traffic" = 0, // dictates the total traffic sum that the signal went through + "type" = SIGNAL_NORMAL, // determines what type of radio input it is: normal broadcast + "server" = null, // the last server to log this signal + "reject" = 0, // if nonzero, the signal will not be accepted by any broadcasting machinery + "level" = pos_z, // The source's z level + "verb" = verb + ) + signal.frequency = connection.frequency // Quick frequency set + + var/filter_type = DATA_LOCAL //If we end up having to send it the old fashioned way, it's with this data var. + + /* ###### Bluespace radios talk directly to receivers (and only directly to receivers) ###### */ + if(bluespace_radio) + //Nothing to transmit to + if(!bs_tx_weakref) + to_chat(loc, "\The [src] buzzes to inform you of the lack of a functioning connection.") + return FALSE + + var/obj/machinery/telecomms/tx_to = bs_tx_weakref.resolve() + //Was linked, now destroyed or something + if(!tx_to) + bs_tx_weakref = null + to_chat(loc, "\The [src] buzzes to inform you of the lack of a functioning connection.") + return FALSE + + //Transmitted in the blind. If we get a message back, cool. If not, oh well. + signal.transmission_method = TRANSMISSION_BLUESPACE + return tx_to.receive_signal(signal) + + /* ###### Radios with subspace_transmission can only broadcast through subspace (unless they have adhoc_fallback) ###### */ + else if(subspace_transmission) var/list/jamming = is_jammed(src) if(jamming) var/distance = jamming["distance"] @@ -375,42 +472,9 @@ var/global/list/default_medbay_channels = list( return FALSE // First, we want to generate a new radio signal - var/datum/signal/signal = new - signal.transmission_method = 2 // 2 would be a subspace transmission. - // transmission_method could probably be enumerated through #define. Would be neater. + signal.transmission_method = TRANSMISSION_SUBSPACE - // --- Finally, tag the actual signal with the appropriate values --- - signal.data = list( - // Identity-associated tags: - "mob" = M, // store a reference to the mob - "mobtype" = M.type, // the mob's type - "realname" = real_name, // the mob's real name - "name" = displayname, // the mob's display name - "job" = jobname, // the mob's job - "key" = mobkey, // the mob's key - "vmessage" = pick(M.speak_emote), // the message to display if the voice wasn't understood - "vname" = M.voice_name, // the name to display if the voice wasn't understood - "vmask" = voicemask, // 1 if the mob is using a voice gas mask - - // We store things that would otherwise be kept in the actual mob - // so that they can be logged even AFTER the mob is deleted or something - - // Other tags: - "compression" = rand(45,50), // compressed radio signal - "message" = message_pieces, // the actual sent message - "connection" = connection, // the radio connection to use - "radio" = src, // stores the radio used for transmission - "slow" = 0, // how much to sleep() before broadcasting - simulates net lag - "traffic" = 0, // dictates the total traffic sum that the signal went through - "type" = 0, // determines what type of radio input it is: normal broadcast - "server" = null, // the last server to log this signal - "reject" = 0, // if nonzero, the signal will not be accepted by any broadcasting machinery - "level" = position.z, // The source's z level - "verb" = verb - ) - signal.frequency = connection.frequency // Quick frequency set - - //#### Sending the signal to all subspace receivers ####// + //#### Sending the signal to all subspace receivers ####// for(var/obj/machinery/telecomms/receiver/R in telecomms_list) R.receive_signal(signal) @@ -420,75 +484,47 @@ var/global/list/default_medbay_channels = list( R.receive_signal(signal) // Receiving code can be located in Telecommunications.dm - if(signal.data["done"] && position.z in signal.data["level"]) + if(signal.data["done"] && (pos_z in signal.data["level"])) return TRUE //Huzzah, sent via subspace else if(adhoc_fallback) //Less huzzah, we have to fallback to_chat(loc, "\The [src] pings as it falls back to local radio transmission.") subspace_transmission = FALSE - return Broadcast_Message(connection, M, voicemask, pick(M.speak_emote), - src, message_pieces, displayname, jobname, real_name, M.voice_name, - signal.transmission_method, signal.data["compression"], GetConnectedZlevels(position.z), connection.frequency,verb) + + else //Oh well + return FALSE - /* ###### Intercoms and station-bounced radios ###### */ + /* ###### Intercoms and station-bounced radios ###### */ + else + /* --- Intercoms can only broadcast to other intercoms, but bounced radios can broadcast to bounced radios and intercoms --- */ + if(istype(src, /obj/item/device/radio/intercom)) + filter_type = DATA_INTERCOM - var/filter_type = 2 + /* --- Try to send a normal subspace broadcast first */ + signal.transmission_method = TRANSMISSION_SUBSPACE + signal.data["compression"] = 0 - /* --- Intercoms can only broadcast to other intercoms, but bounced radios can broadcast to bounced radios and intercoms --- */ - if(istype(src, /obj/item/device/radio/intercom)) - filter_type = 1 + for(var/obj/machinery/telecomms/receiver/R in telecomms_list) + R.receive_signal(signal) - - var/datum/signal/signal = new - signal.transmission_method = 2 - - - /* --- Try to send a normal subspace broadcast first */ - - signal.data = list( - - "mob" = M, // store a reference to the mob - "mobtype" = M.type, // the mob's type - "realname" = real_name, // the mob's real name - "name" = displayname, // the mob's display name - "job" = jobname, // the mob's job - "key" = mobkey, // the mob's key - "vmessage" = pick(M.speak_emote), // the message to display if the voice wasn't understood - "vname" = M.voice_name, // the name to display if the voice wasn't understood - "vmask" = voicemask, // 1 if the mob is using a voice gas mas - - "compression" = 0, // uncompressed radio signal - "message" = message_pieces, // the actual sent message - "connection" = connection, // the radio connection to use - "radio" = src, // stores the radio used for transmission - "slow" = 0, - "traffic" = 0, - "type" = 0, - "server" = null, - "reject" = 0, - "level" = position.z, - "verb" = verb - ) - signal.frequency = connection.frequency // Quick frequency set + // Allinone can act as receivers. + for(var/obj/machinery/telecomms/allinone/R in telecomms_list) + R.receive_signal(signal) for(var/obj/machinery/telecomms/receiver/R in telecomms_list) R.receive_signal(signal) - if(signal.data["done"] && position.z in signal.data["level"]) - if(adhoc_fallback) - to_chat(loc, "\The [src] pings as it reestablishes subspace communications.") - subspace_transmission = TRUE - // we're done here. - return TRUE + if(signal.data["done"] && pos_z in signal.data["level"]) + if(adhoc_fallback) + to_chat(loc, "\The [src] pings as it reestablishes subspace communications.") + subspace_transmission = TRUE + // we're done here. + return TRUE - // Oh my god; the comms are down or something because the signal hasn't been broadcasted yet in our level. - // Send a mundane broadcast with limited targets: - - //THIS IS TEMPORARY. YEAH RIGHT - if(!connection) return FALSE //~Carn + //Nothing handled any sort of remote radio-ing and returned before now, just squawk on this zlevel. return Broadcast_Message(connection, M, voicemask, pick(M.speak_emote), - src, message_pieces, displayname, jobname, real_name, M.voice_name, - filter_type, signal.data["compression"], GetConnectedZlevels(position.z), connection.frequency, verb) + src, message_pieces, displayname, jobname, real_name, M.voice_name, + filter_type, signal.data["compression"], using_map.get_map_levels(pos_z), connection.frequency, verb) /obj/item/device/radio/hear_talk(mob/M, list/message_pieces, verb) @@ -496,25 +532,10 @@ var/global/list/default_medbay_channels = list( if(get_dist(src, M) <= canhear_range) talk_into(M, message_pieces, null, verb) - -/* -/obj/item/device/radio/proc/accept_rad(obj/item/device/radio/R as obj, message) - - if((R.frequency == frequency && message)) - return TRUE - else if - - else - return null - return -*/ - - /obj/item/device/radio/proc/receive_range(freq, level) // check if this radio can receive on the given frequency, and if so, // what the range is in which mobs will hear the radio // returns: -1 if can't receive, range otherwise - if(wires.IsIndexCut(WIRE_RECEIVE)) return -1 if(!listening) @@ -522,8 +543,8 @@ var/global/list/default_medbay_channels = list( if(is_jammed(src)) return -1 if(!(0 in level)) - var/turf/position = get_turf(src) - if(!position || !(position.z in level)) + var/pos_z = get_z(src) + if(!(pos_z in level)) return -1 if(freq in ANTAG_FREQS) if(!(src.syndie))//Checks to see if it's allowed on that frequency, based on the encryption keys diff --git a/code/game/objects/items/devices/radio/radiopack.dm b/code/game/objects/items/devices/radio/radiopack.dm new file mode 100644 index 0000000000..c2ba25ecc2 --- /dev/null +++ b/code/game/objects/items/devices/radio/radiopack.dm @@ -0,0 +1,153 @@ +/obj/item/device/bluespaceradio + name = "bluespace radio" + desc = "A powerful radio that uses a tiny bluespace wormhole to send signals directly to subspace receivers and transmitters, bypassing the limitations of subspace." + icon = 'icons/obj/radio.dmi' + icon_state = "radiopack" + item_state = "radiopack" + slot_flags = SLOT_BACK + force = 5 + throwforce = 6 + preserve_item = 1 + w_class = ITEMSIZE_LARGE + action_button_name = "Remove/Replace Handset" + + var/obj/item/device/radio/bluespacehandset/linked/handset = /obj/item/device/radio/bluespacehandset/linked + +/obj/item/device/bluespaceradio/Initialize() + . = ..() + if(ispath(handset)) + handset = new handset(src, src) + +/obj/item/device/bluespaceradio/Destroy() + . = ..() + QDEL_NULL(handset) + +/obj/item/device/bluespaceradio/ui_action_click() + toggle_handset() + +/obj/item/device/bluespaceradio/attack_hand(var/mob/user) + if(loc == user) + toggle_handset() + else + ..() + +/obj/item/device/bluespaceradio/MouseDrop() + if(ismob(loc)) + if(!CanMouseDrop(src)) + return + var/mob/M = loc + if(!M.unEquip(src)) + return + add_fingerprint(usr) + M.put_in_any_hand_if_possible(src) + +/obj/item/device/bluespaceradio/attackby(var/obj/item/weapon/W, var/mob/user, var/params) + if(W == handset) + reattach_handset(user) + else + return ..() + +/obj/item/device/bluespaceradio/verb/toggle_handset() + set name = "Toggle Handset" + set category = "Object" + + var/mob/living/carbon/human/user = usr + if(!handset) + to_chat(user, "The handset is missing!") + return + + if(handset.loc != src) + reattach_handset(user) //Remove from their hands and back onto the defib unit + return + + if(!slot_check()) + to_chat(user, "You need to equip [src] before taking out [handset].") + else + if(!usr.put_in_hands(handset)) //Detach the handset into the user's hands + to_chat(user, "You need a free hand to hold the handset!") + update_icon() //success + +//checks that the base unit is in the correct slot to be used +/obj/item/device/bluespaceradio/proc/slot_check() + var/mob/M = loc + if(!istype(M)) + return 0 //not equipped + + if((slot_flags & SLOT_BACK) && M.get_equipped_item(slot_back) == src) + return 1 + if((slot_flags & SLOT_BACK) && M.get_equipped_item(slot_s_store) == src) + return 1 + + return 0 + +/obj/item/device/bluespaceradio/dropped(var/mob/user) + ..() + reattach_handset(user) //handset attached to a base unit should never exist outside of their base unit or the mob equipping the base unit + +/obj/item/device/bluespaceradio/proc/reattach_handset(var/mob/user) + if(!handset) return + + if(ismob(handset.loc)) + var/mob/M = handset.loc + if(M.drop_from_inventory(handset, src)) + to_chat(user, "\The [handset] snaps back into the main unit.") + else + handset.forceMove(src) + +//Subspace Radio Handset +/obj/item/device/radio/bluespacehandset + name = "bluespace radio handset" + desc = "A large walkie talkie attached to the bluespace radio by a retractable cord. It sits comfortably on a slot in the radio when not in use." + bluespace_radio = TRUE + icon_state = "signaller" + slot_flags = null + w_class = ITEMSIZE_LARGE + canhear_range = 1 + +/obj/item/device/radio/bluespacehandset/linked + var/obj/item/device/bluespaceradio/base_unit + +/obj/item/device/radio/bluespacehandset/linked/Initialize(mapload, var/obj/item/device/bluespaceradio/radio) + base_unit = radio + . = ..() + +/obj/item/device/radio/bluespacehandset/linked/Destroy() + if(base_unit) + //ensure the base unit's icon updates + if(base_unit.handset == src) + base_unit.handset = null + base_unit = null + return ..() + +/obj/item/device/radio/bluespacehandset/linked/dropped(var/mob/user) + ..() //update twohanding + if(base_unit) + base_unit.reattach_handset(user) //handset attached to a base unit should never exist outside of their base unit or the mob equipping the base unit + +/obj/item/device/radio/bluespacehandset/linked/receive_range(var/freq, var/list/level) + //Only care about megabroadcasts or things that are targeted at us + if(!(0 in level)) + return -1 + if(wires.IsIndexCut(WIRE_RECEIVE)) + return -1 + if(!listening) + return -1 + if(is_jammed(src)) + return -1 + if (!on) + return -1 + if (!freq) //recieved on main frequency + if (!listening) + return -1 + else + var/accept = (freq==frequency && listening) + if (!accept) + for (var/ch_name in channels) + var/datum/radio_frequency/RF = secure_radio_connections[ch_name] + if (RF && RF.frequency==freq && (channels[ch_name]&FREQ_LISTENING)) + accept = 1 + break + if (!accept) + return -1 + + return canhear_range diff --git a/code/modules/admin/verbs/randomverbs.dm b/code/modules/admin/verbs/randomverbs.dm index 4c129fbf73..9b7b676d0e 100644 --- a/code/modules/admin/verbs/randomverbs.dm +++ b/code/modules/admin/verbs/randomverbs.dm @@ -505,7 +505,7 @@ Traitors and the like can also be revived with the previous role mostly intact. //If we're announcing their arrival if(announce) - AnnounceArrival(new_character, new_character.mind.assigned_role) + AnnounceArrival(new_character, new_character.mind.assigned_role, "Common", new_character.z) log_admin("[admin] has spawned [player_key]'s character [new_character.real_name].") message_admins("[admin] has spawned [player_key]'s character [new_character.real_name].", 1) diff --git a/code/modules/mob/new_player/new_player.dm b/code/modules/mob/new_player/new_player.dm index d38b40ccf1..58ecb4a31b 100644 --- a/code/modules/mob/new_player/new_player.dm +++ b/code/modules/mob/new_player/new_player.dm @@ -413,18 +413,19 @@ //Grab some data from the character prefs for use in random news procs. - AnnounceArrival(character, rank, join_message) + AnnounceArrival(character, rank, join_message, announce_channel, character.z) else - AnnounceCyborg(character, rank, join_message) + AnnounceCyborg(character, rank, join_message, announce_channel, character.z) qdel(src) -/mob/new_player/proc/AnnounceCyborg(var/mob/living/character, var/rank, var/join_message) +/mob/new_player/proc/AnnounceCyborg(var/mob/living/character, var/rank, var/join_message, var/channel, var/zlevel) if (ticker.current_state == GAME_STATE_PLAYING) + var/list/zlevels = zlevel ? using_map.get_map_levels(zlevel, TRUE) : null if(character.mind.role_alt_title) rank = character.mind.role_alt_title // can't use their name here, since cyborg namepicking is done post-spawn, so we'll just say "A new Cyborg has arrived"/"A new Android has arrived"/etc. - global_announcer.autosay("A new[rank ? " [rank]" : " visitor" ] [join_message ? join_message : "has arrived on the station"].", "Arrivals Announcement Computer") + global_announcer.autosay("A new[rank ? " [rank]" : " visitor" ] [join_message ? join_message : "has arrived on the station"].", "Arrivals Announcement Computer", channel, zlevels) /mob/new_player/proc/LateChoices() var/name = client.prefs.be_random_name ? "friend" : client.prefs.real_name diff --git a/code/modules/modular_computers/hardware/network_card.dm b/code/modules/modular_computers/hardware/network_card.dm index 69f4a8bb64..2f382e0f4d 100644 --- a/code/modules/modular_computers/hardware/network_card.dm +++ b/code/modules/modular_computers/hardware/network_card.dm @@ -79,20 +79,25 @@ var/global/ntnet_card_uid = 1 return 0 if(holder2) - var/turf/T = get_turf(holder2) - if(!istype(T)) //no reception in nullspace + var/holderz = get_z(holder2) + if(!holderz) //no reception in nullspace return 0 - if(T.z in using_map.station_levels) - // Computer is on station. Low/High signal depending on what type of network card you have - if(long_range) - return 2 - else - return 1 - if(T.z in using_map.contact_levels) //not on station, but close enough for radio signal to travel - if(long_range) // Computer is not on station, but it has upgraded network card. Low signal. - return 1 - - return 0 // Computer is not on station and does not have upgraded network card. No signal. + var/list/zlevels_in_range = using_map.get_map_levels(holderz, long_range) + var/best = 0 + for(var/relay in ntnet_global.relays) + var/obj/machinery/ntnet_relay/R = relay + //Relay is down + if(!R.operable()) + continue + //We're on the same z + if(R.z == holderz) + best = 2 + break // No point in going further + //Not on the same z but within range anyway + if(R.z in zlevels_in_range) + best = 1 + return best + return 0 // No computer! /obj/item/weapon/computer_hardware/network_card/Destroy() if(holder2 && (holder2.network_card == src)) diff --git a/icons/mob/back.dmi b/icons/mob/back.dmi index 99f3f4fd4c9c5ab0912ef0cc17fe54270164246e..5555d0e1adec3814686d5cb88905f5bd62e65347 100644 GIT binary patch delta 18889 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 delta 18271 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 Date: Sat, 11 Apr 2020 13:15:24 -0400 Subject: [PATCH 4/7] Refactor consoles to operate on logically adjacent zlevels --- code/datums/repositories/cameras.dm | 19 ++++++++++-- code/game/machinery/camera/camera.dm | 2 ++ code/game/machinery/camera/presets.dm | 1 + code/game/machinery/computer/camera.dm | 10 +++---- code/modules/alarm/alarm_handler.dm | 29 ++++++++++++------- code/modules/alarm/atmosphere_alarm.dm | 10 +++++-- .../programs/engineering/alarm_monitor.dm | 15 ++++++---- .../programs/engineering/atmos_control.dm | 8 +++-- .../programs/engineering/power_monitor.dm | 8 +++-- .../programs/engineering/rcon_console.dm | 8 +++++ .../engineering/supermatter_monitor.dm | 6 ++-- .../file_system/programs/generic/camera.dm | 10 ++++++- .../programs/medical/suit_sensors.dm | 13 ++++++--- nano/templates/sec_camera.tmpl | 3 +- 14 files changed, 104 insertions(+), 38 deletions(-) diff --git a/code/datums/repositories/cameras.dm b/code/datums/repositories/cameras.dm index 7bac2a4634..d516113394 100644 --- a/code/datums/repositories/cameras.dm +++ b/code/datums/repositories/cameras.dm @@ -14,10 +14,25 @@ var/global/datum/repository/cameras/camera_repository = new() networks = list() ..() -/datum/repository/cameras/proc/cameras_in_network(var/network) +/datum/repository/cameras/proc/cameras_in_network(var/network, var/list/zlevels) setup_cache() var/list/network_list = networks[network] - return network_list + if(LAZYLEN(zlevels)) + var/list/filtered_cameras = list() + for(var/list/C in network_list) + //Camera is marked as always-visible + if(C["omni"]) + filtered_cameras[++filtered_cameras.len] = C + continue + //Camera might be in an adjacent zlevel + var/camz = C["z"] + if(!camz) //It's inside something (helmet, communicator, etc) or nullspace or who knows + camz = get_z(locate(C["camera"]) in cameranet.cameras) + if(camz in zlevels) + filtered_cameras[++filtered_cameras.len] = C //Can't add lists to lists with += + return filtered_cameras + else + return network_list /datum/repository/cameras/proc/setup_cache() if(!invalidated) diff --git a/code/game/machinery/camera/camera.dm b/code/game/machinery/camera/camera.dm index 9fc72b0a17..27e7b5a7bd 100644 --- a/code/game/machinery/camera/camera.dm +++ b/code/game/machinery/camera/camera.dm @@ -33,6 +33,7 @@ var/busy = 0 var/on_open_network = 0 + var/always_visible = FALSE //Visable from any map, good for entertainment network cameras var/affected_by_emp_until = 0 @@ -467,6 +468,7 @@ cam["name"] = sanitize(c_tag) cam["deact"] = !can_use() cam["camera"] = "\ref[src]" + cam["omni"] = always_visible cam["x"] = x cam["y"] = y cam["z"] = z diff --git a/code/game/machinery/camera/presets.dm b/code/game/machinery/camera/presets.dm index 66ad38cf36..15b5f7abca 100644 --- a/code/game/machinery/camera/presets.dm +++ b/code/game/machinery/camera/presets.dm @@ -102,6 +102,7 @@ var/global/list/engineering_networks = list( /obj/machinery/camera/network/thunder network = list(NETWORK_THUNDER) invuln = 1 + always_visible = TRUE // EMP diff --git a/code/game/machinery/computer/camera.dm b/code/game/machinery/computer/camera.dm index f1ba39c7b2..11d1660746 100644 --- a/code/game/machinery/computer/camera.dm +++ b/code/game/machinery/computer/camera.dm @@ -43,11 +43,14 @@ data["current_camera"] = current_camera ? current_camera.nano_structure() : null data["current_network"] = current_network data["networks"] = network ? network : list() + + var/map_levels = using_map.get_map_levels(src.z, TRUE) + data["map_levels"] = map_levels + if(current_network) - data["cameras"] = camera_repository.cameras_in_network(current_network) + data["cameras"] = camera_repository.cameras_in_network(current_network, map_levels) if(current_camera) switch_to_camera(user, current_camera) - data["map_levels"] = using_map.get_map_levels(src.z) ui = SSnanoui.try_update_ui(user, src, ui_key, ui, data, force_open) if (!ui) @@ -91,9 +94,6 @@ . = ..() /obj/machinery/computer/security/attack_hand(var/mob/user as mob) - if (using_map && !(src.z in using_map.contact_levels)) - to_chat(user, "Unable to establish a connection: You're too far away from the station!") - return if(stat & (NOPOWER|BROKEN)) return if(!isAI(user)) diff --git a/code/modules/alarm/alarm_handler.dm b/code/modules/alarm/alarm_handler.dm index 22b069093a..cbec4c25b6 100644 --- a/code/modules/alarm/alarm_handler.dm +++ b/code/modules/alarm/alarm_handler.dm @@ -47,16 +47,17 @@ existing.clear(source) return check_alarm_cleared(existing) -/datum/alarm_handler/proc/major_alarms() - return visible_alarms() +/datum/alarm_handler/proc/major_alarms(var/z) + return visible_alarms(z) -/datum/alarm_handler/proc/has_major_alarms() - if(alarms && alarms.len) - return 1 - return 0 +/datum/alarm_handler/proc/has_major_alarms(var/z) + if(!LAZYLEN(alarms)) + return 0 -/datum/alarm_handler/proc/minor_alarms() - return visible_alarms() + return LAZYLEN(major_alarms(z)) + +/datum/alarm_handler/proc/minor_alarms(var/z) + return visible_alarms(z) /datum/alarm_handler/proc/check_alarm_cleared(var/datum/alarm/alarm) if ((alarm.end_time && world.time > alarm.end_time) || !alarm.sources.len) @@ -101,9 +102,15 @@ for(var/listener in listeners) call(listener, listeners[listener])(src, alarm, was_raised) -/datum/alarm_handler/proc/visible_alarms() +/datum/alarm_handler/proc/visible_alarms(var/z) + if(!LAZYLEN(alarms)) + return list() + + var/list/map_levels = using_map.get_map_levels(z) + var/list/visible_alarms = new() for(var/datum/alarm/A in alarms) - if(!A.hidden) - visible_alarms.Add(A) + if(A.hidden || (z && !(A.origin?.z in map_levels))) + continue + visible_alarms.Add(A) return visible_alarms \ No newline at end of file diff --git a/code/modules/alarm/atmosphere_alarm.dm b/code/modules/alarm/atmosphere_alarm.dm index 94f2e91e05..60e0529956 100644 --- a/code/modules/alarm/atmosphere_alarm.dm +++ b/code/modules/alarm/atmosphere_alarm.dm @@ -1,16 +1,22 @@ /datum/alarm_handler/atmosphere category = "Atmosphere Alarms" -/datum/alarm_handler/atmosphere/major_alarms() +/datum/alarm_handler/atmosphere/major_alarms(var/z) var/list/major_alarms = new() + var/list/map_levels = using_map.get_map_levels(z) for(var/datum/alarm/A in visible_alarms()) + if(z && (z && !(A.origin?.z in map_levels))) + continue if(A.max_severity() > 1) major_alarms.Add(A) return major_alarms -/datum/alarm_handler/atmosphere/minor_alarms() +/datum/alarm_handler/atmosphere/minor_alarms(var/z) var/list/minor_alarms = new() + var/list/map_levels = using_map.get_map_levels(z) for(var/datum/alarm/A in visible_alarms()) + if(z && (z && !(A.origin?.z in map_levels))) + continue if(A.max_severity() == 1) minor_alarms.Add(A) return minor_alarms diff --git a/code/modules/modular_computers/file_system/programs/engineering/alarm_monitor.dm b/code/modules/modular_computers/file_system/programs/engineering/alarm_monitor.dm index c887034754..bb45191f2f 100644 --- a/code/modules/modular_computers/file_system/programs/engineering/alarm_monitor.dm +++ b/code/modules/modular_computers/file_system/programs/engineering/alarm_monitor.dm @@ -61,31 +61,35 @@ AH.unregister_alarm(object) /datum/nano_module/alarm_monitor/proc/all_alarms() + var/z = get_z(nano_host()) var/list/all_alarms = new() for(var/datum/alarm_handler/AH in alarm_handlers) - all_alarms += AH.visible_alarms() + all_alarms += AH.visible_alarms(z) return all_alarms /datum/nano_module/alarm_monitor/proc/major_alarms() + var/z = get_z(nano_host()) var/list/all_alarms = new() for(var/datum/alarm_handler/AH in alarm_handlers) - all_alarms += AH.major_alarms() + all_alarms += AH.major_alarms(z) return all_alarms // Modified version of above proc that uses slightly less resources, returns 1 if there is a major alarm, 0 otherwise. /datum/nano_module/alarm_monitor/proc/has_major_alarms() + var/z = get_z(nano_host()) for(var/datum/alarm_handler/AH in alarm_handlers) - if(AH.has_major_alarms()) + if(AH.has_major_alarms(z)) return 1 return 0 /datum/nano_module/alarm_monitor/proc/minor_alarms() + var/z = get_z(nano_host()) var/list/all_alarms = new() for(var/datum/alarm_handler/AH in alarm_handlers) - all_alarms += AH.minor_alarms() + all_alarms += AH.minor_alarms(z) return all_alarms @@ -104,9 +108,10 @@ var/list/data = host.initial_data() var/categories[0] + var/z = get_z(nano_host()) for(var/datum/alarm_handler/AH in alarm_handlers) categories[++categories.len] = list("category" = AH.category, "alarms" = list()) - for(var/datum/alarm/A in AH.major_alarms()) + for(var/datum/alarm/A in AH.major_alarms(z)) var/cameras[0] var/lost_sources[0] diff --git a/code/modules/modular_computers/file_system/programs/engineering/atmos_control.dm b/code/modules/modular_computers/file_system/programs/engineering/atmos_control.dm index 776e73ec7a..bfcaadee3e 100644 --- a/code/modules/modular_computers/file_system/programs/engineering/atmos_control.dm +++ b/code/modules/modular_computers/file_system/programs/engineering/atmos_control.dm @@ -47,12 +47,17 @@ /datum/nano_module/atmos_control/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1, var/master_ui = null, var/datum/topic_state/state = default_state) var/list/data = host.initial_data() var/alarms[0] - var/turf/T = get_turf(nano_host()) + + var/z = get_z(nano_host()) + var/list/map_levels = using_map.get_map_levels(z) + data["map_levels"] = map_levels // TODO: Move these to a cache, similar to cameras for(var/obj/machinery/alarm/alarm in (monitored_alarms.len ? monitored_alarms : machines)) if(!monitored_alarms.len && alarm.alarms_hidden) continue + if(!(alarm.z in map_levels)) + continue alarms[++alarms.len] = list( "name" = sanitize(alarm.name), "ref"= "\ref[alarm]", @@ -61,7 +66,6 @@ "y" = alarm.y, "z" = alarm.z) data["alarms"] = alarms - data["map_levels"] = using_map.get_map_levels(T.z) ui = SSnanoui.try_update_ui(user, src, ui_key, ui, data, force_open) if(!ui) diff --git a/code/modules/modular_computers/file_system/programs/engineering/power_monitor.dm b/code/modules/modular_computers/file_system/programs/engineering/power_monitor.dm index 32117e845c..b5c086dc26 100644 --- a/code/modules/modular_computers/file_system/programs/engineering/power_monitor.dm +++ b/code/modules/modular_computers/file_system/programs/engineering/power_monitor.dm @@ -53,10 +53,15 @@ var/list/sensors = list() // Focus: If it remains null if no sensor is selected and UI will display sensor list, otherwise it will display sensor reading. var/obj/machinery/power/sensor/focus = null - var/turf/T = get_turf(nano_host()) + + var/z = get_z(nano_host()) + var/list/map_levels = using_map.get_map_levels(z) + data["map_levels"] = map_levels // Build list of data from sensor readings. for(var/obj/machinery/power/sensor/S in grid_sensors) + if(!(S.z in map_levels)) + continue sensors.Add(list(list( "name" = S.name_tag, "alarm" = S.check_grid_warning() @@ -67,7 +72,6 @@ data["all_sensors"] = sensors if(focus) data["focus"] = focus.return_reading_data() - data["map_levels"] = using_map.get_map_levels(T.z) ui = SSnanoui.try_update_ui(user, src, ui_key, ui, data, force_open) if (!ui) diff --git a/code/modules/modular_computers/file_system/programs/engineering/rcon_console.dm b/code/modules/modular_computers/file_system/programs/engineering/rcon_console.dm index d5ee88b670..7603b1115e 100644 --- a/code/modules/modular_computers/file_system/programs/engineering/rcon_console.dm +++ b/code/modules/modular_computers/file_system/programs/engineering/rcon_console.dm @@ -122,11 +122,19 @@ // Description: Refreshes local list of known devices. /datum/nano_module/rcon/proc/FindDevices() known_SMESs = new /list() + + var/z = get_z(nano_host()) + var/list/map_levels = using_map.get_map_levels(z) + for(var/obj/machinery/power/smes/buildable/SMES in machines) + if(!(SMES.z in map_levels)) + continue if(SMES.RCon_tag && (SMES.RCon_tag != "NO_TAG") && SMES.RCon) known_SMESs.Add(SMES) known_breakers = new /list() for(var/obj/machinery/power/breakerbox/breaker in machines) + if(!(breaker.z in map_levels)) + continue if(breaker.RCon_tag != "NO_TAG") known_breakers.Add(breaker) diff --git a/code/modules/modular_computers/file_system/programs/engineering/supermatter_monitor.dm b/code/modules/modular_computers/file_system/programs/engineering/supermatter_monitor.dm index 78a774be0b..421bb65047 100644 --- a/code/modules/modular_computers/file_system/programs/engineering/supermatter_monitor.dm +++ b/code/modules/modular_computers/file_system/programs/engineering/supermatter_monitor.dm @@ -41,10 +41,10 @@ // Refreshes list of active supermatter crystals /datum/nano_module/supermatter_monitor/proc/refresh() supermatters = list() - var/turf/T = get_turf(nano_host()) - if(!T) + var/z = get_z(nano_host()) + if(!z) return - var/valid_z_levels = (GetConnectedZlevels(T.z) & using_map.station_levels) + var/valid_z_levels = using_map.get_map_levels(z) for(var/obj/machinery/power/supermatter/S in machines) // Delaminating, not within coverage, not on a tile. if(S.grav_pulling || S.exploded || !(S.z in valid_z_levels) || !istype(S.loc, /turf/)) diff --git a/code/modules/modular_computers/file_system/programs/generic/camera.dm b/code/modules/modular_computers/file_system/programs/generic/camera.dm index a722dea6b6..e07d9ec293 100644 --- a/code/modules/modular_computers/file_system/programs/generic/camera.dm +++ b/code/modules/modular_computers/file_system/programs/generic/camera.dm @@ -15,6 +15,12 @@ return access_research if(NETWORK_ERT) return access_cent_specops + //VOREStation Add Start + if(NETWORK_TALON_SHIP) + return access_talon + if(NETWORK_TALON_HELMETS) + return access_talon + //VOREStation Add End if(network in using_map.station_networks) return access_security // Default for all other station networks @@ -62,8 +68,10 @@ data["networks"] = all_networks + var/list/map_levels = using_map.get_map_levels(get_z(nano_host()), TRUE) + if(current_network) - data["cameras"] = camera_repository.cameras_in_network(current_network) + data["cameras"] = camera_repository.cameras_in_network(current_network, map_levels) ui = SSnanoui.try_update_ui(user, src, ui_key, ui, data, force_open) if (!ui) diff --git a/code/modules/modular_computers/file_system/programs/medical/suit_sensors.dm b/code/modules/modular_computers/file_system/programs/medical/suit_sensors.dm index a332a064dc..bf8fe50909 100644 --- a/code/modules/modular_computers/file_system/programs/medical/suit_sensors.dm +++ b/code/modules/modular_computers/file_system/programs/medical/suit_sensors.dm @@ -34,16 +34,21 @@ /datum/nano_module/crew_monitor/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1, var/datum/topic_state/state = default_state) var/list/data = host.initial_data() - var/turf/T = get_turf(nano_host()) data["isAI"] = isAI(user) - data["map_levels"] = using_map.get_map_levels(T.z, FALSE) + + var/z = get_z(nano_host()) + var/list/map_levels = using_map.get_map_levels(z, TRUE) + data["map_levels"] = map_levels + data["crewmembers"] = list() - for(var/z in (data["map_levels"] | T.z)) // Always show crew from the current Z even if we can't show a map - data["crewmembers"] += crew_repository.health_data(z) + for(var/zlevel in map_levels) + data["crewmembers"] += crew_repository.health_data(zlevel) if(!data["map_levels"].len) to_chat(user, "The crew monitor doesn't seem like it'll work here.") + if(ui) + ui.close() return ui = SSnanoui.try_update_ui(user, src, ui_key, ui, data, force_open) diff --git a/nano/templates/sec_camera.tmpl b/nano/templates/sec_camera.tmpl index 1d46f1868f..21ecea71e7 100644 --- a/nano/templates/sec_camera.tmpl +++ b/nano/templates/sec_camera.tmpl @@ -8,7 +8,8 @@ Used In File(s): \code\game\machinery\computer\camera.dm {{:helper.link('Reset', 'refresh', {'reset' : 1})}}
-
Current Camera: 
+
Current Camera: 
+
{{if data.current_camera}}
{{:data.current_camera.name}}
{{else}} From b98027d050ca9eee3d2cf226b219ba4051324014 Mon Sep 17 00:00:00 2001 From: Aronai Sieyes Date: Mon, 27 Apr 2020 10:33:30 -0400 Subject: [PATCH 5/7] Various fixes while waiting on PR --- code/controllers/subsystems/ticker.dm | 1128 ++++++++--------- .../game/objects/items/devices/radio/radio.dm | 7 +- code/modules/alarm/atmosphere_alarm.dm | 4 +- code/modules/mob/new_player/new_player.dm | 1 + .../file_system/programs/generic/camera.dm | 6 - 5 files changed, 573 insertions(+), 573 deletions(-) diff --git a/code/controllers/subsystems/ticker.dm b/code/controllers/subsystems/ticker.dm index 7fd6745814..b17640430b 100644 --- a/code/controllers/subsystems/ticker.dm +++ b/code/controllers/subsystems/ticker.dm @@ -1,564 +1,564 @@ -// -// Ticker controls the state of the game, being responsible for round start, game mode, and round end. -// -SUBSYSTEM_DEF(ticker) - name = "Gameticker" - wait = 2 SECONDS - init_order = INIT_ORDER_TICKER - priority = FIRE_PRIORITY_TICKER - flags = SS_NO_TICK_CHECK | SS_KEEP_TIMING - runlevels = RUNLEVEL_LOBBY | RUNLEVEL_SETUP | RUNLEVEL_GAME | RUNLEVEL_POSTGAME // Every runlevel! - - var/const/restart_timeout = 3 MINUTES // Default time to wait before rebooting in desiseconds. - var/current_state = GAME_STATE_INIT // We aren't even at pregame yet // TODO replace with CURRENT_GAME_STATE - - /* Relies upon the following globals (TODO move those in here) */ - // var/master_mode = "extended" //The underlying game mode (so "secret" or the voted mode). - // Set by SSvote when VOTE_GAMEMODE finishes. - // var/round_progressing = 1 //Whether the lobby clock is ticking down. - - var/pregame_timeleft = 0 // Time remaining until game starts in seconds. Set by config - var/start_immediately = FALSE // If true there is no lobby phase, the game starts immediately. - - var/hide_mode = FALSE // If the true game mode should be hidden (because we chose "secret") - var/datum/game_mode/mode = null // The actual gamemode, if selected. - - var/end_game_state = END_GAME_NOT_OVER // Track where we are ending game/round - var/restart_timeleft // Time remaining until restart in desiseconds - var/last_restart_notify // world.time of last restart warning. - var/delay_end = FALSE // If set, the round will not restart on its own. - - var/login_music // music played in pregame lobby - - var/list/datum/mind/minds = list() // The people in the game. Used for objective tracking. - - // TODO - I am sure there is a better place these can go. - var/Bible_icon_state // icon_state the chaplain has chosen for his bible - var/Bible_item_state // item_state the chaplain has chosen for his bible - var/Bible_name // name of the bible - var/Bible_deity_name - - var/random_players = FALSE // If set to nonzero, ALL players who latejoin or declare-ready join will have random appearances/genders - - // TODO - Should this go here or in the job subsystem? - var/triai = FALSE // Global flag for Triumvirate AI being enabled - - //station_explosion used to be a variable for every mob's hud. Which was a waste! - //Now we have a general cinematic centrally held within the gameticker....far more efficient! - var/obj/screen/cinematic = null - -// This global variable exists for legacy support so we don't have to rename every 'ticker' to 'SSticker' yet. -var/global/datum/controller/subsystem/ticker/ticker -/datum/controller/subsystem/ticker/PreInit() - global.ticker = src // TODO - Remove this! Change everything to point at SSticker intead - login_music = pick(\ - /*'sound/music/halloween/skeletons.ogg',\ - 'sound/music/halloween/halloween.ogg',\ - 'sound/music/halloween/ghosts.ogg'*/ - 'sound/music/space.ogg',\ - 'sound/music/traitor.ogg',\ - 'sound/music/title2.ogg',\ - 'sound/music/clouds.s3m',\ - 'sound/music/space_oddity.ogg') //Ground Control to Major Tom, this song is cool, what's going on? - -/datum/controller/subsystem/ticker/Initialize() - pregame_timeleft = config.pregame_time - send2mainirc("Server lobby is loaded and open at byond://[config.serverurl ? config.serverurl : (config.server ? config.server : "[world.address]:[world.port]")]") - return ..() - -/datum/controller/subsystem/ticker/fire(resumed = FALSE) - switch(current_state) - if(GAME_STATE_INIT) - pregame_welcome() - current_state = GAME_STATE_PREGAME - if(GAME_STATE_PREGAME) - pregame_tick() - if(GAME_STATE_SETTING_UP) - setup_tick() - if(GAME_STATE_PLAYING) - playing_tick() - if(GAME_STATE_FINISHED) - post_game_tick() - -/datum/controller/subsystem/ticker/proc/pregame_welcome() - to_world("Welcome to the pregame lobby!") - to_world("Please set up your character and select ready. The round will start in [pregame_timeleft] seconds.") - -// Called during GAME_STATE_PREGAME (RUNLEVEL_LOBBY) -/datum/controller/subsystem/ticker/proc/pregame_tick() - if(round_progressing && last_fire) - pregame_timeleft -= (world.time - last_fire) / (1 SECOND) - - if(start_immediately) - pregame_timeleft = 0 - else if(SSvote.time_remaining) - return // vote still going, wait for it. - - // Time to start the game! - if(pregame_timeleft <= 0) - current_state = GAME_STATE_SETTING_UP - Master.SetRunLevel(RUNLEVEL_SETUP) - if(start_immediately) - fire() // Don't wait for next tick, do it now! - return - - if(pregame_timeleft <= config.vote_autogamemode_timeleft && !SSvote.gamemode_vote_called) - SSvote.autogamemode() // Start the game mode vote (if we haven't had one already) - -// Called during GAME_STATE_SETTING_UP (RUNLEVEL_SETUP) -/datum/controller/subsystem/ticker/proc/setup_tick(resumed = FALSE) - if(!setup_choose_gamemode()) - // It failed, go back to lobby state and re-send the welcome message - pregame_timeleft = config.pregame_time - SSvote.gamemode_vote_called = FALSE // Allow another autogamemode vote - current_state = GAME_STATE_PREGAME - Master.SetRunLevel(RUNLEVEL_LOBBY) - pregame_welcome() - return - // If we got this far we succeeded in picking a game mode. Punch it! - setup_startgame() - return - -// Formerly the first half of setup() - The part that chooses the game mode. -// Returns 0 if failed to pick a mode, otherwise 1 -/datum/controller/subsystem/ticker/proc/setup_choose_gamemode() - //Create and announce mode - if(master_mode == "secret") - src.hide_mode = TRUE - - var/list/runnable_modes = config.get_runnable_modes() - if((master_mode == "random") || (master_mode == "secret")) - if(!runnable_modes.len) - to_world("Unable to choose playable game mode. Reverting to pregame lobby.") - return 0 - if(secret_force_mode != "secret") - src.mode = config.pick_mode(secret_force_mode) - if(!src.mode) - var/list/weighted_modes = list() - for(var/datum/game_mode/GM in runnable_modes) - weighted_modes[GM.config_tag] = config.probabilities[GM.config_tag] - src.mode = gamemode_cache[pickweight(weighted_modes)] - else - src.mode = config.pick_mode(master_mode) - - if(!src.mode) - to_world("Serious error in mode setup! Reverting to pregame lobby.") //Uses setup instead of set up due to computational context. - return 0 - - job_master.ResetOccupations() - src.mode.create_antagonists() - src.mode.pre_setup() - job_master.DivideOccupations() // Apparently important for new antagonist system to register specific job antags properly. - - if(!src.mode.can_start()) - to_world("Unable to start [mode.name]. Not enough players readied, [config.player_requirements[mode.config_tag]] players needed. Reverting to pregame lobby.") - mode.fail_setup() - mode = null - job_master.ResetOccupations() - return 0 - - if(hide_mode) - to_world("The current game mode is - Secret!") - if(runnable_modes.len) - var/list/tmpmodes = new - for (var/datum/game_mode/M in runnable_modes) - tmpmodes+=M.name - tmpmodes = sortList(tmpmodes) - if(tmpmodes.len) - to_world("Possibilities: [english_list(tmpmodes, and_text= "; ", comma_text = "; ")]") - else - src.mode.announce() - return 1 - -// Formerly the second half of setup() - The part that actually initializes everything and starts the game. -/datum/controller/subsystem/ticker/proc/setup_startgame() - setup_economy() - create_characters() //Create player characters and transfer them. - collect_minds() - equip_characters() - data_core.manifest() - - callHook("roundstart") - - spawn(0)//Forking here so we dont have to wait for this to finish - mode.post_setup() - //Cleanup some stuff - for(var/obj/effect/landmark/start/S in landmarks_list) - //Deleting Startpoints but we need the ai point to AI-ize people later - if (S.name != "AI") - qdel(S) - to_world("Enjoy the game!") - world << sound('sound/AI/welcome.ogg') // Skie - //Holiday Round-start stuff ~Carn - Holiday_Game_Start() - - var/list/adm = get_admin_counts() - if(adm["total"] == 0) - send2adminirc("A round has started with no admins online.") - -/* supply_controller.process() //Start the supply shuttle regenerating points -- TLE // handled in scheduler - master_controller.process() //Start master_controller.process() - lighting_controller.process() //Start processing DynamicAreaLighting updates - */ - - processScheduler.start() - current_state = GAME_STATE_PLAYING - Master.SetRunLevel(RUNLEVEL_GAME) - - if(config.sql_enabled) - statistic_cycle() // Polls population totals regularly and stores them in an SQL DB -- TLE - - return 1 - - -// Called during GAME_STATE_PLAYING (RUNLEVEL_GAME) -/datum/controller/subsystem/ticker/proc/playing_tick(resumed = FALSE) - mode.process() // So THIS is where we run mode.process() huh? Okay - - if(mode.explosion_in_progress) - return // wait until explosion is done. - - // Calculate if game and/or mode are finished (Complicated by the continuous_rounds config option) - var/game_finished = FALSE - var/mode_finished = FALSE - if (config.continous_rounds) // Game keeps going after mode ends. - game_finished = (emergency_shuttle.returned() || mode.station_was_nuked) - mode_finished = ((end_game_state >= END_GAME_MODE_FINISHED) || mode.check_finished()) // Short circuit if already finished. - else // Game ends when mode does - game_finished = (mode.check_finished() || (emergency_shuttle.returned() && emergency_shuttle.evac == 1)) || universe_has_ended - mode_finished = game_finished - - if(game_finished && mode_finished) - end_game_state = END_GAME_READY_TO_END - current_state = GAME_STATE_FINISHED - Master.SetRunLevel(RUNLEVEL_POSTGAME) - INVOKE_ASYNC(src, .proc/declare_completion) - else if (mode_finished && (end_game_state < END_GAME_MODE_FINISHED)) - end_game_state = END_GAME_MODE_FINISHED // Only do this cleanup once! - mode.cleanup() - //call a transfer shuttle vote - to_world("The round has ended!") - SSvote.autotransfer() - -// Called during GAME_STATE_FINISHED (RUNLEVEL_POSTGAME) -/datum/controller/subsystem/ticker/proc/post_game_tick() - switch(end_game_state) - if(END_GAME_READY_TO_END) - callHook("roundend") - - if (mode.station_was_nuked) - feedback_set_details("end_proper", "nuke") - restart_timeleft = 1 MINUTE // No point waiting five minutes if everyone's dead. - if(!delay_end) - to_world("Rebooting due to destruction of [station_name()] in [round(restart_timeleft/600)] minute\s.") - last_restart_notify = world.time - else - feedback_set_details("end_proper", "proper completion") - restart_timeleft = restart_timeout - - if(blackbox) - blackbox.save_all_data_to_sql() // TODO - Blackbox or statistics subsystem - - end_game_state = END_GAME_ENDING - return - if(END_GAME_ENDING) - restart_timeleft -= (world.time - last_fire) - if(delay_end) - to_world("An admin has delayed the round end.") - end_game_state = END_GAME_DELAYED - else if(restart_timeleft <= 0) - world.Reboot() - else if (world.time - last_restart_notify >= 1 MINUTE) - to_world("Restarting in [round(restart_timeleft/600, 1)] minute\s.") - last_restart_notify = world.time - return - if(END_GAME_DELAYED) - restart_timeleft -= (world.time - last_fire) - if(!delay_end) - end_game_state = END_GAME_ENDING - else - log_error("Ticker arrived at round end in an unexpected endgame state '[end_game_state]'.") - end_game_state = END_GAME_READY_TO_END - - -// ---------------------------------------------------------------------- -// These two below are not used! But they could be - -// Use these preferentially to directly examining ticker.current_state to help prepare for transition to ticker as subsystem! - -/datum/controller/subsystem/ticker/proc/PreRoundStart() - return (current_state < GAME_STATE_PLAYING) - -/datum/controller/subsystem/ticker/proc/IsSettingUp() - return (current_state == GAME_STATE_SETTING_UP) - -/datum/controller/subsystem/ticker/proc/IsRoundInProgress() - return (current_state == GAME_STATE_PLAYING) - -/datum/controller/subsystem/ticker/proc/HasRoundStarted() - return (current_state >= GAME_STATE_PLAYING) - -// ------------------------------------------------------------------------ -// HELPER PROCS! -// ------------------------------------------------------------------------ - -//Plus it provides an easy way to make cinematics for other events. Just use this as a template :) -/datum/controller/subsystem/ticker/proc/station_explosion_cinematic(var/station_missed=0, var/override = null) - if( cinematic ) return //already a cinematic in progress! - - //initialise our cinematic screen object - cinematic = new(src) - cinematic.icon = 'icons/effects/station_explosion.dmi' - cinematic.icon_state = "station_intact" - cinematic.layer = 100 - cinematic.plane = PLANE_PLAYER_HUD - cinematic.mouse_opacity = 0 - cinematic.screen_loc = "1,0" - - var/obj/structure/bed/temp_buckle = new(src) - //Incredibly hackish. It creates a bed within the gameticker (lol) to stop mobs running around - if(station_missed) - for(var/mob/living/M in living_mob_list) - M.buckled = temp_buckle //buckles the mob so it can't do anything - if(M.client) - M.client.screen += cinematic //show every client the cinematic - else //nuke kills everyone on z-level 1 to prevent "hurr-durr I survived" - for(var/mob/living/M in living_mob_list) - M.buckled = temp_buckle - if(M.client) - M.client.screen += cinematic - - switch(M.z) - if(0) //inside a crate or something - var/turf/T = get_turf(M) - if(T && T.z in using_map.station_levels) //we don't use M.death(0) because it calls a for(/mob) loop and - M.health = 0 - M.set_stat(DEAD) - if(1) //on a z-level 1 turf. - M.health = 0 - M.set_stat(DEAD) - - //Now animate the cinematic - switch(station_missed) - if(1) //nuke was nearby but (mostly) missed - if( mode && !override ) - override = mode.name - switch( override ) - if("mercenary") //Nuke wasn't on station when it blew up - flick("intro_nuke",cinematic) - sleep(35) - world << sound('sound/effects/explosionfar.ogg') - flick("station_intact_fade_red",cinematic) - cinematic.icon_state = "summary_nukefail" - else - flick("intro_nuke",cinematic) - sleep(35) - world << sound('sound/effects/explosionfar.ogg') - //flick("end",cinematic) - - - if(2) //nuke was nowhere nearby //TODO: a really distant explosion animation - sleep(50) - world << sound('sound/effects/explosionfar.ogg') - - - else //station was destroyed - if( mode && !override ) - override = mode.name - switch( override ) - if("mercenary") //Nuke Ops successfully bombed the station - flick("intro_nuke",cinematic) - sleep(35) - flick("station_explode_fade_red",cinematic) - world << sound('sound/effects/explosionfar.ogg') - cinematic.icon_state = "summary_nukewin" - if("AI malfunction") //Malf (screen,explosion,summary) - flick("intro_malf",cinematic) - sleep(76) - flick("station_explode_fade_red",cinematic) - world << sound('sound/effects/explosionfar.ogg') - cinematic.icon_state = "summary_malf" - if("blob") //Station nuked (nuke,explosion,summary) - flick("intro_nuke",cinematic) - sleep(35) - flick("station_explode_fade_red",cinematic) - world << sound('sound/effects/explosionfar.ogg') - cinematic.icon_state = "summary_selfdes" - else //Station nuked (nuke,explosion,summary) - flick("intro_nuke",cinematic) - sleep(35) - flick("station_explode_fade_red", cinematic) - world << sound('sound/effects/explosionfar.ogg') - cinematic.icon_state = "summary_selfdes" - for(var/mob/living/M in living_mob_list) - if(M.loc.z in using_map.station_levels) - M.death()//No mercy - //If its actually the end of the round, wait for it to end. - //Otherwise if its a verb it will continue on afterwards. - sleep(300) - - if(cinematic) qdel(cinematic) //end the cinematic - if(temp_buckle) qdel(temp_buckle) //release everybody - return - - -/datum/controller/subsystem/ticker/proc/create_characters() - for(var/mob/new_player/player in player_list) - if(player && player.ready && player.mind) - if(player.mind.assigned_role=="AI") - player.close_spawn_windows() - player.AIize() - else if(!player.mind.assigned_role) - continue - else - player.create_character() - qdel(player) - - -/datum/controller/subsystem/ticker/proc/collect_minds() - for(var/mob/living/player in player_list) - if(player.mind) - minds += player.mind - - -/datum/controller/subsystem/ticker/proc/equip_characters() - var/captainless=1 - for(var/mob/living/carbon/human/player in player_list) - if(player && player.mind && player.mind.assigned_role) - if(player.mind.assigned_role == "Colony Director") - captainless=0 - if(!player_is_antag(player.mind, only_offstation_roles = 1)) - job_master.EquipRank(player, player.mind.assigned_role, 0) - UpdateFactionList(player) - equip_custom_items(player) - player.apply_traits() - if(captainless) - for(var/mob/M in player_list) - if(!istype(M,/mob/new_player)) - to_chat(M, "Colony Directorship not forced on anyone.") - - -/datum/controller/subsystem/ticker/proc/declare_completion() - to_world("


A round of [mode.name] has ended!

") - for(var/mob/Player in player_list) - if(Player.mind && !isnewplayer(Player)) - if(Player.stat != DEAD) - var/turf/playerTurf = get_turf(Player) - if(emergency_shuttle.departed && emergency_shuttle.evac) - if(isNotAdminLevel(playerTurf.z)) - to_chat(Player, "You survived the round, but remained on [station_name()] as [Player.real_name].") - else - to_chat(Player, "You managed to survive the events on [station_name()] as [Player.real_name].") - else if(isAdminLevel(playerTurf.z)) - to_chat(Player, "You successfully underwent crew transfer after events on [station_name()] as [Player.real_name].") - else if(issilicon(Player)) - to_chat(Player, "You remain operational after the events on [station_name()] as [Player.real_name].") - else - to_chat(Player, "You missed the crew transfer after the events on [station_name()] as [Player.real_name].") - else - if(istype(Player,/mob/observer/dead)) - var/mob/observer/dead/O = Player - if(!O.started_as_observer) - to_chat(Player, "You did not survive the events on [station_name()]...") - else - to_chat(Player, "You did not survive the events on [station_name()]...") - to_world("
") - - for (var/mob/living/silicon/ai/aiPlayer in mob_list) - if (aiPlayer.stat != 2) - to_world("[aiPlayer.name] (Played by: [aiPlayer.key])'s laws at the end of the round were:") - else - to_world("[aiPlayer.name] (Played by: [aiPlayer.key])'s laws when it was deactivated were:") - aiPlayer.show_laws(1) - - if (aiPlayer.connected_robots.len) - var/robolist = "The AI's loyal minions were: " - for(var/mob/living/silicon/robot/robo in aiPlayer.connected_robots) - robolist += "[robo.name][robo.stat?" (Deactivated) (Played by: [robo.key]), ":" (Played by: [robo.key]), "]" - to_world("[robolist]") - - var/dronecount = 0 - - for (var/mob/living/silicon/robot/robo in mob_list) - - if(istype(robo,/mob/living/silicon/robot/drone) && !istype(robo,/mob/living/silicon/robot/drone/swarm)) - dronecount++ - continue - - if (!robo.connected_ai) - if (robo.stat != 2) - to_world("[robo.name] (Played by: [robo.key]) survived as an AI-less stationbound synthetic! Its laws were:") - else - to_world("[robo.name] (Played by: [robo.key]) was unable to survive the rigors of being a stationbound synthetic without an AI. Its laws were:") - - if(robo) //How the hell do we lose robo between here and the world messages directly above this? - robo.laws.show_laws(world) - - if(dronecount) - to_world("There [dronecount>1 ? "were" : "was"] [dronecount] industrious maintenance [dronecount>1 ? "drones" : "drone"] at the end of this round.") - - mode.declare_completion()//To declare normal completion. - - //Ask the event manager to print round end information - SSevents.RoundEnd() - - //Print a list of antagonists to the server log - var/list/total_antagonists = list() - //Look into all mobs in world, dead or alive - for(var/datum/mind/Mind in minds) - var/temprole = Mind.special_role - if(temprole) //if they are an antagonist of some sort. - if(temprole in total_antagonists) //If the role exists already, add the name to it - total_antagonists[temprole] += ", [Mind.name]([Mind.key])" - else - total_antagonists.Add(temprole) //If the role doesnt exist in the list, create it and add the mob - total_antagonists[temprole] += ": [Mind.name]([Mind.key])" - - //Now print them all into the log! - log_game("Antagonists at round end were...") - for(var/i in total_antagonists) - log_game("[i]s[total_antagonists[i]].") - - return 1 - -/datum/controller/subsystem/ticker/stat_entry() - switch(current_state) - if(GAME_STATE_INIT) - ..() - if(GAME_STATE_PREGAME) // RUNLEVEL_LOBBY - ..("START [round_progressing ? "[round(pregame_timeleft)]s" : "(PAUSED)"]") - if(GAME_STATE_SETTING_UP) // RUNLEVEL_SETUP - ..("SETUP") - if(GAME_STATE_PLAYING) // RUNLEVEL_GAME - ..("GAME") - if(GAME_STATE_FINISHED) // RUNLEVEL_POSTGAME - switch(end_game_state) - if(END_GAME_MODE_FINISHED) - ..("MODE OVER, WAITING") - if(END_GAME_READY_TO_END) - ..("ENDGAME PROCESSING") - if(END_GAME_ENDING) - ..("END IN [round(restart_timeleft/10)]s") - if(END_GAME_DELAYED) - ..("END PAUSED") - else - ..("ENDGAME ERROR:[end_game_state]") - -/datum/controller/subsystem/ticker/Recover() - flags |= SS_NO_INIT // Don't initialize again - - current_state = SSticker.current_state - mode = SSticker.mode - pregame_timeleft = SSticker.pregame_timeleft - - end_game_state = SSticker.end_game_state - delay_end = SSticker.delay_end - restart_timeleft = SSticker.restart_timeleft - - minds = SSticker.minds - - Bible_icon_state = SSticker.Bible_icon_state - Bible_item_state = SSticker.Bible_item_state - Bible_name = SSticker.Bible_name - Bible_deity_name = SSticker.Bible_deity_name - random_players = SSticker.random_players +// +// Ticker controls the state of the game, being responsible for round start, game mode, and round end. +// +SUBSYSTEM_DEF(ticker) + name = "Gameticker" + wait = 2 SECONDS + init_order = INIT_ORDER_TICKER + priority = FIRE_PRIORITY_TICKER + flags = SS_NO_TICK_CHECK | SS_KEEP_TIMING + runlevels = RUNLEVEL_LOBBY | RUNLEVEL_SETUP | RUNLEVEL_GAME | RUNLEVEL_POSTGAME // Every runlevel! + + var/const/restart_timeout = 3 MINUTES // Default time to wait before rebooting in desiseconds. + var/current_state = GAME_STATE_INIT // We aren't even at pregame yet // TODO replace with CURRENT_GAME_STATE + + /* Relies upon the following globals (TODO move those in here) */ + // var/master_mode = "extended" //The underlying game mode (so "secret" or the voted mode). + // Set by SSvote when VOTE_GAMEMODE finishes. + // var/round_progressing = 1 //Whether the lobby clock is ticking down. + + var/pregame_timeleft = 0 // Time remaining until game starts in seconds. Set by config + var/start_immediately = FALSE // If true there is no lobby phase, the game starts immediately. + + var/hide_mode = FALSE // If the true game mode should be hidden (because we chose "secret") + var/datum/game_mode/mode = null // The actual gamemode, if selected. + + var/end_game_state = END_GAME_NOT_OVER // Track where we are ending game/round + var/restart_timeleft // Time remaining until restart in desiseconds + var/last_restart_notify // world.time of last restart warning. + var/delay_end = FALSE // If set, the round will not restart on its own. + + var/login_music // music played in pregame lobby + + var/list/datum/mind/minds = list() // The people in the game. Used for objective tracking. + + // TODO - I am sure there is a better place these can go. + var/Bible_icon_state // icon_state the chaplain has chosen for his bible + var/Bible_item_state // item_state the chaplain has chosen for his bible + var/Bible_name // name of the bible + var/Bible_deity_name + + var/random_players = FALSE // If set to nonzero, ALL players who latejoin or declare-ready join will have random appearances/genders + + // TODO - Should this go here or in the job subsystem? + var/triai = FALSE // Global flag for Triumvirate AI being enabled + + //station_explosion used to be a variable for every mob's hud. Which was a waste! + //Now we have a general cinematic centrally held within the gameticker....far more efficient! + var/obj/screen/cinematic = null + +// This global variable exists for legacy support so we don't have to rename every 'ticker' to 'SSticker' yet. +var/global/datum/controller/subsystem/ticker/ticker +/datum/controller/subsystem/ticker/PreInit() + global.ticker = src // TODO - Remove this! Change everything to point at SSticker intead + login_music = pick(\ + /*'sound/music/halloween/skeletons.ogg',\ + 'sound/music/halloween/halloween.ogg',\ + 'sound/music/halloween/ghosts.ogg'*/ + 'sound/music/space.ogg',\ + 'sound/music/traitor.ogg',\ + 'sound/music/title2.ogg',\ + 'sound/music/clouds.s3m',\ + 'sound/music/space_oddity.ogg') //Ground Control to Major Tom, this song is cool, what's going on? + +/datum/controller/subsystem/ticker/Initialize() + pregame_timeleft = config.pregame_time + send2mainirc("Server lobby is loaded and open at byond://[config.serverurl ? config.serverurl : (config.server ? config.server : "[world.address]:[world.port]")]") + return ..() + +/datum/controller/subsystem/ticker/fire(resumed = FALSE) + switch(current_state) + if(GAME_STATE_INIT) + pregame_welcome() + current_state = GAME_STATE_PREGAME + if(GAME_STATE_PREGAME) + pregame_tick() + if(GAME_STATE_SETTING_UP) + setup_tick() + if(GAME_STATE_PLAYING) + playing_tick() + if(GAME_STATE_FINISHED) + post_game_tick() + +/datum/controller/subsystem/ticker/proc/pregame_welcome() + to_world("Welcome to the pregame lobby!") + to_world("Please set up your character and select ready. The round will start in [pregame_timeleft] seconds.") + +// Called during GAME_STATE_PREGAME (RUNLEVEL_LOBBY) +/datum/controller/subsystem/ticker/proc/pregame_tick() + if(round_progressing && last_fire) + pregame_timeleft -= (world.time - last_fire) / (1 SECOND) + + if(start_immediately) + pregame_timeleft = 0 + else if(SSvote.time_remaining) + return // vote still going, wait for it. + + // Time to start the game! + if(pregame_timeleft <= 0) + current_state = GAME_STATE_SETTING_UP + Master.SetRunLevel(RUNLEVEL_SETUP) + if(start_immediately) + fire() // Don't wait for next tick, do it now! + return + + if(pregame_timeleft <= config.vote_autogamemode_timeleft && !SSvote.gamemode_vote_called) + SSvote.autogamemode() // Start the game mode vote (if we haven't had one already) + +// Called during GAME_STATE_SETTING_UP (RUNLEVEL_SETUP) +/datum/controller/subsystem/ticker/proc/setup_tick(resumed = FALSE) + if(!setup_choose_gamemode()) + // It failed, go back to lobby state and re-send the welcome message + pregame_timeleft = config.pregame_time + SSvote.gamemode_vote_called = FALSE // Allow another autogamemode vote + current_state = GAME_STATE_PREGAME + Master.SetRunLevel(RUNLEVEL_LOBBY) + pregame_welcome() + return + // If we got this far we succeeded in picking a game mode. Punch it! + setup_startgame() + return + +// Formerly the first half of setup() - The part that chooses the game mode. +// Returns 0 if failed to pick a mode, otherwise 1 +/datum/controller/subsystem/ticker/proc/setup_choose_gamemode() + //Create and announce mode + if(master_mode == "secret") + src.hide_mode = TRUE + + var/list/runnable_modes = config.get_runnable_modes() + if((master_mode == "random") || (master_mode == "secret")) + if(!runnable_modes.len) + to_world("Unable to choose playable game mode. Reverting to pregame lobby.") + return 0 + if(secret_force_mode != "secret") + src.mode = config.pick_mode(secret_force_mode) + if(!src.mode) + var/list/weighted_modes = list() + for(var/datum/game_mode/GM in runnable_modes) + weighted_modes[GM.config_tag] = config.probabilities[GM.config_tag] + src.mode = gamemode_cache[pickweight(weighted_modes)] + else + src.mode = config.pick_mode(master_mode) + + if(!src.mode) + to_world("Serious error in mode setup! Reverting to pregame lobby.") //Uses setup instead of set up due to computational context. + return 0 + + job_master.ResetOccupations() + src.mode.create_antagonists() + src.mode.pre_setup() + job_master.DivideOccupations() // Apparently important for new antagonist system to register specific job antags properly. + + if(!src.mode.can_start()) + to_world("Unable to start [mode.name]. Not enough players readied, [config.player_requirements[mode.config_tag]] players needed. Reverting to pregame lobby.") + mode.fail_setup() + mode = null + job_master.ResetOccupations() + return 0 + + if(hide_mode) + to_world("The current game mode is - Secret!") + if(runnable_modes.len) + var/list/tmpmodes = new + for (var/datum/game_mode/M in runnable_modes) + tmpmodes+=M.name + tmpmodes = sortList(tmpmodes) + if(tmpmodes.len) + to_world("Possibilities: [english_list(tmpmodes, and_text= "; ", comma_text = "; ")]") + else + src.mode.announce() + return 1 + +// Formerly the second half of setup() - The part that actually initializes everything and starts the game. +/datum/controller/subsystem/ticker/proc/setup_startgame() + setup_economy() + create_characters() //Create player characters and transfer them. + collect_minds() + equip_characters() + data_core.manifest() + + callHook("roundstart") + + spawn(0)//Forking here so we dont have to wait for this to finish + mode.post_setup() + //Cleanup some stuff + for(var/obj/effect/landmark/start/S in landmarks_list) + //Deleting Startpoints but we need the ai point to AI-ize people later + if (S.name != "AI") + qdel(S) + to_world("Enjoy the game!") + world << sound('sound/AI/welcome.ogg') // Skie + //Holiday Round-start stuff ~Carn + Holiday_Game_Start() + + var/list/adm = get_admin_counts() + if(adm["total"] == 0) + send2adminirc("A round has started with no admins online.") + +/* supply_controller.process() //Start the supply shuttle regenerating points -- TLE // handled in scheduler + master_controller.process() //Start master_controller.process() + lighting_controller.process() //Start processing DynamicAreaLighting updates + */ + + processScheduler.start() + current_state = GAME_STATE_PLAYING + Master.SetRunLevel(RUNLEVEL_GAME) + + if(config.sql_enabled) + statistic_cycle() // Polls population totals regularly and stores them in an SQL DB -- TLE + + return 1 + + +// Called during GAME_STATE_PLAYING (RUNLEVEL_GAME) +/datum/controller/subsystem/ticker/proc/playing_tick(resumed = FALSE) + mode.process() // So THIS is where we run mode.process() huh? Okay + + if(mode.explosion_in_progress) + return // wait until explosion is done. + + // Calculate if game and/or mode are finished (Complicated by the continuous_rounds config option) + var/game_finished = FALSE + var/mode_finished = FALSE + if (config.continous_rounds) // Game keeps going after mode ends. + game_finished = (emergency_shuttle.returned() || mode.station_was_nuked) + mode_finished = ((end_game_state >= END_GAME_MODE_FINISHED) || mode.check_finished()) // Short circuit if already finished. + else // Game ends when mode does + game_finished = (mode.check_finished() || (emergency_shuttle.returned() && emergency_shuttle.evac == 1)) || universe_has_ended + mode_finished = game_finished + + if(game_finished && mode_finished) + end_game_state = END_GAME_READY_TO_END + current_state = GAME_STATE_FINISHED + Master.SetRunLevel(RUNLEVEL_POSTGAME) + INVOKE_ASYNC(src, .proc/declare_completion) + else if (mode_finished && (end_game_state < END_GAME_MODE_FINISHED)) + end_game_state = END_GAME_MODE_FINISHED // Only do this cleanup once! + mode.cleanup() + //call a transfer shuttle vote + to_world("The round has ended!") + SSvote.autotransfer() + +// Called during GAME_STATE_FINISHED (RUNLEVEL_POSTGAME) +/datum/controller/subsystem/ticker/proc/post_game_tick() + switch(end_game_state) + if(END_GAME_READY_TO_END) + callHook("roundend") + + if (mode.station_was_nuked) + feedback_set_details("end_proper", "nuke") + restart_timeleft = 1 MINUTE // No point waiting five minutes if everyone's dead. + if(!delay_end) + to_world("Rebooting due to destruction of [station_name()] in [round(restart_timeleft/600)] minute\s.") + last_restart_notify = world.time + else + feedback_set_details("end_proper", "proper completion") + restart_timeleft = restart_timeout + + if(blackbox) + blackbox.save_all_data_to_sql() // TODO - Blackbox or statistics subsystem + + end_game_state = END_GAME_ENDING + return + if(END_GAME_ENDING) + restart_timeleft -= (world.time - last_fire) + if(delay_end) + to_world("An admin has delayed the round end.") + end_game_state = END_GAME_DELAYED + else if(restart_timeleft <= 0) + world.Reboot() + else if (world.time - last_restart_notify >= 1 MINUTE) + to_world("Restarting in [round(restart_timeleft/600, 1)] minute\s.") + last_restart_notify = world.time + return + if(END_GAME_DELAYED) + restart_timeleft -= (world.time - last_fire) + if(!delay_end) + end_game_state = END_GAME_ENDING + else + log_error("Ticker arrived at round end in an unexpected endgame state '[end_game_state]'.") + end_game_state = END_GAME_READY_TO_END + + +// ---------------------------------------------------------------------- +// These two below are not used! But they could be + +// Use these preferentially to directly examining ticker.current_state to help prepare for transition to ticker as subsystem! + +/datum/controller/subsystem/ticker/proc/PreRoundStart() + return (current_state < GAME_STATE_PLAYING) + +/datum/controller/subsystem/ticker/proc/IsSettingUp() + return (current_state == GAME_STATE_SETTING_UP) + +/datum/controller/subsystem/ticker/proc/IsRoundInProgress() + return (current_state == GAME_STATE_PLAYING) + +/datum/controller/subsystem/ticker/proc/HasRoundStarted() + return (current_state >= GAME_STATE_PLAYING) + +// ------------------------------------------------------------------------ +// HELPER PROCS! +// ------------------------------------------------------------------------ + +//Plus it provides an easy way to make cinematics for other events. Just use this as a template :) +/datum/controller/subsystem/ticker/proc/station_explosion_cinematic(var/station_missed=0, var/override = null) + if( cinematic ) return //already a cinematic in progress! + + //initialise our cinematic screen object + cinematic = new(src) + cinematic.icon = 'icons/effects/station_explosion.dmi' + cinematic.icon_state = "station_intact" + cinematic.layer = 100 + cinematic.plane = PLANE_PLAYER_HUD + cinematic.mouse_opacity = 0 + cinematic.screen_loc = "1,0" + + var/obj/structure/bed/temp_buckle = new(src) + //Incredibly hackish. It creates a bed within the gameticker (lol) to stop mobs running around + if(station_missed) + for(var/mob/living/M in living_mob_list) + M.buckled = temp_buckle //buckles the mob so it can't do anything + if(M.client) + M.client.screen += cinematic //show every client the cinematic + else //nuke kills everyone on z-level 1 to prevent "hurr-durr I survived" + for(var/mob/living/M in living_mob_list) + M.buckled = temp_buckle + if(M.client) + M.client.screen += cinematic + + switch(M.z) + if(0) //inside a crate or something + var/turf/T = get_turf(M) + if(T && T.z in using_map.station_levels) //we don't use M.death(0) because it calls a for(/mob) loop and + M.health = 0 + M.set_stat(DEAD) + if(1) //on a z-level 1 turf. + M.health = 0 + M.set_stat(DEAD) + + //Now animate the cinematic + switch(station_missed) + if(1) //nuke was nearby but (mostly) missed + if( mode && !override ) + override = mode.name + switch( override ) + if("mercenary") //Nuke wasn't on station when it blew up + flick("intro_nuke",cinematic) + sleep(35) + world << sound('sound/effects/explosionfar.ogg') + flick("station_intact_fade_red",cinematic) + cinematic.icon_state = "summary_nukefail" + else + flick("intro_nuke",cinematic) + sleep(35) + world << sound('sound/effects/explosionfar.ogg') + //flick("end",cinematic) + + + if(2) //nuke was nowhere nearby //TODO: a really distant explosion animation + sleep(50) + world << sound('sound/effects/explosionfar.ogg') + + + else //station was destroyed + if( mode && !override ) + override = mode.name + switch( override ) + if("mercenary") //Nuke Ops successfully bombed the station + flick("intro_nuke",cinematic) + sleep(35) + flick("station_explode_fade_red",cinematic) + world << sound('sound/effects/explosionfar.ogg') + cinematic.icon_state = "summary_nukewin" + if("AI malfunction") //Malf (screen,explosion,summary) + flick("intro_malf",cinematic) + sleep(76) + flick("station_explode_fade_red",cinematic) + world << sound('sound/effects/explosionfar.ogg') + cinematic.icon_state = "summary_malf" + if("blob") //Station nuked (nuke,explosion,summary) + flick("intro_nuke",cinematic) + sleep(35) + flick("station_explode_fade_red",cinematic) + world << sound('sound/effects/explosionfar.ogg') + cinematic.icon_state = "summary_selfdes" + else //Station nuked (nuke,explosion,summary) + flick("intro_nuke",cinematic) + sleep(35) + flick("station_explode_fade_red", cinematic) + world << sound('sound/effects/explosionfar.ogg') + cinematic.icon_state = "summary_selfdes" + for(var/mob/living/M in living_mob_list) + if(M.loc.z in using_map.station_levels) + M.death()//No mercy + //If its actually the end of the round, wait for it to end. + //Otherwise if its a verb it will continue on afterwards. + sleep(300) + + if(cinematic) qdel(cinematic) //end the cinematic + if(temp_buckle) qdel(temp_buckle) //release everybody + return + + +/datum/controller/subsystem/ticker/proc/create_characters() + for(var/mob/new_player/player in player_list) + if(player && player.ready && player.mind) + if(player.mind.assigned_role=="AI") + player.close_spawn_windows() + player.AIize() + else if(!player.mind.assigned_role) + continue + else + player.create_character() + qdel(player) + + +/datum/controller/subsystem/ticker/proc/collect_minds() + for(var/mob/living/player in player_list) + if(player.mind) + minds += player.mind + + +/datum/controller/subsystem/ticker/proc/equip_characters() + var/captainless=1 + for(var/mob/living/carbon/human/player in player_list) + if(player && player.mind && player.mind.assigned_role) + if(player.mind.assigned_role == "Colony Director") + captainless=0 + if(!player_is_antag(player.mind, only_offstation_roles = 1)) + job_master.EquipRank(player, player.mind.assigned_role, 0) + UpdateFactionList(player) + equip_custom_items(player) + player.apply_traits() + if(captainless) + for(var/mob/M in player_list) + if(!istype(M,/mob/new_player)) + to_chat(M, "Colony Directorship not forced on anyone.") + + +/datum/controller/subsystem/ticker/proc/declare_completion() + to_world("


A round of [mode.name] has ended!

") + for(var/mob/Player in player_list) + if(Player.mind && !isnewplayer(Player)) + if(Player.stat != DEAD) + var/turf/playerTurf = get_turf(Player) + if(emergency_shuttle.departed && emergency_shuttle.evac) + if(isNotAdminLevel(playerTurf.z)) + to_chat(Player, "You survived the round, but remained on [station_name()] as [Player.real_name].") + else + to_chat(Player, "You managed to survive the events on [station_name()] as [Player.real_name].") + else if(isAdminLevel(playerTurf.z)) + to_chat(Player, "You successfully underwent crew transfer after events on [station_name()] as [Player.real_name].") + else if(issilicon(Player)) + to_chat(Player, "You remain operational after the events on [station_name()] as [Player.real_name].") + else + to_chat(Player, "You missed the crew transfer after the events on [station_name()] as [Player.real_name].") + else + if(istype(Player,/mob/observer/dead)) + var/mob/observer/dead/O = Player + if(!O.started_as_observer) + to_chat(Player, "You did not survive the events on [station_name()]...") + else + to_chat(Player, "You did not survive the events on [station_name()]...") + to_world("
") + + for (var/mob/living/silicon/ai/aiPlayer in mob_list) + if (aiPlayer.stat != 2) + to_world("[aiPlayer.name] (Played by: [aiPlayer.key])'s laws at the end of the round were:") + else + to_world("[aiPlayer.name] (Played by: [aiPlayer.key])'s laws when it was deactivated were:") + aiPlayer.show_laws(1) + + if (aiPlayer.connected_robots.len) + var/robolist = "The AI's loyal minions were: " + for(var/mob/living/silicon/robot/robo in aiPlayer.connected_robots) + robolist += "[robo.name][robo.stat?" (Deactivated) (Played by: [robo.key]), ":" (Played by: [robo.key]), "]" + to_world("[robolist]") + + var/dronecount = 0 + + for (var/mob/living/silicon/robot/robo in mob_list) + + if(istype(robo,/mob/living/silicon/robot/drone) && !istype(robo,/mob/living/silicon/robot/drone/swarm)) + dronecount++ + continue + + if (!robo.connected_ai) + if (robo.stat != 2) + to_world("[robo.name] (Played by: [robo.key]) survived as an AI-less stationbound synthetic! Its laws were:") + else + to_world("[robo.name] (Played by: [robo.key]) was unable to survive the rigors of being a stationbound synthetic without an AI. Its laws were:") + + if(robo) //How the hell do we lose robo between here and the world messages directly above this? + robo.laws.show_laws(world) + + if(dronecount) + to_world("There [dronecount>1 ? "were" : "was"] [dronecount] industrious maintenance [dronecount>1 ? "drones" : "drone"] at the end of this round.") + + mode.declare_completion()//To declare normal completion. + + //Ask the event manager to print round end information + SSevents.RoundEnd() + + //Print a list of antagonists to the server log + var/list/total_antagonists = list() + //Look into all mobs in world, dead or alive + for(var/datum/mind/Mind in minds) + var/temprole = Mind.special_role + if(temprole) //if they are an antagonist of some sort. + if(temprole in total_antagonists) //If the role exists already, add the name to it + total_antagonists[temprole] += ", [Mind.name]([Mind.key])" + else + total_antagonists.Add(temprole) //If the role doesnt exist in the list, create it and add the mob + total_antagonists[temprole] += ": [Mind.name]([Mind.key])" + + //Now print them all into the log! + log_game("Antagonists at round end were...") + for(var/i in total_antagonists) + log_game("[i]s[total_antagonists[i]].") + + return 1 + +/datum/controller/subsystem/ticker/stat_entry() + switch(current_state) + if(GAME_STATE_INIT) + ..() + if(GAME_STATE_PREGAME) // RUNLEVEL_LOBBY + ..("START [round_progressing ? "[round(pregame_timeleft)]s" : "(PAUSED)"]") + if(GAME_STATE_SETTING_UP) // RUNLEVEL_SETUP + ..("SETUP") + if(GAME_STATE_PLAYING) // RUNLEVEL_GAME + ..("GAME") + if(GAME_STATE_FINISHED) // RUNLEVEL_POSTGAME + switch(end_game_state) + if(END_GAME_MODE_FINISHED) + ..("MODE OVER, WAITING") + if(END_GAME_READY_TO_END) + ..("ENDGAME PROCESSING") + if(END_GAME_ENDING) + ..("END IN [round(restart_timeleft/10)]s") + if(END_GAME_DELAYED) + ..("END PAUSED") + else + ..("ENDGAME ERROR:[end_game_state]") + +/datum/controller/subsystem/ticker/Recover() + flags |= SS_NO_INIT // Don't initialize again + + current_state = SSticker.current_state + mode = SSticker.mode + pregame_timeleft = SSticker.pregame_timeleft + + end_game_state = SSticker.end_game_state + delay_end = SSticker.delay_end + restart_timeleft = SSticker.restart_timeleft + + minds = SSticker.minds + + Bible_icon_state = SSticker.Bible_icon_state + Bible_item_state = SSticker.Bible_item_state + Bible_name = SSticker.Bible_name + Bible_deity_name = SSticker.Bible_deity_name + random_players = SSticker.random_players diff --git a/code/game/objects/items/devices/radio/radio.dm b/code/game/objects/items/devices/radio/radio.dm index 706d72455f..679b03bb5b 100644 --- a/code/game/objects/items/devices/radio/radio.dm +++ b/code/game/objects/items/devices/radio/radio.dm @@ -361,6 +361,7 @@ var/global/list/default_medbay_channels = list( var/datum/radio_frequency/connection = message_mode var/pos_z = get_z(src) + var/datum/radio_frequency/connection = message_mode //#### Tagging the signal with all appropriate identity values ####// @@ -475,7 +476,6 @@ var/global/list/default_medbay_channels = list( signal.transmission_method = TRANSMISSION_SUBSPACE //#### Sending the signal to all subspace receivers ####// - for(var/obj/machinery/telecomms/receiver/R in telecomms_list) R.receive_signal(signal) @@ -523,8 +523,13 @@ var/global/list/default_medbay_channels = list( //Nothing handled any sort of remote radio-ing and returned before now, just squawk on this zlevel. return Broadcast_Message(connection, M, voicemask, pick(M.speak_emote), +<<<<<<< refs/remotes/origin/master src, message_pieces, displayname, jobname, real_name, M.voice_name, filter_type, signal.data["compression"], using_map.get_map_levels(pos_z), connection.frequency, verb) +======= + src, message_pieces, displayname, jobname, real_name, M.voice_name, + filter_type, signal.data["compression"], using_map.get_map_levels(pos_z), connection.frequency, verb) +>>>>>>> Various fixes while waiting on PR /obj/item/device/radio/hear_talk(mob/M, list/message_pieces, verb) diff --git a/code/modules/alarm/atmosphere_alarm.dm b/code/modules/alarm/atmosphere_alarm.dm index 60e0529956..84302f1b65 100644 --- a/code/modules/alarm/atmosphere_alarm.dm +++ b/code/modules/alarm/atmosphere_alarm.dm @@ -5,7 +5,7 @@ var/list/major_alarms = new() var/list/map_levels = using_map.get_map_levels(z) for(var/datum/alarm/A in visible_alarms()) - if(z && (z && !(A.origin?.z in map_levels))) + if(z && !(A.origin?.z in map_levels)) continue if(A.max_severity() > 1) major_alarms.Add(A) @@ -15,7 +15,7 @@ var/list/minor_alarms = new() var/list/map_levels = using_map.get_map_levels(z) for(var/datum/alarm/A in visible_alarms()) - if(z && (z && !(A.origin?.z in map_levels))) + if(z && !(A.origin?.z in map_levels)) continue if(A.max_severity() == 1) minor_alarms.Add(A) diff --git a/code/modules/mob/new_player/new_player.dm b/code/modules/mob/new_player/new_player.dm index 58ecb4a31b..064f6f9e99 100644 --- a/code/modules/mob/new_player/new_player.dm +++ b/code/modules/mob/new_player/new_player.dm @@ -364,6 +364,7 @@ var/turf/T = join_props["turf"] var/join_message = join_props["msg"] + var/announce_channel = join_props["channel"] || "Common" if(!T || !join_message) return 0 diff --git a/code/modules/modular_computers/file_system/programs/generic/camera.dm b/code/modules/modular_computers/file_system/programs/generic/camera.dm index e07d9ec293..9f0cade5e9 100644 --- a/code/modules/modular_computers/file_system/programs/generic/camera.dm +++ b/code/modules/modular_computers/file_system/programs/generic/camera.dm @@ -15,12 +15,6 @@ return access_research if(NETWORK_ERT) return access_cent_specops - //VOREStation Add Start - if(NETWORK_TALON_SHIP) - return access_talon - if(NETWORK_TALON_HELMETS) - return access_talon - //VOREStation Add End if(network in using_map.station_networks) return access_security // Default for all other station networks From 194f73ad9814ba3e33352f8ec1c6bf4af0dd095e Mon Sep 17 00:00:00 2001 From: Novacat <35587478+Novacat@users.noreply.github.com> Date: Tue, 28 Apr 2020 09:31:05 -0400 Subject: [PATCH 6/7] Fixes conflict --- code/game/objects/items/devices/radio/radio.dm | 5 ----- 1 file changed, 5 deletions(-) diff --git a/code/game/objects/items/devices/radio/radio.dm b/code/game/objects/items/devices/radio/radio.dm index 679b03bb5b..e9a74b87c7 100644 --- a/code/game/objects/items/devices/radio/radio.dm +++ b/code/game/objects/items/devices/radio/radio.dm @@ -523,13 +523,8 @@ var/global/list/default_medbay_channels = list( //Nothing handled any sort of remote radio-ing and returned before now, just squawk on this zlevel. return Broadcast_Message(connection, M, voicemask, pick(M.speak_emote), -<<<<<<< refs/remotes/origin/master - src, message_pieces, displayname, jobname, real_name, M.voice_name, - filter_type, signal.data["compression"], using_map.get_map_levels(pos_z), connection.frequency, verb) -======= src, message_pieces, displayname, jobname, real_name, M.voice_name, filter_type, signal.data["compression"], using_map.get_map_levels(pos_z), connection.frequency, verb) ->>>>>>> Various fixes while waiting on PR /obj/item/device/radio/hear_talk(mob/M, list/message_pieces, verb) From c317247075fcdf9e5372139090e3f1c0f3700ece Mon Sep 17 00:00:00 2001 From: Novacat <35587478+Novacat@users.noreply.github.com> Date: Tue, 28 Apr 2020 10:01:21 -0400 Subject: [PATCH 7/7] Duplicate var --- code/game/objects/items/devices/radio/radio.dm | 1 - 1 file changed, 1 deletion(-) diff --git a/code/game/objects/items/devices/radio/radio.dm b/code/game/objects/items/devices/radio/radio.dm index e9a74b87c7..587c5b3551 100644 --- a/code/game/objects/items/devices/radio/radio.dm +++ b/code/game/objects/items/devices/radio/radio.dm @@ -359,7 +359,6 @@ var/global/list/default_medbay_channels = list( if(!istype(message_mode, /datum/radio_frequency)) //if not a special case, it should be returning a radio connection return FALSE - var/datum/radio_frequency/connection = message_mode var/pos_z = get_z(src) var/datum/radio_frequency/connection = message_mode