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 0000000000..3c7f99c981
Binary files /dev/null and b/icons/mob/items/lefthand_medigun.dmi differ
diff --git a/icons/mob/items/righthand_medigun.dmi b/icons/mob/items/righthand_medigun.dmi
new file mode 100644
index 0000000000..a0ccf97a6b
Binary files /dev/null and b/icons/mob/items/righthand_medigun.dmi differ
diff --git a/icons/obj/borkmedigun.dmi b/icons/obj/borkmedigun.dmi
new file mode 100644
index 0000000000..e4ab2827a2
Binary files /dev/null and b/icons/obj/borkmedigun.dmi differ
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"