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"