From f4fe45f8d353bfeee09ca4d6c45f32b542a5eb04 Mon Sep 17 00:00:00 2001 From: CHOMPStation2StaffMirrorBot <94713762+CHOMPStation2StaffMirrorBot@users.noreply.github.com> Date: Wed, 10 Dec 2025 19:00:49 -0700 Subject: [PATCH] [MIRROR] Medigun Port (#12098) Co-authored-by: Kashargul <144968721+Kashargul@users.noreply.github.com> Co-authored-by: Guti <32563288+TheCaramelion@users.noreply.github.com> Co-authored-by: vorestation-ci[bot] <199609141+vorestation-ci[bot]@users.noreply.github.com> Co-authored-by: Killian <49700375+KillianKirilenko@users.noreply.github.com> Co-authored-by: Jenny Co-authored-by: Cameron Lennox Co-authored-by: Will <7099514+Willburd@users.noreply.github.com> Co-authored-by: TheToaster98 <51209769+TheToaster98@users.noreply.github.com> Co-authored-by: Selis <12716288+ItsSelis@users.noreply.github.com> Co-authored-by: SatinIsle <98125273+SatinIsle@users.noreply.github.com> Co-authored-by: Aura Dusklight <46622484+NovaDusklight@users.noreply.github.com> Co-authored-by: Aroliacue <96730930+Aroliacue@users.noreply.github.com> Co-authored-by: Aroliacue Co-authored-by: nesquik <24830358+lbnesquik@users.noreply.github.com> Co-authored-by: ShadowLarkens Co-authored-by: MeepleMuncher <76881946+MeepleMuncher@users.noreply.github.com> Co-authored-by: Olive <49600480+zeskorion@users.noreply.github.com> Co-authored-by: shybandit5213 Co-authored-by: Bandit --- ATTRIBUTIONS.md | 17 + code/__defines/clothing.dm | 3 +- code/__defines/medigun.dm | 3 + code/__defines/research/techweb_nodes.dm | 1 + code/game/machinery/recharger.dm | 3 +- .../items/weapons/medigun/bork_medigun.dm | 115 ++++ .../items/weapons/medigun/linked_medigun.dm | 280 +++++++++ .../items/weapons/medigun/medigun_backpack.dm | 569 ++++++++++++++++++ .../weapons/medigun/medigun_backpack_ui.dm | 167 +++++ .../weapons/medigun/medigun_modifiers.dm | 8 + .../items/weapons/medigun/medigun_verbs.dm | 28 + .../objects/items/weapons/melee/energy.dm | 1 + .../research/tg/designs/weapon_designs.dm | 11 + .../experisci/experiment/instances/people.dm | 13 + .../tg/techwebs/nodes/medbay_nodes.dm | 12 + icons/mob/items/lefthand_medigun.dmi | Bin 0 -> 1564 bytes icons/mob/items/righthand_medigun.dmi | Bin 0 -> 1479 bytes icons/obj/borkmedigun.dmi | Bin 0 -> 9706 bytes .../Medigun/MedigunHelpers/ChargeStatus.tsx | 39 ++ .../Medigun/MedigunTabs/MedigunContent.tsx | 270 +++++++++ .../Medigun/MedigunTabs/MedigunParts.tsx | 149 +++++ .../tgui/interfaces/Medigun/constants.ts | 11 + .../tgui/interfaces/Medigun/index.tsx | 45 ++ .../packages/tgui/interfaces/Medigun/types.ts | 46 ++ .../tgui/interfaces/TraitTutorial.tsx | 1 - vorestation.dme | 6 + 26 files changed, 1795 insertions(+), 3 deletions(-) create mode 100644 code/__defines/medigun.dm create mode 100644 code/game/objects/items/weapons/medigun/bork_medigun.dm create mode 100644 code/game/objects/items/weapons/medigun/linked_medigun.dm create mode 100644 code/game/objects/items/weapons/medigun/medigun_backpack.dm create mode 100644 code/game/objects/items/weapons/medigun/medigun_backpack_ui.dm create mode 100644 code/game/objects/items/weapons/medigun/medigun_modifiers.dm create mode 100644 code/game/objects/items/weapons/medigun/medigun_verbs.dm create mode 100644 icons/mob/items/lefthand_medigun.dmi create mode 100644 icons/mob/items/righthand_medigun.dmi create mode 100644 icons/obj/borkmedigun.dmi create mode 100644 tgui/packages/tgui/interfaces/Medigun/MedigunHelpers/ChargeStatus.tsx create mode 100644 tgui/packages/tgui/interfaces/Medigun/MedigunTabs/MedigunContent.tsx create mode 100644 tgui/packages/tgui/interfaces/Medigun/MedigunTabs/MedigunParts.tsx create mode 100644 tgui/packages/tgui/interfaces/Medigun/constants.ts create mode 100644 tgui/packages/tgui/interfaces/Medigun/index.tsx create mode 100644 tgui/packages/tgui/interfaces/Medigun/types.ts diff --git a/ATTRIBUTIONS.md b/ATTRIBUTIONS.md index ceaf2d5674..c8e739501a 100644 --- a/ATTRIBUTIONS.md +++ b/ATTRIBUTIONS.md @@ -110,3 +110,20 @@ **Creator / Copyright:** Toriate
**License Holders:** Matica
**License:** [CC BY-NC-SA 3.0](https://creativecommons.org/licenses/by-nc-sa/3.0/)
+
+**File:** `icons/obj/borkmedigun.dmi`
+**Creator:** Commissioned by Cross_Exonar from Toriate
+**Link:** https://github.com/TS-Rogue-Star/Rogue-Star/pull/1010
+**License:** [CC BY-SA 3.0](https://creativecommons.org/licenses/by-sa/3.0/)
+
+**File:** `icons/mob/items/lefthand_medigun.dmi`
+**Icon-States:**medblaster-wielded, medblaster, medblaster_cmo
+**Creator:** Commissioned by Cross_Exonar from Toriate
+**Link:** https://github.com/TS-Rogue-Star/Rogue-Star/pull/1010
+**License:** [CC BY-SA 3.0](https://creativecommons.org/licenses/by-sa/3.0/)
+
+**File:** `icons/mob/items/righthand_medigun.dmi`
+**Icon-States:**medblaster-wielded, medblaster, medblaster_cmo
+**Creator:** Commissioned by Cross_Exonar from Toriate
+**Link:** https://github.com/TS-Rogue-Star/Rogue-Star/pull/1010
+**License:** [CC BY-SA 3.0](https://creativecommons.org/licenses/by-sa/3.0/)
diff --git a/code/__defines/clothing.dm b/code/__defines/clothing.dm index 0eda939656..549cf6243f 100644 --- a/code/__defines/clothing.dm +++ b/code/__defines/clothing.dm @@ -168,7 +168,8 @@ NOTICE: Do not leave trailing commas!!!! #define POCKET_BAYSUIT \ /obj/item/storage/backpack, \ /obj/item/bluespaceradio, \ - /obj/item/defib_kit + /obj/item/defib_kit, \ + /obj/item/medigun_backpack /// Wrapper for adding clothing based traits #define ADD_CLOTHING_TRAIT(mob, trait) ADD_TRAIT(mob, trait, "[CLOTHING_TRAIT]_[REF(src)]") diff --git a/code/__defines/medigun.dm b/code/__defines/medigun.dm new file mode 100644 index 0000000000..87167eeac2 --- /dev/null +++ b/code/__defines/medigun.dm @@ -0,0 +1,3 @@ +#define MEDIGUN_IDLE 0 +#define MEDIGUN_CANCELLED 1 +#define MEDIGUN_BUSY 2 diff --git a/code/__defines/research/techweb_nodes.dm b/code/__defines/research/techweb_nodes.dm index 182ebc14e8..191827e461 100644 --- a/code/__defines/research/techweb_nodes.dm +++ b/code/__defines/research/techweb_nodes.dm @@ -85,6 +85,7 @@ #define TECHWEB_NODE_MEDBAY_EQUIP_ADV "medbay_equip_adv" #define TECHWEB_NODE_MEDBAY_EQUIP_HIGH_TECH "medbay_equip_high_tech" #define TECHWEB_NODE_MEDIGUN "medbay_medigun" +#define TECHWEB_NODE_MEDIGUN_CONSTANT "medbay_medigun_constant" #define TECHWEB_NODE_MINING "mining" #define TECHWEB_NODE_MINING_ADV "mining_adv" #define TECHWEB_NODE_MOD_ANOMALY "mod_anomaly" diff --git a/code/game/machinery/recharger.dm b/code/game/machinery/recharger.dm index 8ee26b5dad..b4beba089e 100644 --- a/code/game/machinery/recharger.dm +++ b/code/game/machinery/recharger.dm @@ -16,7 +16,8 @@ GLOBAL_LIST_INIT(allowed_recharger_devices, list( /obj/item/paicard, /obj/item/personal_shield_generator, /obj/item/gun/projectile/cell_loaded, - /obj/item/ammo_magazine/cell_mag + /obj/item/ammo_magazine/cell_mag, + /obj/item/medigun_backpack )) GLOBAL_LIST_INIT(allowed_wallcharger_devices, list( diff --git a/code/game/objects/items/weapons/medigun/bork_medigun.dm b/code/game/objects/items/weapons/medigun/bork_medigun.dm new file mode 100644 index 0000000000..220b9aefff --- /dev/null +++ b/code/game/objects/items/weapons/medigun/bork_medigun.dm @@ -0,0 +1,115 @@ +/obj/item/bork_medigun + name = "prototype bluespace medigun" + desc = "The Bork BSM-92 or 'Blue Space Medigun' utilizes advanced bluespace technology to transfer beneficial reagents directly to torn tissue. This way, even larger wounds can be mended efficiently in short periods of time" + icon = 'icons/obj/borkmedigun.dmi' + icon_state = "medblaster" + var/wielded_item_state = "medblaster-wielded" + var/base_icon_state = "medblaster" + item_icons = list( + slot_l_hand_str = 'icons/mob/items/lefthand_medigun.dmi', + slot_r_hand_str = 'icons/mob/items/righthand_medigun.dmi', + ) + w_class = ITEMSIZE_HUGE + force = 0 + var/beam_range = 3 // How many tiles away it can scan. Changing this also changes the box size. + var/busy = MEDIGUN_IDLE // Set to true when scanning, to stop multiple scans. + var/action_cancelled = FALSE + var/wielded = FALSE + var/mob/current_target + var/mgcmo + canremove = FALSE + +/obj/item/bork_medigun/update_twohanding() + var/mob/living/M = loc + if(istype(M) && M.item_is_in_hands(src) && !M.hands_are_full()) + wielded = TRUE + name = "[initial(name)] (wielded)" + else + wielded = FALSE + name = initial(name) + ..() + +/obj/item/bork_medigun/update_held_icon() + if(wielded_item_state) + var/mob/living/M = loc + if(istype(M)) + if(M.can_wield_item(src) && src.is_held_twohanded(M)) + LAZYSET(item_state_slots, slot_l_hand_str, wielded_item_state) + LAZYSET(item_state_slots, slot_r_hand_str, wielded_item_state) + else + LAZYSET(item_state_slots, slot_l_hand_str, initial(item_state)) + LAZYSET(item_state_slots, slot_r_hand_str, initial(item_state)) + ..() + +// Draws a box showing the limits of movement while scanning something. +// Only the client supplied will see the box. +/obj/item/bork_medigun/proc/draw_box(atom/A, box_size, client/C) + var/list/our_box = list() + // Things moved with pixel_[x|y] will move the box, so this is to correct that. + var/pixel_x_correction = -A.pixel_x + var/pixel_y_correction = -A.pixel_y + + var/our_icon_size = world.icon_size + // First, place the bottom-left corner. + our_box += draw_line(A, SOUTHWEST, (-box_size * our_icon_size) + pixel_x_correction, (-box_size * our_icon_size) + pixel_y_correction, C) + + var/rendered_size = (box_size * 2) - 1 + // Make a line on the bottom, going right. + for(var/i = 1 to rendered_size) + var/x_displacement = (-box_size * our_icon_size) + (our_icon_size * i) + pixel_x_correction + var/y_displacement = (-box_size * our_icon_size) + pixel_y_correction + our_box += draw_line(A, SOUTH, x_displacement, y_displacement, C) + + // Bottom-right corner. + our_box += draw_line(A, SOUTHEAST, (box_size * our_icon_size) + pixel_x_correction, (-box_size * our_icon_size) + pixel_y_correction, C) + + // Second line, for the right side going up. + for(var/i = 1 to rendered_size) + var/x_displacement = (box_size * our_icon_size) + pixel_x_correction + var/y_displacement = (-box_size * our_icon_size) + (our_icon_size * i) + pixel_y_correction + our_box += draw_line(A, EAST, x_displacement, y_displacement, C) + + // Top-right corner. + our_box += draw_line(A, NORTHEAST, (box_size * our_icon_size) + pixel_x_correction, (box_size * our_icon_size) + pixel_y_correction, C) + + // Third line, for the top, going right. + for(var/i = 1 to rendered_size) + var/x_displacement = (-box_size * our_icon_size) + (our_icon_size * i) + pixel_x_correction + var/y_displacement = (box_size * our_icon_size) + pixel_y_correction + our_box += draw_line(A, NORTH, x_displacement, y_displacement, C) + + // Top-left corner. + our_box += draw_line(A, NORTHWEST, (-box_size * our_icon_size) + pixel_x_correction, (box_size * our_icon_size) + pixel_y_correction, C) + + // Fourth and last line, for the left side going up. + for(var/i = 1 to rendered_size) + var/x_displacement = (-box_size * our_icon_size) + pixel_x_correction + var/y_displacement = (-box_size * our_icon_size) + (our_icon_size * i) + pixel_y_correction + our_box += draw_line(A, WEST, x_displacement, y_displacement, C) + return our_box + +// Draws an individual segment of the box. +/obj/item/bork_medigun/proc/draw_line(atom/A, line_dir, line_pixel_x, line_pixel_y, client/C) + var/image/line = image(icon = 'icons/effects/effects.dmi', loc = A, icon_state = "stripes", dir = line_dir) + line.pixel_x = line_pixel_x + line.pixel_y = line_pixel_y + line.plane = PLANE_FULLSCREEN // It's technically a HUD element but it doesn't need to show above item slots. + line.appearance_flags = RESET_TRANSFORM|RESET_COLOR|RESET_ALPHA|NO_CLIENT_COLOR|TILE_BOUND + line.alpha = 125 + C.images += line + return line + +// Removes the box that was generated before from the client. +/obj/item/bork_medigun/proc/delete_box(list/box_segments, client/C) + for(var/i in box_segments) + C.images -= i + qdel(i) + +/obj/item/bork_medigun/proc/color_box(list/box_segments, new_color, new_time) + for(var/i in box_segments) + animate(i, color = new_color, time = new_time) + +/obj/item/bork_medigun/attack_hand(mob/user) + if(user.get_inactive_hand() == src)// && loc != get_turf) + return + return ..() diff --git a/code/game/objects/items/weapons/medigun/linked_medigun.dm b/code/game/objects/items/weapons/medigun/linked_medigun.dm new file mode 100644 index 0000000000..40c5fc8409 --- /dev/null +++ b/code/game/objects/items/weapons/medigun/linked_medigun.dm @@ -0,0 +1,280 @@ +/obj/item/bork_medigun/linked + var/obj/item/medigun_backpack/medigun_base_unit + +/obj/item/bork_medigun/linked/Destroy() + if(medigun_base_unit) + var/obj/item/bork_medigun/medigun = medigun_base_unit.get_medigun() + //ensure the base unit's icon updates + if(medigun == src) + medigun = null + medigun_base_unit.replace_icon() + if(ismob(loc)) + var/mob/user = loc + user.update_inv_back() + medigun_base_unit = null + return ..() + +/obj/item/bork_medigun/linked/forceMove(atom/destination) //Forcemove override, ugh + if(destination == medigun_base_unit || destination == medigun_base_unit.loc || isturf(destination)) + . = doMove(destination, 0, 0) + if(isturf(destination)) + for(var/atom/A as anything in destination) // If we can't scan the turf, see if we can scan anything on it, to help with aiming. + if(istype(A,/obj/structure/closet )) + break + +/obj/item/bork_medigun/linked/proc/check_charge(var/charge_amt) + return (medigun_base_unit.bcell && medigun_base_unit.bcell.check_charge(charge_amt)) + +/obj/item/bork_medigun/linked/proc/checked_use(var/charge_amt) + return (medigun_base_unit.bcell && medigun_base_unit.bcell.checked_use(charge_amt)) + +/obj/item/bork_medigun/linked/attack_self(mob/living/user) + if(medigun_base_unit.is_twohanded()) + update_twohanding() + if(busy) + busy = MEDIGUN_CANCELLED + +/obj/item/bork_medigun/linked/proc/should_stop(var/mob/living/target, var/mob/living/user, var/active_hand) + if(!target || !user || (!active_hand && medigun_base_unit.is_twohanded()) || !istype(target) || !istype(user) || busy < MEDIGUN_BUSY) + return TRUE + + if((user.get_active_hand() != active_hand || wielded == 0) && medigun_base_unit.is_twohanded()) + to_chat(user, span_warning("Please keep your hands free!")) + return TRUE + + if(user.is_incorporeal()) + return TRUE + + if(user.incapacitated(INCAPACITATION_DEFAULT | INCAPACITATION_KNOCKDOWN | INCAPACITATION_DISABLED | INCAPACITATION_KNOCKOUT | INCAPACITATION_STUNNED | INCAPACITATION_RESTRAINED)) + return TRUE + + if(user.stat) + return TRUE + + if(target.isSynthetic()) + to_chat(user, span_warning("Target is not organic.")) + return TRUE + + //if(get_dist(user, target) > beam_range) + if(!(target in range(beam_range, user)) || (!(target in view(10, user)) && !(medigun_base_unit.smodule.get_rating() >= 5))) + to_chat(user, span_warning("You are too far away from \the [target] to heal them, Or they are not in view. Get closer.")) + return TRUE + + if(!isliving(target)) + //to_chat(user, span_warning("\the [target] is not a valid target.")) + return TRUE + + if(!ishuman(target)) + return TRUE + + /*if(!H.getBruteLoss() && !H.getFireLoss() && !H.getToxLoss())// && !H.getOxyLoss()) // No point Wasting fuel/power if target healed + playsound(src, 'sound/machines/ping.ogg', 50) + to_chat(user, span_warning("\the [target] is fully healed.")) + return TRUE + */ + return FALSE + +/obj/item/bork_medigun/linked/afterattack(atom/target, mob/user, proximity_flag) + // Things that invalidate the scan immediately. + if(isturf(target)) + for(var/atom/A as anything in target) // If we can't scan the turf, see if we can scan anything on it, to help with aiming. + if(isliving(A)) + target = A + break + if(!istype(medigun_base_unit, /obj/item/medigun_backpack/cmo)) + update_twohanding() + if(busy && !(target == current_target) && isliving(target)) + to_chat(user, span_warning("\The [src] is already targeting something.")) + return + + if(!ishuman(target)) + return + + if(!medigun_base_unit.smanipulator) + to_chat(user, span_warning("\The [src] Blinks a red error light, Manipulator missing.")) + return + if(!medigun_base_unit.scapacitor) + to_chat(user, span_warning("\The [src] Blinks a blue error light, capacitor missing.")) + return + if(!medigun_base_unit.slaser) + to_chat(user, span_warning("\The [src] Blinks an orange error light, laser missing.")) + return + if(!medigun_base_unit.smodule) + to_chat(user, span_warning("\The [src] Blinks a pink error light, scanning module missing.")) + return + if(!check_charge(5)) + to_chat(user, span_warning("\The [src] doesn't have enough charge left to do that.")) + return + if(get_dist(target, user) > beam_range) + to_chat(user, span_warning("You are too far away from \the [target] to affect it. Get closer.")) + return + + if(target == current_target && busy) + busy = MEDIGUN_CANCELLED + return + if(target == user) + to_chat(user, span_warning("Cant heal yourself.")) + return + if(!(target in range(beam_range, user)) || (!(target in view(10, user)) && !medigun_base_unit.smodule)) + to_chat(user, span_warning("You are too far away from \the [target] to heal them, Or they are not in view. Get closer.")) + return + + current_target = target + busy = MEDIGUN_BUSY + update_icon() + var/myicon = "medbeam_basic" + var/mycolor = "#037ffc" + var/datum/beam/scan_beam = user.Beam(target, icon = 'icons/obj/borkmedigun.dmi', icon_state = myicon, time = 6000) + var/filter = filter(type = "outline", size = 1, color = mycolor) + var/list/box_segments = list() + playsound(src, 'sound/weapons/wave.ogg', 50) + var/mob/living/carbon/human/H = target + to_chat(user, span_notice("Locking on to [H]")) + to_chat(H, span_warning("[user] is targetting you with their medigun")) + if(user.client) + box_segments = draw_box(target, beam_range, user.client) + color_box(box_segments, mycolor, 5) + process_medigun(H, user, filter) + + action_cancelled = FALSE + busy = MEDIGUN_IDLE + current_target = null + + // Now clean up the effects. + update_icon() + QDEL_NULL(scan_beam) + target.filters -= filter + if(user.client) // If for some reason they logged out mid-scan the box will be gone anyways. + delete_box(box_segments, user.client) + +/obj/item/bork_medigun/linked/proc/process_medigun(mob/living/carbon/human/H, mob/user, filter, ishealing = FALSE) + if(should_stop(H, user, user.get_active_hand())) + return + + if(do_after(user, 1 SECOND, user, timed_action_flags = IGNORE_USER_LOC_CHANGE, hidden = TRUE)) + var/washealing = ishealing // Did we heal last cycle + ishealing = FALSE // The default is 'we didn't heal this cycle' + if(!checked_use(5)) + to_chat(user, span_warning("\The [src] doesn't have enough charge left to do that.")) + return + if(H.stat == DEAD) + process_medigun(H, user, filter) + return + var/lastier = medigun_base_unit.slaser.get_rating() + if(lastier >= 2) + if(checked_use(5)) + H.add_modifier(/datum/modifier/medbeameffect, 2 SECONDS) + if(H.getHalLoss() && checked_use(5)) + H.adjustHalLoss(-20) + if(H.weakened && checked_use(5)) + H.AdjustWeakened(-1) + if(lastier >= 3) + if(H.paralysis && (checked_use(15))) + H.AdjustParalysis(-1) + + var/healmod = lastier + /*if(H.getBruteLoss()) + healmod = min(lastier,medigun_base_unit.brutecharge,H.getBruteLoss()) + if(medigun_base_unit.brutecharge >= healmod) + if(!checked_use(healmod)) + to_chat(user, span_warning("\The [src] doesn't have enough charge left to do that.")) + break + if(healmod < 0) + healmod = 0 + else + H.adjustBruteLoss(-healmod) + medigun_base_unit.brutecharge -= healmod + ishealing = 1 + if(H.getFireLoss()) + healmod = min(lastier,medigun_base_unit.burncharge,H.getFireLoss()) + if(medigun_base_unit.burncharge >= healmod) + if(!checked_use(healmod)) + to_chat(user, span_warning("\The [src] doesn't have enough charge left to do that.")) + break + if(healmod < 0) + healmod = 0 + else + H.adjustFireLoss(-healmod) + medigun_base_unit.burncharge -= healmod + ishealing = 1*/ + if(H.getToxLoss()) + healmod = min(lastier,medigun_base_unit.toxcharge,H.getToxLoss()) + if(medigun_base_unit.toxcharge >= healmod) + if(!checked_use(healmod)) + to_chat(user, span_warning("\The [src] doesn't have enough charge left to do that.")) + return + if(healmod < 0) + healmod = 0 + else + H.adjustToxLoss(-healmod) + medigun_base_unit.toxcharge -= healmod + ishealing = TRUE + if(H.getOxyLoss()) + healmod = min(10*lastier,H.getOxyLoss()) + if(!checked_use(min(10,healmod))) + to_chat(user, span_warning("\The [src] doesn't have enough charge left to do that.")) + return + H.adjustOxyLoss(-healmod) + ishealing = TRUE + + ishealing = process_wounds(H, lastier, lastier, ishealing) + //if(medigun_base_unit.brutecharge <= 0 || medigun_base_unit.burncharge <= 0 || medigun_base_unit.toxcharge <= 0) + medigun_base_unit.update_icon() + //if(medigun_base_unit.slaser.get_rating() >= 5) + + //Blood regeneration if there is some space + if(lastier >= 5) + if(H.vessel.get_reagent_amount("blood") < H.species.blood_volume) + var/datum/reagent/blood/B = locate() in H.vessel.reagent_list //Grab some blood + B.volume += min(5, (H.species.blood_volume - H.vessel.get_reagent_amount("blood")))// regenerate blood + + if(ishealing != washealing) // Either we stopped or started healing this cycle + if(ishealing) + H.filters += filter + else + H.filters -= filter + + process_medigun(H, user, filter, ishealing) + +/obj/item/bork_medigun/linked/proc/process_wounds(mob/living/carbon/human/H, heal_ticks, remaining_strength, ishealing) + while(heal_ticks > 0) + if(remaining_strength <= 0) + return ishealing + if((!H.getFireLoss() || medigun_base_unit.burncharge <= 0) && (!H.getBruteLoss() || medigun_base_unit.burncharge <= 0)) + return ishealing + + for(var/name in BP_ALL) + var/obj/item/organ/external/O = H.organs_by_name[name] + for(var/datum/wound/W in O.wounds) + if (W.internal) + continue + //if (W.bandaged && W.disinfected) + // continue + if (W.damage_type == BRUISE || W.damage_type == CUT || W.damage_type == PIERCE) + if(medigun_base_unit.brutecharge >= 1) + if(W.damage <= 1) + O.wounds -= W + medigun_base_unit.brutecharge -= 1 + ishealing = TRUE + else if(medigun_base_unit.brutecharge >= 1) + W.damage -= 1 + medigun_base_unit.brutecharge -= 1 + remaining_strength -= 1 + ishealing = TRUE + if (W.damage_type == BURN) + if(medigun_base_unit.burncharge >= 1) + if(W.damage <= 1) + O.wounds -= W + medigun_base_unit.burncharge -= 1 + ishealing = TRUE + else if(medigun_base_unit.burncharge >= 1) + W.damage -= 1 + medigun_base_unit.burncharge -= 1 + remaining_strength -= 1 + ishealing = TRUE + if(remaining_strength <= 0) + return ishealing + if(remaining_strength <= 0) + return ishealing + heal_ticks-- + return ishealing diff --git a/code/game/objects/items/weapons/medigun/medigun_backpack.dm b/code/game/objects/items/weapons/medigun/medigun_backpack.dm new file mode 100644 index 0000000000..c12b1a3db2 --- /dev/null +++ b/code/game/objects/items/weapons/medigun/medigun_backpack.dm @@ -0,0 +1,569 @@ +//backpack item +/obj/item/medigun_backpack + name = "protoype bluespace medigun backpack" + desc = "Contains a bluespace medigun, this portable unit digitizes and stores chems and battery power used by the attached gun." + icon = 'icons/obj/borkmedigun.dmi' + icon_override = 'icons/obj/borkmedigun.dmi' + icon_state = "mg-backpack" + item_state = "mg-backpack-onmob" + slot_flags = SLOT_BACK + force = 5 + throwforce = 6 + preserve_item = TRUE + w_class = ITEMSIZE_HUGE + unacidable = TRUE + origin_tech = list(TECH_BIO = 4, TECH_POWER = 2, TECH_BLUESPACE = 4) + + var/obj/item/bork_medigun/linked/medigun_path = /obj/item/bork_medigun/linked + var/obj/item/cell/bcell = /obj/item/cell + var/obj/item/cell/ccell = null + var/obj/item/stock_parts/matter_bin/sbin = /obj/item/stock_parts/matter_bin + var/obj/item/stock_parts/scanning_module/smodule = /obj/item/stock_parts/scanning_module + var/obj/item/stock_parts/manipulator/smanipulator = /obj/item/stock_parts/manipulator + var/obj/item/stock_parts/capacitor/scapacitor = /obj/item/stock_parts/capacitor + var/obj/item/stock_parts/micro_laser/slaser = /obj/item/stock_parts/micro_laser + var/charging = FALSE + var/brutecharge = 0 + var/toxcharge = 0 + var/burncharge = 0 + var/brutevol = 0 + var/toxvol = 0 + var/burnvol = 0 + var/chemcap = 60 + var/tankmax = 30 + var/containsgun = TRUE + var/maintenance = FALSE + var/smaniptier = 1 + var/sbintier = 1 + var/gridstatus = 0 + var/chargecap = 1000 + +//backpack item +/obj/item/medigun_backpack/cmo + name = "prototype bluespace medigun backpack - CMO" + desc = "Contains a compact version of the bluespace medigun able to be used one handed, this portable unit digitizes and stores chems and battery power used by the attached gun." + icon_state = "mg-backpack_cmo" + item_state = "mg-backpack_cmo-onmob" + scapacitor = /obj/item/stock_parts/capacitor/adv + smanipulator = /obj/item/stock_parts/manipulator/nano + smodule = /obj/item/stock_parts/scanning_module/adv + slaser = /obj/item/stock_parts/micro_laser/high + bcell = /obj/item/cell/apc + tankmax = 60 + brutecharge = 60 + toxcharge = 60 + burncharge = 60 + chemcap = 120 + brutevol = 120 + toxvol = 120 + burnvol = 120 + chargecap = 5000 + +/obj/item/medigun_backpack/proc/is_twohanded() + return TRUE + +/obj/item/medigun_backpack/cmo/is_twohanded() + return FALSE + +/obj/item/medigun_backpack/proc/apc_charge() + gridstatus = 0 + var/area/A = get_area(src) + if(!istype(A) || !A.powered(EQUIP)) + return FALSE + gridstatus = 1 + if(bcell && (bcell.charge < bcell.maxcharge)) + var/cur_charge = bcell.charge + var/delta = min(50, bcell.maxcharge-cur_charge) + bcell.give(delta) + A.use_power_oneoff(delta*100, EQUIP) + gridstatus = 2 + return TRUE + +/obj/item/medigun_backpack/proc/adjust_brutevol(modifier) + if(modifier > brutevol) + modifier = brutevol + if(modifier > (tankmax - brutecharge)) + modifier = tankmax - brutecharge + brutevol -= modifier + brutecharge += modifier + +/obj/item/medigun_backpack/proc/adjust_burnvol(modifier) + if(modifier > burnvol) + modifier = burnvol + if(modifier > (tankmax - burncharge)) + modifier = tankmax - burncharge + burnvol -= modifier + burncharge += modifier + +/obj/item/medigun_backpack/proc/adjust_toxvol(modifier) + if(modifier > toxvol) + modifier = toxvol + if(modifier > (tankmax - toxcharge)) + modifier = tankmax - toxcharge + toxvol -= modifier + toxcharge += modifier + +/obj/item/medigun_backpack/process() + if(!bcell) + return + + var/obj/item/bork_medigun/medigun = get_medigun() + + if(bcell.charge >= 10) + var/icon_needs_update = FALSE + if(brutecharge < tankmax && brutevol > 0 && (bcell.checked_use(smaniptier * 2))) + adjust_brutevol(smaniptier * 2) + icon_needs_update = TRUE + if(burncharge < tankmax && burnvol > 0 && (bcell.checked_use(smaniptier * 2))) + adjust_burnvol(smaniptier * 2) + icon_needs_update = TRUE + if(toxcharge < tankmax && toxvol > 0 && (bcell.checked_use(smaniptier * 2))) + adjust_toxvol(smaniptier * 2) + icon_needs_update = TRUE + //Alien tier + if(sbintier >= 5 && medigun.busy == MEDIGUN_IDLE && (bcell.charge >= 10)) + if(brutevol < chemcap && (bcell.checked_use(10))) + icon_needs_update = TRUE + brutevol ++ + if(burnvol < chemcap && (bcell.checked_use(10))) + icon_needs_update = TRUE + burnvol ++ + if(toxvol < chemcap && (bcell.checked_use(10))) + icon_needs_update = TRUE + toxvol ++ + + if(icon_needs_update) + update_icon() + + if(scapacitor.get_rating() >= 5) + if(apc_charge()) + charging = FALSE + return + charging = TRUE + + if(!charging || !ccell) + return + + var/scaptier = scapacitor.get_rating() + var/missing = min(scaptier*25, bcell.amount_missing()) + + if(missing > 0) + if(ccell && ccell.checked_use(missing)) + bcell.give(missing) + update_icon() + return + + if(ismob(loc)) + to_chat(loc, span_notice("The [ccell] runs out of power..")) + charging = FALSE + +/obj/item/medigun_backpack/get_cell() + return bcell + +/obj/item/medigun_backpack/update_icon() + . = ..() + cut_overlays() + if((bcell.percent() <= 5 )) + add_overlay(image('icons/obj/borkmedigun.dmi', "no_battery")) + else if((bcell.percent() <= 25 && bcell.percent() > 5)) + add_overlay(image('icons/obj/borkmedigun.dmi', "low_battery")) + + if(brutevol <= 0 && brutecharge > 0) + add_overlay(image('icons/obj/borkmedigun.dmi', "red")) + else if(brutecharge <= 0 && brutevol <= 0) + add_overlay(image('icons/obj/borkmedigun.dmi', "redstrike-blink")) + + if(toxvol <= 0 && toxcharge > 0) + add_overlay(image('icons/obj/borkmedigun.dmi', "green")) + else if(toxcharge <= 0 && toxvol <= 0) + add_overlay(image('icons/obj/borkmedigun.dmi', "greenstrike-blink")) + + if(burnvol <= 0 && burncharge > 0) + add_overlay(image('icons/obj/borkmedigun.dmi', "orange")) + else if(burncharge <= 0 && burnvol <= 0) + add_overlay(image('icons/obj/borkmedigun.dmi', "orangestrike-blink")) + +/obj/item/medigun_backpack/proc/replace_icon(inhand) + var/obj/item/bork_medigun/medigun = get_medigun() + if(inhand) + icon_state = "mg-backpack-deployed" + item_state = "mg-backpack-deployed-onmob" + if(is_twohanded()) + medigun.icon_state = "medblaster" + medigun.base_icon_state = "medblaster" + medigun.wielded_item_state = "medblaster-wielded" + medigun.update_icon() + else + medigun.icon_state = "medblaster_cmo" + medigun.base_icon_state = "medblaster_cmo" + medigun.wielded_item_state = "" + medigun.update_icon() + else if(is_twohanded()) + icon_state = "mg-backpack" + item_state = "mg-backpack-onmob" + medigun.icon_state = "medblaster" + medigun.base_icon_state = "medblaster" + else + icon_state = "mg-backpack_cmo" + item_state = "mg-backpack_cmo-onmob" + medigun.icon_state = "medblaster_cmo" + medigun.base_icon_state = "medblaster_cmo" + + update_icon() + +/obj/item/medigun_backpack/Initialize(mapload) + AddComponent(/datum/component/tethered_item, medigun_path) + . = ..() + + var/obj/item/bork_medigun/linked/medigun = get_medigun() + medigun.medigun_base_unit = src + + if(!is_twohanded()) + medigun.beam_range = 4 + medigun.icon_state = "medblaster_cmo" + medigun.base_icon_state = "medblaster_cmo" + medigun.wielded_item_state = "" + medigun.update_icon() + if(ispath(bcell)) + bcell = new bcell(src) + bcell.charge = 0 + if(ispath(sbin)) + sbin = new sbin(src) + if(ispath(smodule)) + smodule = new smodule(src) + START_PROCESSING(SSobj, src) + if(ispath(smanipulator)) + smanipulator = new smanipulator(src) + if(ispath(scapacitor)) + scapacitor = new scapacitor(src) + if(ispath(slaser)) + slaser = new slaser(src) + update_icon() + + +/obj/item/medigun_backpack/Destroy() + STOP_PROCESSING(SSobj, src) + QDEL_NULL(bcell) + QDEL_NULL(smodule) + QDEL_NULL(smanipulator) + QDEL_NULL(scapacitor) + QDEL_NULL(slaser) + . = ..() + +/obj/item/medigun_backpack/proc/get_medigun() + var/datum/component/tethered_item/TI = GetComponent(/datum/component/tethered_item) + return TI.get_handheld() + +/obj/item/medigun_backpack/emp_act(severity) + . = ..() + if(bcell) + bcell.emp_act(severity) + +/obj/item/medigun_backpack/attack_hand(mob/living/user) + // See important note in tethered_item.dm + if(SEND_SIGNAL(src,COMSIG_ITEM_ATTACK_SELF,user) & COMPONENT_CANCEL_ATTACK_CHAIN) + return TRUE + . = ..() + +/obj/item/medigun_backpack/MouseDrop() + if(ismob(src.loc)) + if(!CanMouseDrop(src)) + return + var/mob/M = src.loc + if(!M.unEquip(src)) + return + src.add_fingerprint(usr) + M.put_in_any_hand_if_possible(src) + /*icon_state = "mg-backpack" + item_state = "mg-backpack-onmob" + update_icon() //success + usr.update_inv_back()*/ + + +/obj/item/medigun_backpack/attackby(obj/item/W, mob/user, params) + if(refill_reagent(W, user)) + return + + var/obj/item/bork_medigun/medigun = get_medigun() + + if(W.is_crowbar() && maintenance) + if(smodule ) + smodule.forceMove(get_turf(loc)) + smodule = null + + if(smanipulator) + STOP_PROCESSING(SSobj, src) + smanipulator.forceMove(get_turf(loc)) + smanipulator = null + smaniptier = 0 + + if(slaser) + slaser.forceMove(get_turf(loc)) + slaser = null + + if(scapacitor) + STOP_PROCESSING(SSobj, src) + scapacitor.forceMove(get_turf(loc)) + scapacitor = null + + if(sbin) + STOP_PROCESSING(SSobj, src) + sbin.forceMove(get_turf(loc)) + sbin = null + sbintier = 0 + + to_chat(user, span_notice("You remove the Components from \the [src].")) + update_icon() + return TRUE + + if(W.is_screwdriver()) + if(!maintenance) + maintenance = TRUE + to_chat(user, span_notice("You open the maintenance hatch on \the [src].")) + return + + maintenance = FALSE + to_chat(user, span_notice("You close the maintenance hatch on \the [src].")) + return + + if(istype(W, /obj/item/cell)) + if(!user.unEquip(W)) + return + W.forceMove(src) + if(ccell) + to_chat(user, span_notice("You swap the [W] for \the [ccell].")) + ccell = W + to_chat(user, span_notice("You install the [W] into \the [src].")) + charging = TRUE + return + + if(maintenance) + if(istype(W, /obj/item/stock_parts/scanning_module)) + if(smodule) + to_chat(user, span_notice("\The [src] already has a scanning module.")) + else + if(!user.unEquip(W)) + return + W.forceMove(src) + smodule = W + to_chat(user, span_notice("You install the [W] into \the [src].")) + medigun.beam_range = 3+smodule.get_rating() + update_icon() + return + + if(istype(W, /obj/item/stock_parts/manipulator)) + if(smanipulator) + to_chat(user, span_notice("\The [src] already has a manipulator.")) + return + if(!user.unEquip(W)) + return + W.forceMove(src) + smanipulator = W + smaniptier = smanipulator.get_rating() + if(sbin && scapacitor)START_PROCESSING(SSobj, src) + to_chat(user, span_notice("You install the [W] into \the [src].")) + update_icon() + return + + if(istype(W, /obj/item/stock_parts/micro_laser)) + if(slaser) + to_chat(user, span_notice("\The [src] already has a micro laser.")) + return + if(!user.unEquip(W)) + return + W.forceMove(src) + slaser = W + to_chat(user, span_notice("You install the [W] into \the [src].")) + update_icon() + return + + if(istype(W, /obj/item/stock_parts/capacitor)) + if(scapacitor) + to_chat(user, span_notice("\The [src] already has a capacitor.")) + return + if(!user.unEquip(W)) + return + W.forceMove(src) + scapacitor = W + var/scaptier = scapacitor.get_rating() + if(scaptier == 1) + chargecap = 1000 + bcell.maxcharge = 1000 + if(bcell.charge > chargecap) + bcell.charge = chargecap + else if(scaptier == 2) + chargecap = 2000 + bcell.maxcharge = 2000 + if(bcell.charge > chargecap) + bcell.charge = chargecap + else if(scaptier == 3) + chargecap = 3000 + bcell.maxcharge = 3000 + if(bcell.charge > chargecap) + bcell.charge = chargecap + else if(scaptier == 4) + chargecap = 4000 + bcell.maxcharge = 4000 + if(bcell.charge > chargecap) + bcell.charge = chargecap + else if(scaptier == 5) + chargecap = 5000 + bcell.maxcharge = 5000 + if(bcell.charge > chargecap) + bcell.charge = chargecap + + if(sbin && smanipulator)START_PROCESSING(SSobj, src) + to_chat(user, span_notice("You install the [W] into \the [src].")) + update_icon() + return + + if(istype(W, /obj/item/stock_parts/matter_bin)) + if(sbin) + to_chat(user, span_notice("\The [src] already has a matter bin.")) + return + if(!user.unEquip(W)) + return + W.forceMove(src) + sbin = W + sbintier = sbin.get_rating() + if(sbintier >= 5) + chemcap = 300 + tankmax = 150 + else + chemcap = 60*(sbintier) + tankmax = 30*sbintier + if(brutecharge > chemcap) + brutecharge = chemcap + if(burncharge > chemcap) + burncharge = chemcap + if(toxcharge > chemcap) + toxcharge = chemcap + if(brutecharge > tankmax) + brutecharge = tankmax + if(burncharge > tankmax) + burncharge = tankmax + if(toxcharge > tankmax) + toxcharge = tankmax + if(scapacitor && smanipulator)START_PROCESSING(SSobj, src) + to_chat(user, span_notice("You install the [W] into \the [src].")) + update_icon() + return + + return ..() + + +/obj/item/medigun_backpack/proc/refill_reagent(var/obj/item/container, mob/user) + . = FALSE + if(!maintenance && (istype(container, /obj/item/reagent_containers/glass/beaker) || istype(container, /obj/item/reagent_containers/glass/bottle))) + + if(!(container.flags & OPENCONTAINER)) + to_chat(user, span_warning("You need to open the [container] first!")) + return + + var/reagentwhitelist = list(REAGENT_ID_BICARIDINE, REAGENT_ID_ANTITOXIN, REAGENT_ID_KELOTANE, REAGENT_ID_DERMALINE)//, "tricordrazine") + + for(var/G in container.reagents.reagent_list) + var/datum/reagent/R = G + var/modifier = 1 + var/totransfer = 0 + var/name = "" + + if(R.id in reagentwhitelist) + switch(R.id) + if(REAGENT_ID_BICARIDINE) + name = "bruteheal" + modifier = 4 + totransfer = chemcap - brutevol + if(REAGENT_ID_ANTITOXIN) + name = "toxheal" + modifier = 4 + totransfer = chemcap - toxvol + if(REAGENT_ID_KELOTANE) + name = "burnheal" + modifier = 4 + totransfer = chemcap - burnvol + if(REAGENT_ID_DERMALINE) + name = "burnheal" + modifier = 8 + totransfer = chemcap - burnvol + /*if("tricordrazine") + name = "tricordrazine" + modifier = 1 + if((brutevol != chemcap) && (burnvol != chemcap) && (toxvol != chemcap)) + totransfer = 1 //tempcheck to get past the totransfer check + else + totransfer = 0*/ + if(totransfer <= 0) + to_chat(user, span_notice("The [src] cannot accept anymore [name]!")) + totransfer = min(totransfer, container.reagents.get_reagent_amount(R.id) * modifier) + + switch(R.id) + if(REAGENT_ID_BICARIDINE) + brutevol += totransfer + if(REAGENT_ID_ANTITOXIN) + toxvol += totransfer + if(REAGENT_ID_KELOTANE) + burnvol += totransfer + if(REAGENT_ID_DERMALINE) + burnvol += totransfer + /*if("tricordrazine") //Tricord too problematic + var/maxamount = container.reagents.get_reagent_amount(R.id) + var/amountused + var/oldbrute = brutevol + var/oldburn = burnvol + var/oldtox = toxvol + + while(maxamount > 0) + if(brutevol >= chemcap && burnvol >= chemcap && toxvol >= chemcap) + break + maxamount -- + amountused++ + totransfer ++ + if(brutevol < chemcap) + brutevol ++ + if(burnvol < chemcap) + burnvol ++ + if(toxvol < chemcap) + toxvol ++ + var/readout = "You add [amountused] units of [R.name] to the [src]. \n The [src] Stores " + var/readoutadditions = FALSE + if(oldbrute != brutevol) + readout += "[round(brutevol - oldbrute)] U of bruteheal vol" + readoutadditions = TRUE + if(oldburn != burnvol) + if(readoutadditions) + readout += ", " + readout += "[round(burnvol - oldburn)] U of burnheal vol" + readoutadditions = TRUE + if(oldtox != toxvol) + if(readoutadditions) + readout += ", " + readout += "[round(toxvol - oldtox)] U of toxheal vol" + if(oldbrute != brutevol || oldburn != burnvol || oldtox != toxvol)to_chat(user, span_notice("[readout]."))*/ + if(totransfer > 0) + if(R.id != "tricordrazine") + to_chat(user, span_notice("You add [totransfer / modifier] units of [R.name] to the [src]. \n The [src] stores [round(totransfer)] U of [name].")) + container.reagents.remove_reagent(R.id, totransfer / modifier) + playsound(src, 'sound/weapons/empty.ogg', 50, 1) + update_icon() + . = TRUE + return + +//checks that the base unit is in the correct slot to be used +/obj/item/medigun_backpack/proc/slot_check() + var/mob/M = loc + if(!istype(M)) + return FALSE //not equipped + + if((slot_flags & SLOT_BACK) && M.get_equipped_item(slot_back) == src) + return TRUE + if((slot_flags & SLOT_BACK) && M.get_equipped_item(slot_s_store) == src) + return TRUE + return FALSE + +/obj/item/medigun_backpack/dropped(mob/user) + ..() + replace_icon() + +/obj/item/medigun_backpack/proc/checked_use(var/charge_amt) + return (bcell && bcell.checked_use(charge_amt)) diff --git a/code/game/objects/items/weapons/medigun/medigun_backpack_ui.dm b/code/game/objects/items/weapons/medigun/medigun_backpack_ui.dm new file mode 100644 index 0000000000..b53ff6e229 --- /dev/null +++ b/code/game/objects/items/weapons/medigun/medigun_backpack_ui.dm @@ -0,0 +1,167 @@ +/obj/item/medigun_backpack/tgui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "Medigun", name) + ui.open() + +/obj/item/medigun_backpack/tgui_data(mob/user) + var/obj/item/bork_medigun/medigun = get_medigun() + var/mob/living/carbon/human/H = medigun.current_target + var/patientname + var/patienthealth = 0 + var/patientbruteloss = 0 + var/patientfireloss = 0 + var/patienttoxloss = 0 + var/patientoxyloss = 0 + var/patientstatus = 0 + var/list/bloodData = list() + var/inner_bleeding = FALSE + var/organ_damage = FALSE + + //var/minhealth = 0 + if(scapacitor?.get_rating() < 5) + gridstatus = 3 + if(H) + for(var/obj/item/organ/org in H.internal_organs) + if(org.robotic >= ORGAN_ROBOT) + continue + if(org.status & ORGAN_BLEEDING) + inner_bleeding = TRUE + if(org.damage >= 1 && !istype(org, /obj/item/organ/internal/brain)) + organ_damage = TRUE + patientname = H + patienthealth = max(0, (H.health + abs(-H.getMaxHealth())) / (H.getMaxHealth() + abs(-H.getMaxHealth()))) + patientbruteloss = H.getBruteLoss() + patientfireloss = H.getFireLoss() + patienttoxloss = H.getToxLoss() + patientoxyloss = H.getOxyLoss() + patientstatus = H.stat + if(H.vessel) + bloodData["volume"] = round(H.vessel.get_reagent_amount("blood")) + bloodData["max_volume"] = H.species.blood_volume + var/list/data = list( + "maintenance" = maintenance, + "generator" = charging, + "gridstatus" = gridstatus, + "tankmax" = tankmax, + "power_cell_status" = bcell ? bcell.percent() : null, + "cell_status" = ccell ? (ccell.percent()/100) : null, + "bruteheal_charge" = scapacitor ? brutecharge : null, + "burnheal_charge" = scapacitor ? burncharge : null, + "toxheal_charge" = scapacitor ? toxcharge : null, + "bruteheal_vol" = sbin ? brutevol : null, + "burnheal_vol" = sbin ? burnvol : null, + "toxheal_vol" = sbin ? toxvol : null, + "patient_name" = smodule ? patientname : null, + "patient_health" = smodule ? patienthealth : null, + "patient_brute" = smodule ? patientbruteloss : null, + "patient_burn" = smodule ? patientfireloss : null, + "patient_tox" = smodule ? patienttoxloss : null, + "patient_oxy" = smodule ? patientoxyloss : null, + "blood_status" = smodule ? bloodData : null, + "patient_status" = smodule ? patientstatus : null, + "organ_damage" = smodule ? organ_damage : null, + "inner_bleeding" = smodule ? inner_bleeding : null, + "examine_data" = get_examine_data() + ) + return data + +/obj/item/medigun_backpack/proc/get_examine_data() + var/obj/item/bork_medigun/medigun = get_medigun() + + return list( + "smodule" = smodule ? list("name" = smodule.name, "range" = medigun.beam_range, "rating" = smodule.get_rating()) : null, + "smanipulator" = smanipulator ? list("name" = smanipulator.name, "rating" = smaniptier) : null, + "slaser" = slaser ? list("name" = slaser.name, "rating" = slaser.get_rating()) : null, + "scapacitor" = scapacitor ? list("name" = scapacitor.name, "chargecap" = chargecap, "rating" = scapacitor.get_rating()) : null, + "sbin" = sbin ? list("name" = sbin.name, "chemcap" = chemcap, "tankmax" = tankmax, "rating" = sbin.get_rating()) : null + ) + +/obj/item/medigun_backpack/tgui_act(action, params, datum/tgui/ui) + if(..()) + return TRUE + + . = TRUE + + var/obj/item/bork_medigun/medigun = get_medigun() + + switch(action) + if("celleject") + cell_eject() + return TRUE + + if("cancel_healing") + if(medigun?.busy) + medigun.busy = MEDIGUN_CANCELLED + return TRUE + + if("toggle_maintenance") + maintenance = !maintenance + return TRUE + + if("rem_smodule") + if(!smodule || !maintenance) + return FALSE + smodule.forceMove(get_turf(loc)) + to_chat(ui.user, span_notice("You remove the [smodule] from \the [src].")) + smodule = null + update_icon() + return TRUE + + if("rem_mani") + if(!smanipulator || !maintenance) + return FALSE + STOP_PROCESSING(SSobj, src) + smanipulator.forceMove(get_turf(loc)) + to_chat(ui.user, span_notice("You remove the [smanipulator] from \the [src].")) + smanipulator = null + smaniptier = 0 + update_icon() + return TRUE + + if("rem_laser") + if(!slaser || !maintenance) + return FALSE + slaser.forceMove(get_turf(loc)) + to_chat(ui.user, span_notice("You remove the [slaser] from \the [src].")) + slaser = null + update_icon() + return TRUE + + if("rem_cap") + if(!scapacitor || !maintenance) + return FALSE + STOP_PROCESSING(SSobj, src) + scapacitor.forceMove(get_turf(loc)) + to_chat(ui.user, span_notice("You remove the [scapacitor] from \the [src].")) + scapacitor = null + update_icon() + return TRUE + + if("rem_bin") + if(!sbin || !maintenance) + return FALSE + STOP_PROCESSING(SSobj, src) + sbin.forceMove(get_turf(loc)) + to_chat(ui.user, span_notice("You remove the [sbin] from \the [src].")) + sbin = null + sbintier = 0 + update_icon() + return TRUE + +/obj/item/medigun_backpack/ShiftClick(mob/user) + . = ..() + var/obj/item/bork_medigun/medigun = get_medigun() + if(!medigun) + return + tgui_interact(user) + +/obj/item/medigun_backpack/proc/cell_eject() + if(!ccell) + return FALSE + charging = FALSE + ccell.forceMove(get_turf(loc)) + to_chat(usr, span_notice("You remove the [ccell] from \the [src].")) + ccell = null + update_icon() + return TRUE diff --git a/code/game/objects/items/weapons/medigun/medigun_modifiers.dm b/code/game/objects/items/weapons/medigun/medigun_modifiers.dm new file mode 100644 index 0000000000..2cea3e761b --- /dev/null +++ b/code/game/objects/items/weapons/medigun/medigun_modifiers.dm @@ -0,0 +1,8 @@ +/datum/modifier/medbeameffect + name = "medgunffect" + desc = "You're being stabilized" + mob_overlay_state = "medigun_effect" + stacks = MODIFIER_STACK_EXTEND + //pain_immunity = TRUE + bleeding_rate_percent = 0.1 //only a little + incoming_oxy_damage_percent = 0 diff --git a/code/game/objects/items/weapons/medigun/medigun_verbs.dm b/code/game/objects/items/weapons/medigun/medigun_verbs.dm new file mode 100644 index 0000000000..37997f4be6 --- /dev/null +++ b/code/game/objects/items/weapons/medigun/medigun_verbs.dm @@ -0,0 +1,28 @@ +/obj/item/medigun_backpack/verb/toggle_medigun() + set name = "Toggle medigun" + set category = "Object" + + var/mob/living/carbon/human/user = usr + if(maintenance) + to_chat(user, span_warning("Please close the maintenance hatch with a screwdriver first, or to remove components, use a crowbar.")) + return + + if(!medigun) + to_chat(user, span_warning("The medigun is missing!")) + return + + if(medigun.loc != src) + reattach_medigun(user) //Remove from their hands and back onto the medigun unit + return + + if(!slot_check()) + to_chat(user, span_warning("You need to equip [src] before taking out [medigun].")) + else + if(!user.put_in_hands(medigun)) //Detach the medigun into the user's hands + to_chat(user, span_warning("You need a free hand to hold the medigun!")) + else + containsgun = 0 + replace_icon(TRUE) + if(is_twohanded()) + medigun.update_twohanding() + user.update_inv_back() diff --git a/code/game/objects/items/weapons/melee/energy.dm b/code/game/objects/items/weapons/melee/energy.dm index b48c61f8b5..2a6240f242 100644 --- a/code/game/objects/items/weapons/melee/energy.dm +++ b/code/game/objects/items/weapons/melee/energy.dm @@ -406,6 +406,7 @@ active_armourpen = 25 projectile_parry_chance = 40 colorable = TRUE + item_flags = DROPDEL | NOSTRIP hitcost = 75 diff --git a/code/modules/research/tg/designs/weapon_designs.dm b/code/modules/research/tg/designs/weapon_designs.dm index 599d9ec696..2a634b86e5 100644 --- a/code/modules/research/tg/designs/weapon_designs.dm +++ b/code/modules/research/tg/designs/weapon_designs.dm @@ -870,6 +870,17 @@ ) departmental_flags = DEPARTMENT_BITFLAG_MEDICAL | DEPARTMENT_BITFLAG_SCIENCE +/datum/design_techweb/medigun_constant + name = "Prototype Bluespace Medigun Backpack" + id = "medigun_constant" + build_type = PROTOLATHE + materials = list(MAT_STEEL = 8000, MAT_PLASTIC = 8000, MAT_GLASS = 5000, MAT_SILVER = 1000, MAT_GOLD = 1000, MAT_URANIUM = 1000) + build_path = /obj/item/medigun_backpack + category = list( + RND_CATEGORY_WEAPONS + RND_SUBCATEGORY_WEAPONS_RANGED + ) + departmental_flags = DEPARTMENT_BITFLAG_MEDICAL | DEPARTMENT_BITFLAG_SCIENCE + /datum/design_techweb/upgradeAOE name = "Mining Explosion Upgrade" desc = "An area of effect upgrade for the Proto-Kinetic Accelerator." diff --git a/code/modules/research/tg/experisci/experiment/instances/people.dm b/code/modules/research/tg/experisci/experiment/instances/people.dm index a0a45dc741..93bdd5b2cc 100644 --- a/code/modules/research/tg/experisci/experiment/instances/people.dm +++ b/code/modules/research/tg/experisci/experiment/instances/people.dm @@ -9,3 +9,16 @@ if(check.size_multiplier < 1.25 && check.size_multiplier > 0.75) return FALSE return TRUE + +/datum/experiment/scanning/people/hurt_medigun + required_count = 3 + required_traits_desc = "Our newly and improvised Medi-Gun needs field testing! Surely, there has to be someone who's gotten a few bruises or scratches here or there." + +/datum/experiment/scanning/people/hurt_medigun/is_valid_scan_target(mob/living/carbon/human/check, datum/component/experiment_handler/experiment_handler) + . = ..() + if(!.) + return + var/HP_percent = check.health/check.getMaxHealth() + if(HP_percent < 1) + return TRUE + return FALSE diff --git a/code/modules/research/tg/techwebs/nodes/medbay_nodes.dm b/code/modules/research/tg/techwebs/nodes/medbay_nodes.dm index 2dd10a6df8..3a3a9444b6 100644 --- a/code/modules/research/tg/techwebs/nodes/medbay_nodes.dm +++ b/code/modules/research/tg/techwebs/nodes/medbay_nodes.dm @@ -173,6 +173,18 @@ research_costs = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_5_POINTS) announce_channels = list(CHANNEL_MEDICAL) +/datum/techweb_node/medigun_constant + id = TECHWEB_NODE_MEDIGUN_CONSTANT + display_name = "Medigun Backpack" + description = "A revised version of the ML3M series. This one features a cell-powered constant beam, and ability to charge it with chemicals." + prereq_ids = list(TECHWEB_NODE_MEDIGUN) + design_ids = list( + "medigun_constant" + ) + research_costs = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_5_POINTS) + announce_channels = list(CHANNEL_MEDICAL) + discount_experiments = list(/datum/experiment/scanning/people/hurt_medigun = TECHWEB_TIER_3_POINTS) + /datum/techweb_node/nif id = TECHWEB_NODE_NIF display_name = "Nanite-Implant Frameworks" diff --git a/icons/mob/items/lefthand_medigun.dmi b/icons/mob/items/lefthand_medigun.dmi new file mode 100644 index 0000000000000000000000000000000000000000..3c7f99c98133c78e68ed9a17b3cd4321cbfd7442 GIT binary patch literal 1564 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 literal 0 HcmV?d00001 diff --git a/icons/obj/borkmedigun.dmi b/icons/obj/borkmedigun.dmi new file mode 100644 index 0000000000000000000000000000000000000000..e4ab2827a23ab9734188a7b5864534b6a0c2c30f GIT binary patch literal 9706 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 AX8-^I literal 0 HcmV?d00001 diff --git a/tgui/packages/tgui/interfaces/Medigun/MedigunHelpers/ChargeStatus.tsx b/tgui/packages/tgui/interfaces/Medigun/MedigunHelpers/ChargeStatus.tsx new file mode 100644 index 0000000000..84915a1d20 --- /dev/null +++ b/tgui/packages/tgui/interfaces/Medigun/MedigunHelpers/ChargeStatus.tsx @@ -0,0 +1,39 @@ +import { Box, LabeledList, ProgressBar, Stack } from 'tgui-core/components'; + +export const ChargeStatus = (props: { + name: string; + color: string; + charge: number | null; + max: number | null; + volume: number | null; +}) => { + const { name, color, charge, max, volume } = props; + + return ( + + + + {charge !== null ? ( + + {charge.toFixed()} / {max} + + ) : ( + Missing Capacitor + )} + + + Reserve: + + + {volume !== null ? ( + + {volume.toFixed()} + + ) : ( + Missing Bin + )} + + + + ); +}; diff --git a/tgui/packages/tgui/interfaces/Medigun/MedigunTabs/MedigunContent.tsx b/tgui/packages/tgui/interfaces/Medigun/MedigunTabs/MedigunContent.tsx new file mode 100644 index 0000000000..63faad850a --- /dev/null +++ b/tgui/packages/tgui/interfaces/Medigun/MedigunTabs/MedigunContent.tsx @@ -0,0 +1,270 @@ +import { useBackend } from 'tgui/backend'; +import { + Box, + Button, + LabeledList, + ProgressBar, + Section, + Stack, +} from 'tgui-core/components'; +import { + gridStatusToColor, + gridStatusToText, + powerToColor, + powerToText, + statToColor, + statToString, +} from '../constants'; +import { ChargeStatus } from '../MedigunHelpers/ChargeStatus'; +import type { Data, SModule } from '../types'; + +export const MedigunContent = (props: { smodule: SModule }) => { + const { act, data } = useBackend(); + + const { + maintenance, + tankmax, + generator, + gridstatus, + power_cell_status, + cell_status, + bruteheal_charge, + burnheal_charge, + toxheal_charge, + bruteheal_vol, + burnheal_vol, + toxheal_vol, + patient_name, + patient_health, + patient_brute, + patient_burn, + patient_tox, + patient_oxy, + blood_status, + patient_status, + organ_damage, + inner_bleeding, + } = data; + + const { smodule } = props; + + const moduleLevel = smodule?.rating || 0; + + return ( + + +
+ + + {maintenance ? ( + Maintenance Hatch Open + ) : power_cell_status !== null ? ( + + ) : ( + Missing Cell + )} + + {gridstatus !== 3 && ( + + {gridStatusToText[gridstatus] || 'Unavailable'} + + )} + act('celleject')} + /> + } + > + [ {powerToText[generator] || 'Unavailable'} ] + + {cell_status !== null ? ( + + + + ) : ( + Missing Cell + )} + +
+
+ +
+ + + + + +
+
+ +
+ + {patient_health !== null && + patient_brute !== null && + patient_burn !== null && + patient_tox !== null && + patient_oxy !== null ? ( + <> + + + + {patient_name ? ( + + {patient_name} + + + + + ) : ( + 'No Target' + )} + + {!!data.patient_name && ( + <> + + + + {moduleLevel >= 2 && ( + <> + + {!!organ_damage && ( + + Organ Damage! + + )} + {!!patient_status && ( + + + {statToString[patient_status]} + + + )} + + )} + + {`${(patient_health * 100).toFixed()}%`} + + + + + {moduleLevel >= 2 && ( + + {blood_status ? ( + + + + {!!inner_bleeding && ( + + Inner Bleeding! + + )} + + {`${( + (blood_status.volume / + blood_status.max_volume) * + 100 + ).toFixed()}%`} + + + + ) : ( + No Blood Detected + )} + + )} + + )} + + + + {!!data.patient_name && moduleLevel >= 2 && ( + + + + + {patient_brute} + + + {patient_burn} + + + + + + + {patient_tox} + + + {patient_oxy} + + + + + )} + + + ) : ( + Missing Scanning Module + )} + +
+
+
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/Medigun/MedigunTabs/MedigunParts.tsx b/tgui/packages/tgui/interfaces/Medigun/MedigunTabs/MedigunParts.tsx new file mode 100644 index 0000000000..b9d28144f1 --- /dev/null +++ b/tgui/packages/tgui/interfaces/Medigun/MedigunTabs/MedigunParts.tsx @@ -0,0 +1,149 @@ +import { useBackend } from 'tgui/backend'; +import { Box, Button, LabeledList, Section, Stack } from 'tgui-core/components'; +import type { BooleanLike } from 'tgui-core/react'; +import type { ExamineData } from '../types'; + +export const MedigunParts = (props: { + examineData: ExamineData; + maintenance: BooleanLike; +}) => { + const { act } = useBackend(); + const { examineData, maintenance } = props; + const { smodule, smanipulator, slaser, scapacitor, sbin } = examineData; + + return ( +
act('toggle_maintenance')} + selected={maintenance} + tooltip="Toggle maintenance mode" + icon="wrench" + /> + } + > + + + + + {smodule ? ( + maintenance ? ( + act('rem_smodule')}> + Remove Module + + ) : ( + + {'It has a ' + + smodule.name + + ' installed, device will function within ' + + smodule.range + + ' tiles'} + {smodule.rating >= 5 ? ' and' : undefined} + {smodule.rating >= 5 ? ' through walls' : undefined} + {'.'} + + ) + ) : ( + Missing + )} + + + {smanipulator ? ( + maintenance ? ( + act('rem_mani')}> + Remove Manipulator + + ) : ( + + {'It has a ' + + smanipulator.name + + ' installed, chem digitizing is '} + {smanipulator.rating >= 5 + ? '125% Efficient' + : `${(smanipulator.rating / 4) * 100}% Efficient`} + {'.'} + + ) + ) : ( + Missing + )} + + + {slaser ? ( + maintenance ? ( + act('rem_laser')}> + Remove Laser + + ) : ( + + {'It has a ' + + slaser.name + + ' installed, and can heal ' + + slaser.rating + + ' damage per cycle'} + {slaser.rating >= 5 ? ' and will' : undefined} + {slaser.rating >= 5 ? ' regenerate blood' : undefined} + {slaser.rating >= 5 ? ' while beam is focused' : ''} + {'.'} + + ) + ) : ( + Missing + )} + + + {scapacitor ? ( + maintenance ? ( + act('rem_cap')}> + Remove Capacitor + + ) : ( + + {'It has a ' + + scapacitor.name + + ' installed, battery Capacity is ' + + scapacitor.chargecap + + ' Units'} + {scapacitor.rating >= 5 + ? ' the cell will recharge from the local power grid' + : ''} + {''} + {'.'} + + ) + ) : ( + Missing + )} + + + {sbin ? ( + maintenance ? ( + act('rem_bin')}> + Remove Bin + + ) : ( + + {'It has a ' + + sbin.name + + ' installed, can hold ' + + sbin.tankmax + + ' charge and ' + + sbin.chemcap + + ' reserve chems'} + {sbin.rating >= 5 + ? 'and will slowly generate chems in exchange for power.' + : '.'} + + ) + ) : ( + Missing + )} + + + + +
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/Medigun/constants.ts b/tgui/packages/tgui/interfaces/Medigun/constants.ts new file mode 100644 index 0000000000..b486ece4be --- /dev/null +++ b/tgui/packages/tgui/interfaces/Medigun/constants.ts @@ -0,0 +1,11 @@ +export const powerToColor = ['bad', 'good']; + +export const powerToText = ['Cell Offline', 'Cell Charging']; + +export const gridStatusToColor = ['bad', 'average', 'good']; + +export const gridStatusToText = ['Off Grid', 'Grid Available', 'Using Grid']; + +export const statToString = ['Alive', 'Unconscious', 'Dead']; + +export const statToColor = ['orange', 'red']; diff --git a/tgui/packages/tgui/interfaces/Medigun/index.tsx b/tgui/packages/tgui/interfaces/Medigun/index.tsx new file mode 100644 index 0000000000..cfe749ebac --- /dev/null +++ b/tgui/packages/tgui/interfaces/Medigun/index.tsx @@ -0,0 +1,45 @@ +import { useState } from 'react'; +import { useBackend } from 'tgui/backend'; +import { Window } from 'tgui/layouts'; +import { Stack, Tabs } from 'tgui-core/components'; +import { MedigunContent } from './MedigunTabs/MedigunContent'; +import { MedigunParts } from './MedigunTabs/MedigunParts'; +import type { Data } from './types'; + +export const Medigun = (props) => { + const { data } = useBackend(); + const { maintenance, examine_data } = data; + const [selectedTab, setSelectedTab] = useState(0); + + const tab: React.JSX.Element[] = []; + tab[0] = ; + tab[1] = ( + + ); + + return ( + + + + + + setSelectedTab(0)} + > + Medigun Content + + setSelectedTab(1)} + > + Medigun Parts + + + + {tab[selectedTab]} + + + + ); +}; diff --git a/tgui/packages/tgui/interfaces/Medigun/types.ts b/tgui/packages/tgui/interfaces/Medigun/types.ts new file mode 100644 index 0000000000..95a6046d82 --- /dev/null +++ b/tgui/packages/tgui/interfaces/Medigun/types.ts @@ -0,0 +1,46 @@ +import type { BooleanLike } from 'tgui-core/react'; + +export type Data = { + maintenance: BooleanLike; + tankmax: number; + generator: number; + gridstatus: number; + power_cell_status: number | null; + cell_status: number | null; + bruteheal_charge: number | null; + burnheal_charge: number | null; + toxheal_charge: number | null; + bruteheal_vol: number | null; + burnheal_vol: number | null; + toxheal_vol: number | null; + patient_name: string | null; + patient_health: number | null; + patient_brute: number | null; + patient_burn: number | null; + patient_tox: number | null; + patient_oxy: number | null; + blood_status: { volume: number; max_volume: number } | null; + patient_status: number | null; + organ_damage: BooleanLike; + inner_bleeding: BooleanLike; + examine_data: ExamineData; +}; + +export type SModule = { name: string; range: number; rating: number } | null; + +export type ExamineData = { + smodule: SModule; + smanipulator: { name: string; rating: number } | null; + slaser: { name: string; rating: number } | null; + scapacitor: { + name: string; + chargecap: number; + rating: number; + } | null; + sbin: { + name: string; + chemcap: number; + tankmax: number; + rating: number; + } | null; +}; diff --git a/tgui/packages/tgui/interfaces/TraitTutorial.tsx b/tgui/packages/tgui/interfaces/TraitTutorial.tsx index 8569b4a17d..10787fb01d 100644 --- a/tgui/packages/tgui/interfaces/TraitTutorial.tsx +++ b/tgui/packages/tgui/interfaces/TraitTutorial.tsx @@ -1,4 +1,3 @@ -/* eslint-disable react/no-danger */ import { useBackend } from 'tgui/backend'; import { Window } from 'tgui/layouts'; import { Box, Section, Stack, Tabs } from 'tgui-core/components'; diff --git a/vorestation.dme b/vorestation.dme index a2801a2507..a67c6deff1 100644 --- a/vorestation.dme +++ b/vorestation.dme @@ -128,6 +128,7 @@ #include "code\__defines\matrices.dm" #include "code\__defines\MC.dm" #include "code\__defines\mecha.dm" +#include "code\__defines\medigun.dm" #include "code\__defines\misc.dm" #include "code\__defines\misc_ch.dm" #include "code\__defines\mobs.dm" @@ -1948,6 +1949,11 @@ #include "code\game\objects\items\weapons\material\twohanded_ch.dm" #include "code\game\objects\items\weapons\material\twohanded_vr.dm" #include "code\game\objects\items\weapons\material\whetstone.dm" +#include "code\game\objects\items\weapons\medigun\bork_medigun.dm" +#include "code\game\objects\items\weapons\medigun\linked_medigun.dm" +#include "code\game\objects\items\weapons\medigun\medigun_backpack.dm" +#include "code\game\objects\items\weapons\medigun\medigun_backpack_ui.dm" +#include "code\game\objects\items\weapons\medigun\medigun_modifiers.dm" #include "code\game\objects\items\weapons\melee\deflect.dm" #include "code\game\objects\items\weapons\melee\energy.dm" #include "code\game\objects\items\weapons\melee\energy_vr.dm"