diff --git a/code/_globalvars/misc.dm b/code/_globalvars/misc.dm index c4528ed7ec8a..f9b5d2d64f46 100644 --- a/code/_globalvars/misc.dm +++ b/code/_globalvars/misc.dm @@ -132,3 +132,5 @@ GLOBAL_VAR(obfs_z) /// List of giant lizards that are alive. GLOBAL_LIST_EMPTY(giant_lizards_alive) +/// List of F5CT Field Camera Tripods +GLOBAL_LIST_EMPTY_TYPED(deployed_tripod_cameras, /obj/structure/overwatch_camera_tripod) diff --git a/code/game/machinery/vending/vendor_types/squad_prep/squad_engineer.dm b/code/game/machinery/vending/vendor_types/squad_prep/squad_engineer.dm index 50ecbef3b1a7..9d685e9a5647 100644 --- a/code/game/machinery/vending/vendor_types/squad_prep/squad_engineer.dm +++ b/code/game/machinery/vending/vendor_types/squad_prep/squad_engineer.dm @@ -83,6 +83,7 @@ GLOBAL_LIST_INIT(cm_vending_gear_engi, list( list("Motion Detector", 8, /obj/item/device/motiondetector, null, VENDOR_ITEM_REGULAR), list("Whistle", 3, /obj/item/clothing/accessory/device/whistle, null, VENDOR_ITEM_REGULAR), list("Synthetic Reset Key", 10, /obj/item/device/defibrillator/synthetic, null, VENDOR_ITEM_REGULAR), + list("FCT - Field Camera Tripod", 5, /obj/item/device/overwatch_camera/tripod, null, VENDOR_ITEM_REGULAR), list("BINOCULARS", 0, null, null, null), list("Binoculars", 5, /obj/item/device/binoculars, null, VENDOR_ITEM_REGULAR), diff --git a/code/modules/cm_marines/equipment/gear.dm b/code/modules/cm_marines/equipment/gear.dm index 33fb84c2942d..ba4ea6f5e1b5 100644 --- a/code/modules/cm_marines/equipment/gear.dm +++ b/code/modules/cm_marines/equipment/gear.dm @@ -357,3 +357,220 @@ /obj/item/device/overwatch_camera/see_emote(mob/living/sourcemob, emote, audible) SEND_SIGNAL(src, COMSIG_BROADCAST_SEE_EMOTE, sourcemob, emote, audible, loc == sourcemob && audible) + +/obj/item/device/overwatch_camera/tripod + name = "FTC Tripod Camera" + desc = "A Motoca-430-T deployable tripod camera that connects to the overwatch network. It can be renamed and deployed." + icon = 'icons/overwatch.dmi' // ToDO: Get real sprites + icon_state = "undeployed" + desc_lore = "Following modernisation efforts in the Marine'70 program, USCM Platoons were shrunk and squads re-organised to emphasise individual firepower and mobility. The Motoca-430-T, the precursor to the Motoca-500 Helmet Camera, was commissioned by the Department of Defense to be utilised by Colonial Marine squads in establishing secure perimeters and watching rear areas remotely through the Overwatch system." + var/label + var/datum/squad/squad + +/obj/item/device/overwatch_camera/tripod/Initialize(mapload, ...) + . = ..() + camera = new /obj/structure/machinery/camera/overwatch(src) + AddComponent(/datum/component/overwatch_console_control) + +/obj/item/device/overwatch_camera/tripod/Destroy() + QDEL_NULL(camera) + return ..() + +/obj/item/device/overwatch_camera/tripod/attack_self(mob/user) + ..() + var/choice = tgui_alert(user, "What would you like to do with [src]?", "Tripod Camera", list("Rename", "Deploy", "Cancel")) + switch(choice) + if("Cancel") + return + if("Rename") + var/new_name = tgui_input_text(user, "Enter new name for the camera:", "Rename Camera", label ? label : initial(name), MAX_NAME_LEN, ui_state=GLOB.not_incapacitated_state, encode=FALSE) + if(!new_name) + return + new_name = trim_right(replace_non_alphanumeric_plus(new_name)) + if(!length(new_name)) + to_chat(user, SPAN_WARNING("Invalid name.")) + return + label = new_name + name = new_name + if(camera) + camera.c_tag = new_name + to_chat(user, SPAN_NOTICE("Camera renamed to [name].")) + return + if("Deploy") + deploy_tripod(user) + +/obj/item/device/overwatch_camera/tripod/proc/deploy_tripod(mob/user) + var/datum/squad/user_squad = null // find squad for addition to label + + if(ishuman(user)) // synths can place so not strict check + var/mob/living/carbon/human/human_user = user + user_squad = human_user.assigned_squad + if(isyautja(user)) + to_chat(user, SPAN_WARNING("You can't think of a reason to interact with [src] and decide to leave it alone.")) + return + if(user.is_mob_incapacitated()) + return + // if(user. != src) + // to_chat(user, SPAN_WARNING("You need to hold [src] in your hand to deploy it!")) + // return + + var/turf/deploy_turf = get_turf(user) + if(!deploy_turf) + return + + var/area/deploy_area = get_area(deploy_turf) + if(!deploy_area.allow_construction) + to_chat(user, SPAN_WARNING("You cannot deploy [src] here!")) + return + if(istype(deploy_area, /area/shuttle)) + to_chat(user, SPAN_WARNING("You cannot deploy [src] in a shuttle area.")) // i copied this from M2C so idk if this is necessary? + return + if(!istype(deploy_turf, /turf/open)) + to_chat(user, SPAN_WARNING("[src] must be placed on a solid surface!")) + return + + for(var/obj/blocking_object in deploy_turf) + if(blocking_object.density && blocking_object != src) + to_chat(user, SPAN_WARNING("[blocking_object] is blocking the deployment spot!")) + return + + if(!do_after(user, 3 SECONDS, INTERRUPT_ALL, BUSY_ICON_BUILD)) + to_chat(user, SPAN_WARNING("You must stand still while deploying the tripod.")) + return + + if(user.stat != CONCIOUS || user.is_mob_incapacitated()) //not sure if this is the same check or not :D + return + + if(user.get_active_hand() != src) + to_chat(user, SPAN_WARNING("You must hold [src] in your hand to deploy it!")) + return + + var/base_label = label ? label : initial(name) + var/final_label = user_squad ? "[user_squad.name] - [base_label]" : base_label + + var/obj/structure/overwatch_camera_tripod/deployed_structure = new(deploy_turf) // transform to new struc + deployed_structure.label = final_label + deployed_structure.name = final_label + deployed_structure.squad = user_squad + deployed_structure.icon_state = "deployed" + + if(camera) + camera.forceMove(deployed_structure) + camera.c_tag = final_label + camera.status = TRUE + deployed_structure.camera = camera + src.camera = null + + to_chat(user, SPAN_NOTICE("You deploy [src].")) + user.temp_drop_inv_item() + qdel(src) + +/obj/structure/overwatch_camera_tripod + name = "FTC Tripod Camera" + desc = "A Motoca-430-T deployed tripod camera connected to the overwatch network." + icon = 'icons/overwatch.dmi' // ToDO: Get real sprites + icon_state = "deployed" + density = TRUE + anchored = TRUE + layer = OBJ_LAYER + desc_lore = "Following modernisation efforts in the Marine'70 program, USCM Platoons were shrunk and squads re-organised to emphasise individual firepower and mobility. The Motoca-430-T, the precursor to the Motoca-500 Helmet Camera, was commissioned by the Department of Defense to be utilised by Colonial Marine squads in establishing secure perimeters and watching rear areas remotely through the Overwatch system." + var/label = "Tripod Camera" + var/obj/structure/machinery/camera/camera + var/datum/squad/squad + var/slash_count = 0 // tracks xeno slashes 4 breaking + +/obj/structure/overwatch_camera_tripod/Initialize(mapload) + . = ..() + icon_state = "deployed" + camera = new /obj/structure/machinery/camera/overwatch(src) + camera.c_tag = label + camera.status = TRUE + AddComponent(/datum/component/overwatch_console_control) + GLOB.deployed_tripod_cameras += src + +/obj/structure/overwatch_camera_tripod/Destroy() + GLOB.deployed_tripod_cameras -= src + QDEL_NULL(camera) + return ..() + +/obj/structure/overwatch_camera_tripod/examine(mob/user) + . = ..() + to_chat(user, SPAN_INFO("The label label reads: [label]")) // ToDO: This maybe should be in the description box I just don't know how to add it atm + if(squad) + to_chat(user, SPAN_INFO("It is currently assigned to squad: [squad.name]")) // ToDO: This maybe should be in the description box I just don't know how to add it atm + +/obj/structure/overwatch_camera_tripod/attack_hand(mob/user) + if(user.a_intent != INTENT_HELP) // I've left this in just in case maints want me to change the tgui menu to intent handling or smth. + return ..() + var/choice = tgui_alert(user, "What would you like to do with [src]?", "Tripod Camera", list("Rename", "Pick Up", "Cancel")) + switch(choice) + if("Cancel") + return + if("Rename") + if(isyautja(user)) + to_chat(user, SPAN_WARNING("You can't think of a reason to interact with [src] and decide to leave it alone.")) + return + var/new_name = tgui_input_text(user, "Enter new label for the camera:", "Rename Camera", label, MAX_NAME_LEN, ui_state=GLOB.not_incapacitated_state, encode=FALSE) + if(!new_name) + return + new_name = trim_right(replace_non_alphanumeric_plus(new_name)) + if(!length(new_name)) + to_chat(user, SPAN_WARNING("Invalid name.")) + return + label = new_name + name = new_name + if(camera) + camera.c_tag = new_name + to_chat(user, SPAN_NOTICE("[src] renamed to [name].")) + return + if("Pick Up") + if(isyautja(user)) + to_chat(user, SPAN_WARNING("You can't think of a reason to interact with [src] and decide to leave it alone.")) + return + if(!user.Adjacent(src)) + to_chat(user, SPAN_WARNING("You must be closer to pick up [src].")) + return + if(!do_after(user, 2 SECONDS, INTERRUPT_ALL, BUSY_ICON_GENERIC)) + to_chat(user, SPAN_WARNING("You were interrupted while picking up the [src].")) + return + // Create a new tripod item from the structure + undeploy(user) + return // not sure if i need this here + +/obj/structure/overwatch_camera_tripod/attack_alien(mob/living/carbon/xenomorph/Xeno) + if(islarva(Xeno)) + return + slash_count++ + Xeno.animation_attack_on(src) + Xeno.flick_attack_overlay(src, "slash") + playsound(loc, 'sound/weapons/slash.ogg', 25, 1) + if(slash_count >= 3) + Xeno.visible_message(SPAN_DANGER("[Xeno] slashes [src] apart!"), + SPAN_DANGER("You tear through [src]!")) + undeploy() + else + Xeno.visible_message(SPAN_DANGER("[Xeno] slashes [src]!"), + SPAN_DANGER("You slash [src]!")) + return XENO_ATTACK_ACTION + +/obj/structure/overwatch_camera_tripod/proc/undeploy(mob/user) + var/obj/item/device/overwatch_camera/tripod/new_tripod = new(get_turf(src)) + new_tripod.label = label + new_tripod.name = label + new_tripod.squad = squad + if(camera) + camera.forceMove(new_tripod) + camera.c_tag = label + camera.status = TRUE + new_tripod.camera = camera + src.camera = null + if(user && ishuman(user)) + user.put_in_hands(new_tripod) + to_chat(user, SPAN_NOTICE("You disassemble [src].")) + else + new_tripod.visible_message(SPAN_WARNING("[new_tripod] falls to the floor.")) + qdel(src) + +/obj/structure/overwatch_camera_tripod/ex_act(severity) + if(severity >= EXPLOSION_THRESHOLD_LOW) // no idea if i need to add this or it's inherited from parent somewhere + undeploy() diff --git a/code/modules/cm_marines/overwatch.dm b/code/modules/cm_marines/overwatch.dm index 01d277572ee7..413a49e0abee 100644 --- a/code/modules/cm_marines/overwatch.dm +++ b/code/modules/cm_marines/overwatch.dm @@ -360,6 +360,40 @@ GLOBAL_LIST_EMPTY_TYPED(active_overwatch_consoles, /obj/structure/machinery/comp leader_count++ marine_count-- + for(var/obj/structure/overwatch_camera_tripod/tripod_camera in GLOB.deployed_tripod_cameras) // add cameras to list o' marines + if(current_squad && current_squad.name != "Root") + if(!tripod_camera.squad || tripod_camera.squad != current_squad) // tldr: show cameras in root squad if placed by non-squad marines + continue + if(!tripod_camera.camera || !tripod_camera.camera.can_use()) // skip broken (code) or damaged (in-game) cameras + continue // ToDO: There should be an error log if camera is missing camera comp. + if(!tripod_camera.loc) // skip null location cameras + continue // ToDO: Error Log if camera has no LOC + var/turf/camera_turf = get_turf(tripod_camera) + if(!camera_turf) + continue // ToDO: Error Log if camera has no turf. + switch(z_hidden) + if(HIDE_ALMAYER) + if(is_mainship_level(camera_turf.z)) + continue + if(HIDE_GROUND) + if(is_ground_level(camera_turf.z)) + continue + var/area/camera_area = get_area(tripod_camera) + var/camera_area_name = camera_area ? sanitize_area(camera_area.name) : "Unknown" + var/list/camera_data = list( + "name" = tripod_camera.label, + "state" = "Active", + "has_helmet" = TRUE, // can't click the button in OW if set to false + "role" = "Tripod Camera", + "acting_sl" = "", // not sure if i need to null these or not + "fteam" = "", + "distance" = "N/A", + "area_name" = camera_area_name, + "ref" = REF(tripod_camera), + "rank" = "", + ) + data["marines"] += list(camera_data) + data["total_deployed"] = leader_count + ftl_count + spec_count + medic_count + engi_count + smart_count + marine_count data["living_count"] = leaders_alive + ftl_alive + spec_alive + medic_alive + engi_alive + smart_alive + marines_alive @@ -788,47 +822,62 @@ GLOBAL_LIST_EMPTY_TYPED(active_overwatch_consoles, /obj/structure/machinery/comp return if(!params["target_ref"]) return - if(current_squad) - var/mob/living/carbon/human/cam_target = locate(params["target_ref"]) + if(!current_squad) + return - if(!istype(cam_target)) - return + var/atom/target_ref = locate(params["target_ref"]) + var/obj/structure/machinery/camera/new_cam = null + var/obj/item/new_holder = null + var/atom/cam_target = null - var/obj/item/new_holder = cam_target.get_camera_holder() - var/obj/structure/machinery/camera/new_cam + if(ishuman(target_ref)) // not strict since synths can be placed in OW squads + var/mob/living/carbon/human/Human = target_ref + cam_target = Human + new_holder = Human.get_camera_holder() if(new_holder) new_cam = new_holder.get_camera() - if(user.interactee != src) //if we multitasking - user.set_interaction(src) - if(cam == new_cam) //if we switch to a console that is already watching this cam - return - if(!new_cam || !new_cam.can_use()) - to_chat(user, "[icon2html(src, user)] [SPAN_WARNING("Searching for camera. No camera found for this marine! Tell your squad to put their cameras on!")]") - else if(cam && cam == new_cam)//click the camera you're watching a second time to stop watching. - visible_message("[icon2html(src, viewers(src))] [SPAN_BOLDNOTICE("Stopping camera view of [cam_target].")]") - for(var/datum/weakref/user_ref in concurrent_users) - var/mob/concurrent = user_ref.resolve() - if(!concurrent) - continue - stop_watching_camera(concurrent) + else if(istype(target_ref, /obj/structure/overwatch_camera_tripod)) + var/obj/structure/overwatch_camera_tripod/tripod_camera = target_ref + if(tripod_camera.camera) + new_cam = tripod_camera.camera + cam_target = tripod_camera + else + to_chat(user, "[icon2html(src, user)] [SPAN_WARNING("Invalid target.")]") + return + + if(user.interactee != src) //if we multitasking + user.set_interaction(src) + if(cam == new_cam) //if we switch to a console that is already watching this cam + return + if(!new_cam || !new_cam.can_use()) + to_chat(user, "[icon2html(src, user)] [SPAN_WARNING("Searching for camera. No camera found for this target!")]") + else if(cam && cam == new_cam)//click the camera you're watching a second time to stop watching. + visible_message("[icon2html(src, viewers(src))] [SPAN_BOLDNOTICE("Stopping camera view.")]") + for(var/datum/weakref/user_ref in concurrent_users) + var/mob/concurrent = user_ref.resolve() + if(!concurrent) + continue + stop_watching_camera(concurrent) + concurrent.UnregisterSignal(cam, COMSIG_PARENT_QDELETING) + disconnect_holder() + cam = null + else if(user.client.view != GLOB.world_view_size) + to_chat(user, SPAN_WARNING("You're too busy peering through binoculars.")) + else + for(var/datum/weakref/user_ref in concurrent_users) + var/mob/concurrent = user_ref.resolve() + if(!concurrent) + continue + if(cam) concurrent.UnregisterSignal(cam, COMSIG_PARENT_QDELETING) - disconnect_holder() - cam = null - else if(user.client.view != GLOB.world_view_size) - to_chat(user, SPAN_WARNING("You're too busy peering through binoculars.")) - else - for(var/datum/weakref/user_ref in concurrent_users) - var/mob/concurrent = user_ref.resolve() - if(!concurrent) - continue - if(cam) - concurrent.UnregisterSignal(cam, COMSIG_PARENT_QDELETING) - start_watching_camera(concurrent, new_cam) + start_watching_camera(concurrent, new_cam) + if(cam_target) set_onscreen_text(concurrent, cam_target) - concurrent.RegisterSignal(new_cam, COMSIG_PARENT_QDELETING, TYPE_PROC_REF(/mob, reset_observer_view_on_deletion)) - if(camera_holder) - disconnect_holder() - cam = new_cam + concurrent.RegisterSignal(new_cam, COMSIG_PARENT_QDELETING, TYPE_PROC_REF(/mob, reset_observer_view_on_deletion)) + if(camera_holder) + disconnect_holder() + cam = new_cam + if(new_holder) connect_holder(new_holder) if("change_operator") @@ -1580,6 +1629,15 @@ GLOBAL_LIST_EMPTY_TYPED(active_overwatch_consoles, /obj/structure/machinery/comp watcher.hud_used.overwatch_text.maptext = name_part + location_part + job_part + living_part + else if(istype(target, /obj/structure/overwatch_camera_tripod)) // on-screen text - in theory you can't click on a downed camera + var/obj/structure/overwatch_camera_tripod/tripod = target + var/area/current_area = get_area(tripod) + var/area_name = current_area ? sanitize_area(current_area.name) : "Unknown" + var/name_part = "[tripod.label]
" + var/location_part = "[area_name]
" + var/job_part = "Tripod Camera" + watcher.hud_used.overwatch_text.maptext = name_part + location_part + job_part + /obj/structure/machinery/computer/overwatch/almayer density = FALSE icon = 'icons/obj/structures/machinery/computer.dmi' diff --git a/icons/overwatch.dmi b/icons/overwatch.dmi new file mode 100644 index 000000000000..9dc21f644143 Binary files /dev/null and b/icons/overwatch.dmi differ