From c0b75dbd91502feded909214c1d39fed4fe23ca4 Mon Sep 17 00:00:00 2001 From: zimon9 <122945887+zimon9@users.noreply.github.com> Date: Tue, 3 Dec 2024 08:06:14 -0500 Subject: [PATCH] Easy Energy Cells -- Integrated Retainment Latches (#3853) ## About The Pull Request All energy weapons now come with a retainment clip for their energy cells, if their cells are designed to be removable. After a cell is unlatched, a user will be able to remove the cell like as they would a magazine from a ballistic weapon. However, one must remember that these retainment latches were designed into these weapons for a reason. Firing one with a latch hanging loose will likely cause the cell to fall out and/or disconnect due to jostling. In order to latch or unlatch a retainment clip, one must alt-click on their weapon. If one is using a dual-mode weapon, such as an E-40, they will need to switch to laser mode in order to latch and unlatch the retainment clip, and remove the cell. If one has attachments on their weapon, you'll need to remove the cell on their weapon before attempting to remove an attachment. For the E-40, one simply needs to swap to the ballistic mode in order to do that. A line indicating the latch status was added to the examine text of energy weapons. ![image](https://github.com/user-attachments/assets/5bd8d99b-c066-4664-a6e0-6584fe11667f) This PR also slightly modifies the examine text of the E-40 to include cell charge characteristics. ![image](https://github.com/user-attachments/assets/5bfadcd4-1ca7-4e10-9e56-c37f382e549e) Small sample of it in operation: https://github.com/user-attachments/assets/5ac44c60-fed4-4448-ac15-4f65cf3202b9 Full demo of it in action: https://youtu.be/Be1c_0AFAds A clip of the state of the latch sprites at this time: https://github.com/user-attachments/assets/66d036c1-27df-486f-9b34-c82c22adbd06 ## Why It's Good For The Game Having cell removal be tied to alt-click felt smoother to me, compared to the current way that cells are handled, with removal requiring a screwdriver. The time it takes to remove and replace a cell is comparable as before the change, but it no longer requires a screwdriver to do. ## Changelog :cl: balance: replaced screwdriver cell removal with a cell-retainment clip mechanism /:cl: --- .../projectiles/guns/ballistic/assault.dm | 65 +++++++++++++- code/modules/projectiles/guns/energy.dm | 80 ++++++++++++++---- icons/obj/guns/cell_latch.dmi | Bin 0 -> 356 bytes 3 files changed, 127 insertions(+), 18 deletions(-) create mode 100644 icons/obj/guns/cell_latch.dmi diff --git a/code/modules/projectiles/guns/ballistic/assault.dm b/code/modules/projectiles/guns/ballistic/assault.dm index f6da18d86254..2c59adaaa2d0 100644 --- a/code/modules/projectiles/guns/ballistic/assault.dm +++ b/code/modules/projectiles/guns/ballistic/assault.dm @@ -180,6 +180,9 @@ /obj/item/gun/ballistic/automatic/assault/e40/process_fire(atom/target, mob/living/user, message, params, zone_override, bonus_spread) var/current_firemode = gun_firemodes[firemode_index] if(current_firemode != FIREMODE_OTHER) + if(!secondary.latch_closed && prob(65)) + to_chat(user, span_warning("[src]'s cell falls out!")) + secondary.eject_cell() return ..() return secondary.process_fire(target, user, message, params, zone_override, bonus_spread) @@ -198,10 +201,42 @@ /obj/item/gun/ballistic/automatic/assault/e40/attackby(obj/item/attack_obj, mob/user, params) if(istype(attack_obj, /obj/item/stock_parts/cell/gun)) return secondary.attackby(attack_obj, user, params) - if(istype(attack_obj, /obj/item/screwdriver)) - return secondary.screwdriver_act(user, attack_obj,) return ..() +/obj/item/gun/ballistic/automatic/assault/e40/attack_hand(mob/user) + var/current_firemode = gun_firemodes[firemode_index] + if(current_firemode == FIREMODE_OTHER && loc == user && user.is_holding(src) && secondary.cell && !secondary.latch_closed) + secondary.eject_cell(user) + return + if(current_firemode == FIREMODE_OTHER && loc == user && user.is_holding(src) && secondary.cell && secondary.latch_closed) + to_chat(user, span_warning("The cell retainment clip is latched!")) + return + return ..() + +/obj/item/gun/ballistic/automatic/assault/e40/AltClick(mob/living/user) + var/current_firemode = gun_firemodes[firemode_index] + if(current_firemode == FIREMODE_OTHER) + if(secondary.latch_closed) + to_chat(user, span_notice("You start to unlatch the [src]'s power cell retainment clip...")) + if(do_after(user, secondary.latch_toggle_delay, src, IGNORE_USER_LOC_CHANGE)) + to_chat(user, span_notice("You unlatch [src]'s power cell retainment clip " + "OPEN" + ".")) + playsound(src, 'sound/items/taperecorder/taperecorder_play.ogg', 50, FALSE) + secondary.tac_reloads = TRUE + secondary.latch_closed = FALSE + update_appearance() + return + else + to_chat(user, span_warning("You start to latch the [src]'s power cell retainment clip...")) + if (do_after(user, secondary.latch_toggle_delay, src, IGNORE_USER_LOC_CHANGE)) + to_chat(user, span_notice("You latch [src]'s power cell retainment clip " + "CLOSED" + ".")) + playsound(src, 'sound/items/taperecorder/taperecorder_close.ogg', 50, FALSE) + secondary.tac_reloads = FALSE + secondary.latch_closed = TRUE + update_appearance() + return + else + return ..() + /obj/item/gun/ballistic/automatic/assault/e40/on_wield(obj/item/source, mob/user) wielded = TRUE secondary.wielded = TRUE @@ -241,6 +276,20 @@ . += "[icon_state]_charge[ratio]" if(secondary.cell) . += "[icon_state]_cell" + if(ismob(loc)) + var/mutable_appearance/latch_overlay + latch_overlay = mutable_appearance('icons/obj/guns/cell_latch.dmi') + if(secondary.latch_closed) + if(secondary.cell) + latch_overlay.icon_state = "latch-on-full" + else + latch_overlay.icon_state = "latch-on-empty" + else + if(secondary.cell) + latch_overlay.icon_state = "latch-off-full" + else + latch_overlay.icon_state = "latch-off-empty" + . += latch_overlay /obj/item/gun/ballistic/automatic/assault/e40/toggle_safety(mob/user, silent=FALSE) @@ -257,6 +306,17 @@ SEND_SIGNAL(src, COMSIG_GUN_SET_AUTOFIRE_SPEED, fire_delay) SEND_SIGNAL(src, COMSIG_UPDATE_AMMO_HUD) +/obj/item/gun/ballistic/automatic/assault/e40/examine(mob/user) + . = ..() + if(!secondary.internal_magazine) + . += "The cell retainment latch is [secondary.latch_closed ? "CLOSED" : "OPEN"]. Alt-Click to toggle the latch." + var/obj/item/ammo_casing/energy/shot = secondary.ammo_type[select] + if(secondary.cell) + . += "\The [name]'s cell has [secondary.cell.percent()]% charge remaining." + . += "\The [name] has [round(secondary.cell.charge/shot.e_cost)] shots remaining on [shot.select_name] mode." + else + . += span_notice("\The [name] doesn't seem to have a cell!") + //laser /obj/item/gun/energy/laser/e40_laser_secondary @@ -268,5 +328,6 @@ fire_delay = 0.2 SECONDS gun_firemodes = list(FIREMODE_FULLAUTO) default_firemode = FIREMODE_FULLAUTO + latch_toggle_delay = 1.2 SECONDS spread_unwielded = 20 diff --git a/code/modules/projectiles/guns/energy.dm b/code/modules/projectiles/guns/energy.dm index 1f595e994902..41147c0e0452 100644 --- a/code/modules/projectiles/guns/energy.dm +++ b/code/modules/projectiles/guns/energy.dm @@ -30,6 +30,9 @@ tac_reloads = FALSE tactical_reload_delay = 1.2 SECONDS + var/latch_closed = TRUE + var/latch_toggle_delay = 1.0 SECONDS + valid_attachments = list( /obj/item/attachment/laser_sight, /obj/item/attachment/rail_light, @@ -128,7 +131,7 @@ if (!internal_magazine && (A.type in (allowed_ammo_types - blacklisted_ammo_types))) var/obj/item/stock_parts/cell/gun/C = A if (!cell) - insert_cell(user, C) + return insert_cell(user, C) else if (tac_reloads) eject_cell(user, C) @@ -136,14 +139,18 @@ return ..() /obj/item/gun/energy/proc/insert_cell(mob/user, obj/item/stock_parts/cell/gun/C) - if(user.transferItemToLoc(C, src)) - cell = C - to_chat(user, span_notice("You load the [C] into \the [src].")) - playsound(src, load_sound, load_sound_volume, load_sound_vary) - update_appearance() - return TRUE + if(!latch_closed) + if(user.transferItemToLoc(C, src)) + cell = C + to_chat(user, span_notice("You load the [C] into \the [src].")) + playsound(src, load_sound, load_sound_volume, load_sound_vary) + update_appearance() + return TRUE + else + to_chat(user, span_warning("You cannot seem to get \the [src] out of your hands!")) + return FALSE else - to_chat(user, span_warning("You cannot seem to get \the [src] out of your hands!")) + to_chat(user, span_warning("The [src]'s cell retainment clip is latched!")) return FALSE /obj/item/gun/energy/proc/eject_cell(mob/user, obj/item/stock_parts/cell/gun/tac_load = null) @@ -167,13 +174,33 @@ user.put_in_hands(old_cell) update_appearance() -/obj/item/gun/energy/screwdriver_act(mob/living/user, obj/item/I) - if(cell && !internal_magazine) - to_chat(user, span_notice("You begin unscrewing and pulling out the cell...")) - if(I.use_tool(src, user, unscrewing_time, volume = 100)) - to_chat(user, span_notice("You remove the power cell.")) - eject_cell(user) - return ..() +//special is_type_in_list method to counteract problem with current method +/obj/item/gun/energy/proc/is_attachment_in_contents_list() + for(var/content_item in contents) + if(istype(content_item, /obj/item/attachment/)) + return TRUE + return FALSE + +/obj/item/gun/energy/AltClick(mob/living/user) + if(!internal_magazine && latch_closed) + to_chat(user, span_notice("You start to unlatch the [src]'s power cell retainment clip...")) + if(do_after(user, latch_toggle_delay, src, IGNORE_USER_LOC_CHANGE)) + to_chat(user, span_notice("You unlatch the [src]'s power cell retainment clip " + "OPEN" + ".")) + playsound(src, 'sound/items/taperecorder/taperecorder_play.ogg', 50, FALSE) + tac_reloads = TRUE + latch_closed = FALSE + update_appearance() + else if(!internal_magazine && !latch_closed) + if(!cell && is_attachment_in_contents_list()) + return ..() //should bring up the attachment menu if attachments are added. If none are added, it just does leaves the latch open + to_chat(user, span_warning("You start to latch the [src]'s power cell retainment clip...")) + if (do_after(user, latch_toggle_delay, src, IGNORE_USER_LOC_CHANGE)) + to_chat(user, span_notice("You latch the [src]'s power cell retainment clip " + "CLOSED" + ".")) + playsound(src, 'sound/items/taperecorder/taperecorder_close.ogg', 50, FALSE) + tac_reloads = FALSE + latch_closed = TRUE + update_appearance() + return /obj/item/gun/energy/can_shoot(visuals) if(safety && !visuals) @@ -213,7 +240,12 @@ /obj/item/gun/energy/process_fire(atom/target, mob/living/user, message = TRUE, params = null, zone_override = "", bonus_spread = 0) if(!chambered && can_shoot()) process_chamber() // If the gun was drained and then recharged, load a new shot. - return ..() + ..() //process the gunshot as normal + if(!latch_closed && prob(65)) //make the cell slide out if it's fired while the retainment clip is unlatched, with a 65% probability + to_chat(user, span_warning("The [src]'s cell falls out!")) + eject_cell() + return + /obj/item/gun/energy/proc/select_fire(mob/living/user) select++ @@ -252,6 +284,20 @@ var/overlay_icon_state = "[icon_state]_charge" var/obj/item/ammo_casing/energy/shot = ammo_type[modifystate ? select : 1] var/ratio = get_charge_ratio() + if(ismob(loc) && !internal_magazine) + var/mutable_appearance/latch_overlay + latch_overlay = mutable_appearance('icons/obj/guns/cell_latch.dmi') + if(latch_closed) + if(cell) + latch_overlay.icon_state = "latch-on-full" + else + latch_overlay.icon_state = "latch-on-empty" + else + if(cell) + latch_overlay.icon_state = "latch-off-full" + else + latch_overlay.icon_state = "latch-off-empty" + . += latch_overlay if(cell) . += "[icon_state]_cell" if(ratio == 0) @@ -322,6 +368,8 @@ /obj/item/gun/energy/examine(mob/user) . = ..() + if(!internal_magazine) + . += "The cell retainment latch is [latch_closed ? "CLOSED" : "OPEN"]. Alt-Click to toggle the latch." var/obj/item/ammo_casing/energy/shot = ammo_type[select] if(ammo_type.len > 1) . += "You can switch firemodes by pressing the unique action key. By default, this is space" diff --git a/icons/obj/guns/cell_latch.dmi b/icons/obj/guns/cell_latch.dmi new file mode 100644 index 0000000000000000000000000000000000000000..6372df688776df8ced09d3f993956194d5df20a5 GIT binary patch literal 356 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0J3?w7mbKU|ei2$Dv*8>L*#Ld)hcpSuVhC!?# z@xO}783yAfF0oH>Tatll7)yfuf*Bm1-ADs+nyW%0N?cNllZ!G7N;32F7#J$%1cwzA zm45#cT=4Ph6D@CDt#fD22X6>9xM=*~kM?sS;KhCFQw{dFo@+ zxX6Q7&$GUXWcZ@tURZVQZ41yj*`6+rAs(G?PZ@GG81S?{tY~&-?dIVUvpITxPbhmR>nG>bn?KI& ztj}@zA{7&6kP#rf|8tqFkLU^h$)%62pW78P^OuP!ZrT{X9cV3sr>mdKI;Vst02S?r A-~a#s literal 0 HcmV?d00001