From a45da3f0bbe630f1c758e6e4a0f96e4e78d44a24 Mon Sep 17 00:00:00 2001 From: Aronai Sieyes Date: Mon, 11 May 2020 21:35:06 -0400 Subject: [PATCH] Add UAV and UAV control software --- code/_helpers/game.dm | 8 +- code/game/machinery/doors/door.dm | 5 + code/game/objects/items/uav.dm | 348 ++++++++++++++++++ code/game/objects/objs.dm | 1 + code/modules/mob/living/carbon/human/human.dm | 2 +- code/modules/mob/living/carbon/human/life.dm | 24 +- code/modules/mob/living/life.dm | 2 - code/modules/mob/mob_movement.dm | 15 +- .../computers/modular_computer/core.dm | 12 + .../modular_computers/file_system/program.dm | 8 +- .../file_system/programs/generic/uav.dm | 266 +++++++++++++ .../hardware/network_card.dm | 33 +- code/modules/nano/modules/nano_module.dm | 3 + code/modules/research/mechfab_designs.dm | 9 + icons/obj/uav.dmi | Bin 0 -> 11615 bytes nano/templates/mod_uav.tmpl | 53 +++ polaris.dme | 2 + 17 files changed, 761 insertions(+), 30 deletions(-) create mode 100644 code/game/objects/items/uav.dm create mode 100644 code/modules/modular_computers/file_system/programs/generic/uav.dm create mode 100644 icons/obj/uav.dmi create mode 100644 nano/templates/mod_uav.tmpl diff --git a/code/_helpers/game.dm b/code/_helpers/game.dm index 7be9093596..438483b07b 100644 --- a/code/_helpers/game.dm +++ b/code/_helpers/game.dm @@ -272,17 +272,17 @@ var/list/hearturfs = list() for(var/thing in hear) - if(istype(thing,/obj)) + if(istype(thing, /obj)) //Can't use isobj() because /atom/movable returns true in that, and so lighting overlays would be included objs += thing hearturfs |= get_turf(thing) - else if(istype(thing,/mob)) + if(ismob(thing)) mobs += thing hearturfs |= get_turf(thing) //A list of every mob with a client for(var/mob in player_list) - if(!istype(mob, /mob)) - crash_with("There is a null or non-mob reference inside player_list.") + if(!ismob(mob)) + player_list -= mob continue if(get_turf(mob) in hearturfs) mobs |= mob diff --git a/code/game/machinery/doors/door.dm b/code/game/machinery/doors/door.dm index b866504b33..f778612351 100644 --- a/code/game/machinery/doors/door.dm +++ b/code/game/machinery/doors/door.dm @@ -112,6 +112,11 @@ else bumpopen(M) + if(istype(AM, /obj/item/device/uav)) + if(check_access(null)) + open() + else + do_animate("deny") if(istype(AM, /mob/living/bot)) var/mob/living/bot/bot = AM diff --git a/code/game/objects/items/uav.dm b/code/game/objects/items/uav.dm new file mode 100644 index 0000000000..2b323e4898 --- /dev/null +++ b/code/game/objects/items/uav.dm @@ -0,0 +1,348 @@ +#define UAV_OFF 0 +#define UAV_ON 1 +#define UAV_PAIRING 2 +#define UAV_PACKED 3 + +/obj/item/device/uav + name = "recon skimmer" + desc = "A semi-portable reconnaissance drone that folds into a backpack-sized carrying case." + icon = 'icons/obj/uav.dmi' + icon_state = "uav" + + var/obj/item/weapon/cell/cell + var/cell_type = null //Can put a starting cell here + + density = 1 //Is dense, but not anchored, so you can swap with it + slowdown = 3 //Heevvee. + + health = 100 + var/power_per_process = 50 // About 6.5 minutes of use on a high-cell (10,000) + var/state = UAV_OFF + + var/datum/effect/effect/system/ion_trail_follow/ion_trail + + var/list/mob/living/masters + + // So you know which is which + var/nickname = "Generic Droan" + + // Radial menu + var/static/image/radial_pickup = image(icon = 'icons/mob/radial.dmi', icon_state = "radial_pickup") + var/static/image/radial_wrench = image(icon = 'icons/mob/radial.dmi', icon_state = "radial_wrench") + var/static/image/radial_power = image(icon = 'icons/mob/radial.dmi', icon_state = "radial_power") + var/static/image/radial_pair = image(icon = 'icons/mob/radial.dmi', icon_state = "radial_pair") + + // Movement cooldown + var/next_move = 0 + + // Idle shutdown time + var/no_masters_time = 0 + +/obj/item/device/uav/loaded + cell_type = /obj/item/weapon/cell/high + +/obj/item/device/uav/Initialize() + . = ..() + + if(!cell && cell_type) + cell = new cell_type + + ion_trail = new /datum/effect/effect/system/ion_trail_follow() + ion_trail.set_up(src) + ion_trail.stop() + +/obj/item/device/uav/Destroy() + qdel_null(cell) + qdel_null(ion_trail) + LAZYCLEARLIST(masters) + STOP_PROCESSING(SSobj, src) + return ..() + +/obj/item/device/uav/attack_hand(var/mob/user) + //Has to be on the ground to work with it properly + if(!isturf(loc)) + return ..() + + var/list/options = list( + "Pick Up" = radial_pickup, + "(Dis)Assemble" = radial_wrench, + "Toggle Power" = radial_power, + "Pairing Mode" = radial_pair) + var/choice = show_radial_menu(user, src, options, require_near = !issilicon(user)) + + switch(choice) + // Can pick up when off or packed + if("Pick Up") + if(state == UAV_OFF || state == UAV_PACKED) + return ..() + else + to_chat(user,"Turn [nickname] off or pack it first!") + return + // Can disasemble or reassemble from packed or off (and this one takes time) + if("(Dis)Assemble") + if(can_transition_to(state == UAV_PACKED ? UAV_OFF : UAV_PACKED, user) && do_after(user, 10 SECONDS, src)) + return toggle_packed(user) + // Can toggle power from on and off + if("Toggle Power") + if(can_transition_to(state == UAV_ON ? UAV_OFF : UAV_ON, user)) + return toggle_power(user) + // Can pair when off + if("Pairing Mode") + if(can_transition_to(state == UAV_PAIRING ? UAV_OFF : UAV_PAIRING, user)) + return toggle_pairing(user) + +/obj/item/device/uav/attackby(var/obj/item/I, var/mob/user) + if(istype(I, /obj/item/modular_computer) && state == UAV_PAIRING) + var/obj/item/modular_computer/MC = I + LAZYDISTINCTADD(MC.paired_uavs, weakref(src)) + playsound(src, 'sound/machines/buttonbeep.ogg', 50, 1) + visible_message("[user] pairs [I] to [nickname]") + toggle_pairing() + + else if(I.is_screwdriver() && cell) + if(do_after(user, 3 SECONDS, src)) + to_chat(user, "You remove [cell] into [nickname].") + playsound(src, I.usesound, 50, 1) + power_down() + cell.forceMove(get_turf(src)) + cell = null + + else if(istype(I, /obj/item/weapon/cell) && !cell) + if(do_after(user, 3 SECONDS, src)) + to_chat(user, "You insert [I] into [nickname].") + playsound(src, 'sound/items/deconstruct.ogg', 50, 1) + power_down() + user.remove_from_mob(I) + I.forceMove(src) + cell = I + + else if(istype(I, /obj/item/weapon/pen) || istype(I, /obj/item/device/flashlight/pen)) + var/tmp_label = sanitizeSafe(input(user, "Enter a nickname for [src]", "Nickname", nickname), MAX_NAME_LEN) + if(length(tmp_label) > 50 || length(tmp_label) < 3) + to_chat(user, "The nickname must be between 3 and 50 characters.") + else + to_chat(user, "You scribble your new nickname on the side of [src].") + nickname = tmp_label + desc = initial(desc) + " This one has '[nickname]' scribbled on the side." + else + return ..() + +/obj/item/device/uav/proc/can_transition_to(var/new_state, var/mob/user) + switch(state) //Current one + if(UAV_ON) + if(new_state == UAV_OFF || new_state == UAV_PACKED) + . = TRUE + if(UAV_OFF) + if(new_state == UAV_ON || new_state == UAV_PACKED || new_state == UAV_PAIRING) + . = TRUE + if(UAV_PAIRING) + if(new_state == UAV_OFF) + . = TRUE + if(UAV_PACKED) + if(new_state == UAV_OFF) + . = TRUE + + if(!.) + if(user) + to_chat(user, "You can't do that while [nickname] is in this state.") + return FALSE + +/obj/item/device/uav/update_icon() + cut_overlays() + switch(state) + if(UAV_PAIRING) + add_overlay("[initial(icon_state)]_pairing") + icon_state = "[initial(icon_state)]" + if(UAV_ON) + icon_state = "[initial(icon_state)]_on" + if(UAV_OFF) + icon_state = "[initial(icon_state)]" + if(UAV_PACKED) + icon_state = "[initial(icon_state)]_packed" + +/obj/item/device/uav/process() + if(cell?.use(power_per_process) != power_per_process) + visible_message("[src] sputters and thuds to the ground, inert.") + playsound(src, 'sound/items/drop/metalboots.ogg', 75, 1) + power_down() + health -= initial(health)*0.25 //Lose 25% of your original health + + if(LAZYLEN(masters)) + no_masters_time = 0 + else if(no_masters_time++ > 50) + power_down() + +/obj/item/device/uav/proc/toggle_pairing() + switch(state) + if(UAV_PAIRING) + state = UAV_OFF + update_icon() + return TRUE + if(UAV_OFF) + state = UAV_PAIRING + update_icon() + return TRUE + return FALSE + +/obj/item/device/uav/proc/toggle_power() + switch(state) + if(UAV_OFF) + power_up() + return TRUE + if(UAV_ON) + power_down() + return TRUE + return FALSE + +/obj/item/device/uav/proc/toggle_packed() + if(UAV_ON) + power_down() + switch(state) + if(UAV_OFF) //Packing + state = UAV_PACKED + w_class = ITEMSIZE_LARGE + slowdown = 1 + density = FALSE + update_icon() + return TRUE + if(UAV_PACKED) //Unpacking + state = UAV_OFF + w_class = ITEMSIZE_HUGE + slowdown = 3 + density = TRUE + update_icon() + return TRUE + return FALSE + +/obj/item/device/uav/proc/power_up() + if(state != UAV_OFF || !isturf(loc)) + return + if(cell?.use(power_per_process) != power_per_process) + visible_message("[src] sputters and chugs as it tries, and fails, to power up.") + return + + state = UAV_ON + update_icon() + start_hover() + set_light(4, 4, "#FFFFFF") + START_PROCESSING(SSobj, src) + no_masters_time = 0 + visible_message("[nickname] buzzes and lifts into the air.") + +/obj/item/device/uav/proc/power_down() + if(state != UAV_ON) + return + + state = UAV_OFF + update_icon() + stop_hover() + set_light(0) + LAZYCLEARLIST(masters) + STOP_PROCESSING(SSobj, src) + visible_message("[nickname] gracefully settles onto the ground.") + +//////////////// Helpers +/obj/item/device/uav/get_cell() + return cell + +/obj/item/device/uav/relaymove(var/mob/user, direction, signal = 1) + if(signal && state == UAV_ON && (weakref(user) in masters)) + if(next_move <= world.time) + next_move = world.time + (1 SECOND/signal) + step(src, direction) + return TRUE // Even if we couldn't step, we're taking credit for absorbing the move + return FALSE + +/obj/item/device/uav/proc/get_status_string() + return "[nickname] - [get_x(src)],[get_y(src)],[get_z(src)] - I:[health]/[initial(health)] - C:[cell ? "[cell.charge]/[cell.maxcharge]" : "Not Installed"]" + +/obj/item/device/uav/proc/add_master(var/mob/living/M) + LAZYDISTINCTADD(masters, weakref(M)) + +/obj/item/device/uav/proc/remove_master(var/mob/living/M) + LAZYREMOVE(masters, weakref(M)) + +/obj/item/device/uav/check_eye() + if(state == UAV_ON) + return 0 + else + return -1 + +/obj/item/device/uav/proc/start_hover() + if(!ion_trail.on) //We'll just use this to store if we're floating or not + ion_trail.start() + var/amplitude = 2 //maximum displacement from original position + var/period = 36 //time taken for the mob to go up >> down >> original position, in deciseconds. Should be multiple of 4 + + var/top = old_y + amplitude + var/bottom = old_y - amplitude + var/half_period = period / 2 + var/quarter_period = period / 4 + + animate(src, pixel_y = top, time = quarter_period, easing = SINE_EASING | EASE_OUT, loop = -1) //up + animate(pixel_y = bottom, time = half_period, easing = SINE_EASING, loop = -1) //down + animate(pixel_y = old_y, time = quarter_period, easing = SINE_EASING | EASE_IN, loop = -1) //back + +/obj/item/device/uav/proc/stop_hover() + if(ion_trail.on) + ion_trail.stop() + animate(src, pixel_y = old_y, time = 5, easing = SINE_EASING | EASE_IN) //halt animation + +/obj/item/device/uav/hear_talk(var/mob/M, list/message_pieces, verb) + var/name_used = M.GetVoice() + for(var/wr_master in masters) + var/weakref/wr = wr_master + var/mob/master = wr.resolve() + var/message = master.combine_message(message_pieces, verb, M) + var/rendered = "UAV received: [name_used] [message]" + master.show_message(rendered, 2) + +/obj/item/device/uav/see_emote(var/mob/living/M, text) + for(var/wr_master in masters) + var/weakref/wr = wr_master + var/mob/master = wr.resolve() + var/rendered = "UAV received, [text]" + master.show_message(rendered, 2) + +/obj/item/device/uav/show_message(msg, type, alt, alt_type) + for(var/wr_master in masters) + var/weakref/wr = wr_master + var/mob/master = wr.resolve() + var/rendered = "UAV received, [msg]" + master.show_message(rendered, type) + +/obj/item/device/uav/take_damage(var/damage) + health -= damage + CheckHealth() + return + +/obj/item/device/uav/attack_generic(var/mob/user, var/damage, var/attack_verb) + visible_message("[user] [attack_verb] the [src]!") + playsound(src, 'sound/weapons/smash.ogg', 50, 1) + user.do_attack_animation(src) + health -= damage + CheckHealth() + return + +/obj/item/device/uav/ex_act(severity) + switch(severity) + if(1.0) + die() + if(2.0) + health -= 25 + CheckHealth() + +/obj/item/device/uav/proc/CheckHealth() + if(health <= 0) + die() + +/obj/item/device/uav/proc/die() + visible_message("[src] shorts out and explodes!") + power_down() + var/turf/T = get_turf(src) + qdel(src) + explosion(T, -1, 0, 1, 2) //Not very large + +#undef UAV_OFF +#undef UAV_ON +#undef UAV_PACKED \ No newline at end of file diff --git a/code/game/objects/objs.dm b/code/game/objects/objs.dm index a14a62eaa8..2a361f1d46 100644 --- a/code/game/objects/objs.dm +++ b/code/game/objects/objs.dm @@ -142,6 +142,7 @@ return /mob/proc/unset_machine() + machine?.remove_visual(src) src.machine = null /mob/proc/set_machine(var/obj/O) diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm index db67313aa0..e54577585e 100644 --- a/code/modules/mob/living/carbon/human/human.dm +++ b/code/modules/mob/living/carbon/human/human.dm @@ -418,7 +418,7 @@ BITSET(hud_updateflag, WANTED_HUD) if(istype(usr,/mob/living/carbon/human)) var/mob/living/carbon/human/U = usr - U.handle_regular_hud_updates() + U.handle_hud_list() if(istype(usr,/mob/living/silicon/robot)) var/mob/living/silicon/robot/U = usr U.handle_regular_hud_updates() diff --git a/code/modules/mob/living/carbon/human/life.dm b/code/modules/mob/living/carbon/human/life.dm index b8ad9fb56d..08019ebb0f 100644 --- a/code/modules/mob/living/carbon/human/life.dm +++ b/code/modules/mob/living/carbon/human/life.dm @@ -58,8 +58,8 @@ ..() - if(life_tick%30==15) - hud_updateflag = 1022 + if(life_tick % 30) + hud_updateflag = (1 << TOTAL_HUDS) - 1 voice = GetVoice() @@ -90,7 +90,7 @@ else if(stat == DEAD && !stasis) handle_defib_timer() - if(!handle_some_updates()) + if(skip_some_updates()) return //We go ahead and process them 5 times for HUD images and other stuff though. //Update our name based on whether our face is obscured/disfigured @@ -98,10 +98,10 @@ pulse = handle_pulse() -/mob/living/carbon/human/proc/handle_some_updates() +/mob/living/carbon/human/proc/skip_some_updates() if(life_tick > 5 && timeofdeath && (timeofdeath < 5 || world.time - timeofdeath > 6000)) //We are long dead, or we're junk mobs spawned like the clowns on the clown shuttle - return 0 - return 1 + return 1 + return 0 /mob/living/carbon/human/breathe() if(!inStasisNow()) @@ -928,7 +928,7 @@ //DO NOT CALL handle_statuses() from this proc, it's called from living/Life() as long as this returns a true value. /mob/living/carbon/human/handle_regular_status_updates() - if(!handle_some_updates()) + if(skip_some_updates()) return 0 if(status_flags & GODMODE) return 0 @@ -1269,8 +1269,11 @@ else bodytemp.icon_state = "temp0" - if(blinded) overlay_fullscreen("blind", /obj/screen/fullscreen/blind) - else clear_fullscreens() + if(blinded) + overlay_fullscreen("blind", /obj/screen/fullscreen/blind) + + else if(!machine) + clear_fullscreens() if(disabilities & NEARSIGHTED) //this looks meh but saves a lot of memory by not requiring to add var/prescription if(glasses) //to every /obj/item @@ -1370,11 +1373,12 @@ if(machine) var/viewflags = machine.check_eye(src) - machine.apply_visual(src) if(viewflags < 0) reset_view(null, 0) else if(viewflags && !looking_elsewhere) sight |= viewflags + else + machine.apply_visual(src) else if(eyeobj) if(eyeobj.owner != src) diff --git a/code/modules/mob/living/life.dm b/code/modules/mob/living/life.dm index 0718dc9421..360984a2f0 100644 --- a/code/modules/mob/living/life.dm +++ b/code/modules/mob/living/life.dm @@ -172,13 +172,11 @@ if(ear_damage < 100) adjustEarDamage(-0.05,-1) -//this handles hud updates. Calls update_vision() and handle_hud_icons() /mob/living/handle_regular_hud_updates() if(!client) return 0 ..() - handle_vision() handle_darksight() handle_hud_icons() diff --git a/code/modules/mob/mob_movement.dm b/code/modules/mob/mob_movement.dm index ea4259effd..fd23efc9da 100644 --- a/code/modules/mob/mob_movement.dm +++ b/code/modules/mob/mob_movement.dm @@ -110,8 +110,8 @@ /client/Move(n, direct) - if(!mob) - return // Moved here to avoid nullrefs below + //if(!mob) // Clients cannot have a null mob, as enforced by byond + // return // Moved here to avoid nullrefs below if(mob.control_object) Move_object(direct) @@ -166,8 +166,11 @@ if(!mob.canmove) return - //if(istype(mob.loc, /turf/space) || (mob.flags & NOGRAV)) - // if(!mob.Process_Spacemove(0)) return 0 + //Relaymove could handle it + if(mob.machine) + var/result = mob.machine.relaymove(mob, direct) + if(result) + return result if(!mob.lastarea) mob.lastarea = get_area(mob.loc) @@ -214,10 +217,6 @@ direct = turn(direct, pick(90, -90)) return mob.buckled.relaymove(mob,direct) - if(istype(mob.machine, /obj/machinery)) - if(mob.machine.relaymove(mob,direct)) - return - if(mob.pulledby || mob.buckled) // Wheelchair driving! if(istype(mob.loc, /turf/space)) return // No wheelchair driving in space diff --git a/code/modules/modular_computers/computers/modular_computer/core.dm b/code/modules/modular_computers/computers/modular_computer/core.dm index 2848468da8..db15e841ca 100644 --- a/code/modules/modular_computers/computers/modular_computer/core.dm +++ b/code/modules/modular_computers/computers/modular_computer/core.dm @@ -255,6 +255,18 @@ else return ..() +/obj/item/modular_computer/apply_visual(var/mob/user) + if(active_program) + return active_program.apply_visual(user) + +/obj/item/modular_computer/remove_visual(var/mob/user) + if(active_program) + return active_program.remove_visual(user) + +/obj/item/modular_computer/relaymove(var/mob/user, direction) + if(active_program) + return active_program.relaymove(user, direction) + /obj/item/modular_computer/proc/set_autorun(program) if(!hard_drive) return diff --git a/code/modules/modular_computers/file_system/program.dm b/code/modules/modular_computers/file_system/program.dm index 895b73b5fa..ef7eece062 100644 --- a/code/modules/modular_computers/file_system/program.dm +++ b/code/modules/modular_computers/file_system/program.dm @@ -203,8 +203,12 @@ /datum/computer_file/program/apply_visual(mob/M) if(NM) - NM.apply_visual(M) + return NM.apply_visual(M) /datum/computer_file/program/remove_visual(mob/M) if(NM) - NM.remove_visual(M) + return NM.remove_visual(M) + +/datum/computer_file/program/proc/relaymove(var/mob/M, direction) + if(NM) + return NM.relaymove(M, direction) \ No newline at end of file diff --git a/code/modules/modular_computers/file_system/programs/generic/uav.dm b/code/modules/modular_computers/file_system/programs/generic/uav.dm new file mode 100644 index 0000000000..8e4909ab04 --- /dev/null +++ b/code/modules/modular_computers/file_system/programs/generic/uav.dm @@ -0,0 +1,266 @@ +/obj/item/modular_computer + var/list/paired_uavs //Weakrefs, don't worry about it! + +/datum/computer_file/program/uav + filename = "rigger" + filedesc = "UAV Control" + nanomodule_path = /datum/nano_module/uav + program_icon_state = "comm_monitor" + program_key_state = "generic_key" + program_menu_icon = "link" + extended_desc = "This program allows remote control of certain drones, but only when paired with this device." + size = 12 + available_on_ntnet = 1 + //requires_ntnet = 1 + +/datum/nano_module/uav + name = "UAV Control program" + var/obj/item/device/uav/current_uav = null //The UAV we're watching + var/signal_strength = 0 //Our last signal strength report (cached for a few seconds) + var/signal_test_counter = 0 //How long until next signal strength check + var/list/viewers //Who's viewing a UAV through us + var/adhoc_range = 30 //How far we can operate on a UAV without NTnet + +/datum/nano_module/uav/Destroy() + if(LAZYLEN(viewers)) + for(var/weakref/W in viewers) + var/M = W.resolve() + if(M) + unlook(M) + . = ..() + +/datum/nano_module/uav/ui_interact(mob/user, ui_key = "main", datum/nanoui/ui = null, force_open = 1, state = default_state) + var/list/data = host.initial_data() + + if(current_uav) + if(QDELETED(current_uav)) + set_current(null) + else if(signal_test_counter-- <= 0) + signal_strength = get_signal_to(current_uav) + if(!signal_strength) + set_current(null) + else // Don't reset counter until we find a UAV that's actually in range we can stay connected to + signal_test_counter = 20 + + data["current_uav"] = null + if(current_uav) + data["current_uav"] = list("status" = current_uav.get_status_string(), "power" = current_uav.state == 1 ? 1 : null) + data["signal_strength"] = signal_strength ? signal_strength >= 2 ? "High" : "Low" : "None" + data["in_use"] = LAZYLEN(viewers) + + var/list/paired_map = list() + var/obj/item/modular_computer/mc_host = nano_host() + if(istype(mc_host)) + for(var/puav in mc_host.paired_uavs) + var/weakref/wr = puav + var/obj/item/device/uav/U = wr.resolve() + paired_map[++paired_map.len] = list("name" = "[U ? U.nickname : "!!Missing!!"]", "uavref" = "\ref[U]") + + data["paired_uavs"] = paired_map + + ui = SSnanoui.try_update_ui(user, src, ui_key, ui, data, force_open) + if (!ui) + ui = new(user, src, ui_key, "mod_uav.tmpl", "UAV Control", 600, 500, state = state) + ui.set_initial_data(data) + ui.open() + ui.set_auto_update(1) + +/datum/nano_module/uav/Topic(var/href, var/href_list = list(), var/datum/topic_state/state) + if((. = ..())) + return + state = state || DefaultTopicState() || global.default_state + if(CanUseTopic(usr, state, href_list) == STATUS_INTERACTIVE) + CouldUseTopic(usr) + return OnTopic(usr, href_list, state) + CouldNotUseTopic(usr) + return TRUE + +/datum/nano_module/uav/proc/OnTopic(var/mob/user, var/list/href_list) + if(href_list["switch_uav"]) + var/obj/item/device/uav/U = locate(href_list["switch_uav"]) //This is a \ref to the UAV itself + if(!istype(U)) + to_chat(usr,"Something is blocking the connection to that UAV. In-person investigation is required.") + return TOPIC_NOACTION + + if(!get_signal_to(U)) + to_chat(usr,"The screen freezes for a moment, before returning to the UAV selection menu. It's not able to connect to that UAV.") + return TOPIC_NOACTION + + set_current(U) + return TOPIC_REFRESH + + if(href_list["del_uav"]) + var/refstring = href_list["del_uav"] //This is a \ref to the UAV itself + var/obj/item/modular_computer/mc_host = nano_host() + //This is so we can really scrape up any weakrefs that can't resolve + for(var/weakref/wr in mc_host.paired_uavs) + if(wr.ref == refstring) + if(current_uav?.weakref == wr) + set_current(null) + LAZYREMOVE(mc_host.paired_uavs, wr) + + else if(href_list["view_uav"]) + if(!current_uav) + return TOPIC_NOACTION + + if(current_uav.check_eye(user) < 0) + to_chat(usr,"The screen freezes for a moment, before returning to the UAV selection menu. It's not able to connect to that UAV.") + else + viewing_uav(user) ? unlook(user) : look(user) + return TOPIC_NOACTION + + else if(href_list["power_uav"]) + if(!current_uav) + return TOPIC_NOACTION + else if(current_uav.toggle_power()) + //Clean up viewers faster + if(LAZYLEN(viewers)) + for(var/weakref/W in viewers) + var/M = W.resolve() + if(M) + unlook(M) + return TOPIC_REFRESH + +/datum/nano_module/uav/proc/DefaultTopicState() + return global.default_state + +/datum/nano_module/uav/proc/CouldNotUseTopic(mob/user) + . = ..() + unlook(user) + +/datum/nano_module/uav/proc/CouldUseTopic(mob/user) + . = ..() + if(viewing_uav(user)) + look(user) + +/datum/nano_module/uav/proc/set_current(var/obj/item/device/uav/U) + if(current_uav == U) + return + + signal_strength = 0 + current_uav = U + + if(LAZYLEN(viewers)) + for(var/weakref/W in viewers) + var/M = W.resolve() + if(M) + if(current_uav) + to_chat(M, "You're disconnected from the UAV's camera!") + unlook(M) + else + look(M) + +//// +//// Finding signal strength between us and the UAV +//// +/datum/nano_module/uav/proc/get_signal_to(var/atom/movable/AM) + // Following roughly the ntnet signal levels + // 0 is none + // 1 is weak + // 2 is strong + var/obj/item/modular_computer/host = nano_host() //Better not add this to anything other than modular computers. + if(!istype(host)) + return + var/our_signal = host.get_ntnet_status() //1 low, 2 good, 3 wired, 0 none + var/their_z = get_z(AM) + + //If we have no NTnet connection don't bother getting theirs + if(!our_signal) + if(get_z(host) == their_z && (get_dist(host, AM) < adhoc_range)) + return 1 //We can connect (with weak signal) in same z without ntnet, within 30 turfs + else + return 0 + + var/list/zlevels_in_range = using_map.get_map_levels(their_z, FALSE) + var/list/zlevels_in_long_range = using_map.get_map_levels(their_z, TRUE) - zlevels_in_range + var/their_signal = 0 + for(var/relay in ntnet_global.relays) + var/obj/machinery/ntnet_relay/R = relay + if(!R.operable()) + continue + if(R.z == their_z) + their_signal = 2 + break + if(R.z in zlevels_in_range) + their_signal = 2 + break + if(R.z in zlevels_in_long_range) + their_signal = 1 + break + + if(!their_signal) //They have no NTnet at all + if(get_z(host) == their_z && (get_dist(host, AM) < adhoc_range)) + return 1 //We can connect (with weak signal) in same z without ntnet, within 30 turfs + else + return 0 + else + return max(our_signal, their_signal) + +//// +//// UAV viewer handling +//// +/datum/nano_module/uav/proc/viewing_uav(mob/user) + return (weakref(user) in viewers) + +/datum/nano_module/uav/proc/look(var/mob/user) + if(issilicon(user)) //Too complicated for me to want to mess with at the moment + to_chat(user, "Regulations prevent you from controlling several corporeal forms at the same time!") + return + + if(!current_uav) + return + + user.set_machine(nano_host()) + user.reset_view(current_uav) + current_uav.add_master(user) + LAZYDISTINCTADD(viewers, weakref(user)) + +/datum/nano_module/uav/proc/unlook(var/mob/user) + user.unset_machine() + user.reset_view() + if(current_uav) + current_uav.remove_master(user) + LAZYREMOVE(viewers, weakref(user)) + +/datum/nano_module/uav/check_eye(var/mob/user) + if(get_dist(user, nano_host()) > 1 || user.blinded || !current_uav) + unlook(user) + return -1 + + var/viewflag = current_uav.check_eye(user) + if (viewflag < 0) //camera doesn't work + unlook(user) + return -1 + + return viewflag + +//// +//// Relaying movements to the UAV +//// +/datum/nano_module/uav/relaymove(var/mob/user, direction) + if(current_uav) + return current_uav.relaymove(user, direction, signal_strength) + +//// +//// The effects when looking through a UAV +//// +/datum/nano_module/uav/apply_visual(var/mob/M) + if(!M.client) + return + if(weakref(M) in viewers) + M.overlay_fullscreen("fishbed",/obj/screen/fullscreen/fishbed) + M.overlay_fullscreen("scanlines",/obj/screen/fullscreen/scanline) + + if(signal_strength <= 1) + M.overlay_fullscreen("whitenoise",/obj/screen/fullscreen/noise) + else + M.clear_fullscreen("whitenoise", 0) + else + remove_visual(M) + +/datum/nano_module/uav/remove_visual(mob/M) + if(!M.client) + return + M.clear_fullscreen("fishbed",0) + M.clear_fullscreen("scanlines",0) + M.clear_fullscreen("whitenoise",0) diff --git a/code/modules/modular_computers/hardware/network_card.dm b/code/modules/modular_computers/hardware/network_card.dm index 2f382e0f4d..1447c5008e 100644 --- a/code/modules/modular_computers/hardware/network_card.dm +++ b/code/modules/modular_computers/hardware/network_card.dm @@ -39,6 +39,27 @@ var/global/ntnet_card_uid = 1 icon_state = "netcard_advanced" hardware_size = 1 +/obj/item/weapon/computer_hardware/network_card/quantum + name = "quantum NTNet network card" + desc = "A network card that can connect to NTnet from anywhere, using quantum entanglement." + long_range = 1 + origin_tech = list(TECH_DATA = 6, TECH_ENGINEERING = 7) + power_usage = 200 // Infinite range but higher power usage. + icon_state = "netcard_advanced" + hardware_size = 1 + +/obj/item/weapon/computer_hardware/network_card/quantum/get_signal(var/specific_action = 0) + if(!holder2) + return 0 + + if(!enabled) + return 0 + + if(!check_functionality() || !ntnet_global || is_banned()) + return 0 + + return 2 + /obj/item/weapon/computer_hardware/network_card/wired name = "wired NTNet network card" desc = "An advanced network card for usage with standard NTNet frequencies. This one also supports wired connection." @@ -82,7 +103,8 @@ var/global/ntnet_card_uid = 1 var/holderz = get_z(holder2) if(!holderz) //no reception in nullspace return 0 - var/list/zlevels_in_range = using_map.get_map_levels(holderz, long_range) + var/list/zlevels_in_range = using_map.get_map_levels(holderz, FALSE) + var/list/zlevels_in_long_range = using_map.get_map_levels(holderz, TRUE) - zlevels_in_range var/best = 0 for(var/relay in ntnet_global.relays) var/obj/machinery/ntnet_relay/R = relay @@ -91,11 +113,16 @@ var/global/ntnet_card_uid = 1 continue //We're on the same z if(R.z == holderz) - best = 2 + best = 2 //Every network card gets high signal on the same z as the relay break // No point in going further //Not on the same z but within range anyway if(R.z in zlevels_in_range) - best = 1 + best = long_range ? 2 : 1 //High-power network cards get good signal further away + break + //Only in long range + if(long_range && (R.z in zlevels_in_long_range)) + best = 1 //High-power network cards can get low signal even at long range + break return best return 0 // No computer! diff --git a/code/modules/nano/modules/nano_module.dm b/code/modules/nano/modules/nano_module.dm index 90c213da1c..75c38c9ed3 100644 --- a/code/modules/nano/modules/nano_module.dm +++ b/code/modules/nano/modules/nano_module.dm @@ -62,3 +62,6 @@ /datum/proc/update_layout() return FALSE + +/datum/nano_module/proc/relaymove(var/mob/user, direction) + return FALSE diff --git a/code/modules/research/mechfab_designs.dm b/code/modules/research/mechfab_designs.dm index 125de3db6c..7316c42179 100644 --- a/code/modules/research/mechfab_designs.dm +++ b/code/modules/research/mechfab_designs.dm @@ -1030,3 +1030,12 @@ req_tech = list(TECH_MATERIAL = 7, TECH_ENGINEERING = 5, TECH_MAGNET = 5, TECH_POWER = 6, TECH_ILLEGAL = 3, TECH_BLUESPACE = 4, TECH_ARCANE = 2, TECH_PRECURSOR = 3) materials = list(MAT_DURASTEEL = 5000, MAT_GRAPHITE = 3000, MAT_MORPHIUM = 1500, MAT_OSMIUM = 1500, MAT_PHORON = 1750, MAT_VERDANTIUM = 3000, MAT_SUPERMATTER = 2000) build_path = /obj/item/rig_module/teleporter + +/datum/design/item/mechfab/uav/basic + name = "UAV - Recon Skimmer" + id = "recon_skimmer" + build_path = /obj/item/device/uav + time = 20 + req_tech = list(TECH_MATERIAL = 6, TECH_ENGINEERING = 5, TECH_PHORON = 3, TECH_MAGNET = 4, TECH_POWER = 6) + materials = list(DEFAULT_WALL_MATERIAL = 10000, "glass" = 6000, "silver" = 4000) + \ No newline at end of file diff --git a/icons/obj/uav.dmi b/icons/obj/uav.dmi new file mode 100644 index 0000000000000000000000000000000000000000..daae9a03da8721510823274e2cb9834a4d50de41 GIT binary patch literal 11615 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 +
+
UAV:
+
+ {{if data.current_uav}} + {{:data.current_uav.status}} + {{else}} + [Not Connected] + {{/if}} +
+
+ +
+
Signal:
+
+ {{if data.current_uav}} + {{:data.signal_strength}} + {{else}} + [Not Connected] + {{/if}} +
+
+ +
+
Power:
+
+ {{if data.current_uav}} + {{:helper.link(data.current_uav.power ? 'Online' : 'Offline', data.current_uav.power ? 'check' : 'close', {'power_uav' : 1}, null, data.current_uav.power ? 'linkOn' : 'redButton')}} + {{else}} + [Not Connected] + {{/if}} +
+
+ +
+
Camera:
+
+ {{if data.current_uav}} + {{:helper.link(data.current_uav.power ? 'Available' : 'Unavailable', data.current_uav.power ? 'check' : 'close', {'view_uav' : 1}, null, data.in_use ? 'linkOn' : null)}} + {{else}} + [Not Connected] + {{/if}} +
+
+ +
+
Paired UAVs:
+
+{{for data.paired_uavs}} +
+ {{:helper.link(value.name, '', {'switch_uav' : value.uavref})}}{{:helper.link('', 'close', {'del_uav' : value.uavref}, null, 'redButton')}} +
+{{/for}} diff --git a/polaris.dme b/polaris.dme index a1b1569da5..4d65767d28 100644 --- a/polaris.dme +++ b/polaris.dme @@ -943,6 +943,7 @@ #include "code\game\objects\items\toys.dm" #include "code\game\objects\items\trash.dm" #include "code\game\objects\items\trash_material.dm" +#include "code\game\objects\items\uav.dm" #include "code\game\objects\items\devices\ai_detector.dm" #include "code\game\objects\items\devices\aicard.dm" #include "code\game\objects\items\devices\binoculars.dm" @@ -2357,6 +2358,7 @@ #include "code\modules\modular_computers\file_system\programs\generic\ntdownloader.dm" #include "code\modules\modular_computers\file_system\programs\generic\ntnrc_client.dm" #include "code\modules\modular_computers\file_system\programs\generic\nttransfer.dm" +#include "code\modules\modular_computers\file_system\programs\generic\uav.dm" #include "code\modules\modular_computers\file_system\programs\generic\wordprocessor.dm" #include "code\modules\modular_computers\file_system\programs\medical\suit_sensors.dm" #include "code\modules\modular_computers\file_system\programs\research\email_administration.dm"