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