Skip to content

Commit

Permalink
Componentizes power cells (cmss13-devs#4855)
Browse files Browse the repository at this point in the history
# About the pull request
Creates a new, flexible component for power cells. Eventually, anything
powered will use a cell component instead of holding a ref to a
`/obj/item/cell` or having a `charge` variable.

Converts NVGs over to the new system as an example, the rest will come
in time.

Minor rework of how `emp_act()` works

# Explain why it's good for the game
Cells are one of the most obvious things to be componentized, and
componentizing them allows cells to be added to new objects in a very
easy manner.

# Testing Photographs and Procedure
<details>
<summary>Screenshots & Videos</summary>

Works

</details>
  • Loading branch information
Zonespace27 authored Nov 9, 2023
1 parent ace2443 commit 41b2e51
Show file tree
Hide file tree
Showing 68 changed files with 348 additions and 138 deletions.
3 changes: 3 additions & 0 deletions code/__DEFINES/dcs/signals/atom/signals_atom.dm
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,6 @@

/// Called when an atom is mouse dropped on another atom, from /client/MouseDrop: (atom/dropped_onto)
#define COMSIG_ATOM_DROP_ON "atom_drop_on"

/// Called when an atom has emp_act called on it, from /atom/emp_act: (severity)
#define COMSIG_ATOM_EMP_ACT "atom_emp_act"
26 changes: 26 additions & 0 deletions code/__DEFINES/dcs/signals/atom/signals_cell.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/// (charge_amount)
#define COMSIG_CELL_USE_CHARGE "cell_use_charge"
#define COMPONENT_CELL_NO_USE_CHARGE (1<<0)

/// (charge_amount)
#define COMSIG_CELL_ADD_CHARGE "cell_add_charge"

#define COMSIG_CELL_START_TICK_DRAIN "cell_start_tick_drain"

#define COMSIG_CELL_STOP_TICK_DRAIN "cell_stop_tick_drain"

/// (mob/living/user)
#define COMSIG_CELL_TRY_RECHARGING "cell_try_recharging"
#define COMPONENT_CELL_NO_RECHARGE (1<<0)

#define COMSIG_CELL_OUT_OF_CHARGE "cell_out_of_charge"

/// (charge_amount)
#define COMSIG_CELL_CHECK_CHARGE "cell_check_charge"
#define COMPONENT_CELL_CHARGE_INSUFFICIENT (1<<0)

#define COMSIG_CELL_TRY_INSERT_CELL "cell_try_insert_cell"
#define COMPONENT_CANCEL_CELL_INSERT (1<<0)

/// (mob/living/user)
#define COMSIG_CELL_REMOVE_CELL "cell_remove_cell"
202 changes: 202 additions & 0 deletions code/datums/components/cell.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
#define UNLIMITED_CHARGE -1
#define UNLIMITED_DISTANCE -1

/datum/component/cell
dupe_mode = COMPONENT_DUPE_UNIQUE
/// Maximum charge of the power cell, set to -1 for infinite charge
var/max_charge = 10000
/// Initial max charge of the power cell
var/initial_max_charge
/// Current charge of power cell
var/charge = 10000
/// If the component can be recharged by hitting its parent with a cell
var/hit_charge = FALSE
/// The maximum amount that can be recharged per tick when using a cell to recharge this component
var/max_recharge_tick = 400
/// If draining charge on process(), how much to drain per process call
var/charge_drain = 10
/// If the parent should show cell charge on examine
var/display_charge = TRUE
/// From how many tiles at the highest someone can examine the parent to see the charge
var/charge_examine_range = 1
/// If the component requires a cell to be inserted to work instead of having an integrated one
var/cell_insert = FALSE
/// Ref to an inserted cell. Should only be null if cell_insert is false
var/obj/item/cell/inserted_cell


/datum/component/cell/Initialize(
max_charge = 10000,
hit_charge = FALSE,
max_recharge_tick = 400,
charge_drain = 10,
display_charge = TRUE,
charge_examine_range = 1,
cell_insert = FALSE,
)

. = ..()
if(!isatom(parent))
return COMPONENT_INCOMPATIBLE

src.max_charge = max_charge
charge = max_charge
src.hit_charge = hit_charge
src.max_recharge_tick = max_recharge_tick
src.charge_drain = charge_drain
src.display_charge = display_charge
src.charge_examine_range = charge_examine_range
src.cell_insert = cell_insert

/datum/component/cell/Destroy(force, silent)
QDEL_NULL(inserted_cell)
return ..()


/datum/component/cell/RegisterWithParent()
..()
RegisterSignal(parent, list(COMSIG_PARENT_ATTACKBY, COMSIG_ITEM_ATTACKED), PROC_REF(on_object_hit))
RegisterSignal(parent, COMSIG_CELL_ADD_CHARGE, PROC_REF(add_charge))
RegisterSignal(parent, COMSIG_CELL_USE_CHARGE, PROC_REF(use_charge))
RegisterSignal(parent, COMSIG_CELL_CHECK_CHARGE, PROC_REF(has_charge))
RegisterSignal(parent, COMSIG_CELL_START_TICK_DRAIN, PROC_REF(start_drain))
RegisterSignal(parent, COMSIG_CELL_STOP_TICK_DRAIN, PROC_REF(stop_drain))
RegisterSignal(parent, COMSIG_CELL_REMOVE_CELL, PROC_REF(remove_cell))
RegisterSignal(parent, COMSIG_PARENT_EXAMINE, PROC_REF(on_examine))
RegisterSignal(parent, COMSIG_ATOM_EMP_ACT, PROC_REF(on_emp))

/datum/component/cell/process()
use_charge(null, charge_drain)

/datum/component/cell/proc/on_emp(datum/source, severity)
SIGNAL_HANDLER

use_charge(null, round(max_charge / severity))

/datum/component/cell/proc/start_drain(datum/source)
SIGNAL_HANDLER

START_PROCESSING(SSobj, src)

/datum/component/cell/proc/stop_drain(datum/source)
SIGNAL_HANDLER

STOP_PROCESSING(SSobj, src)

/datum/component/cell/proc/on_examine(datum/source, mob/examiner, list/examine_text)
SIGNAL_HANDLER

if(!display_charge)
return

if((charge_examine_range != UNLIMITED_DISTANCE) && get_dist(examiner, parent) > charge_examine_range)
return

examine_text += "A small gauge in the corner reads \"Power: [round(100 * charge / max_charge)]%\"."

/datum/component/cell/proc/on_object_hit(datum/source, obj/item/cell/attack_obj, mob/living/attacker, params)
SIGNAL_HANDLER

if(!hit_charge || !istype(attack_obj))
return

if(!cell_insert)
INVOKE_ASYNC(src, PROC_REF(charge_from_cell), attack_obj, attacker)

else
insert_cell(attack_obj, attacker)

return COMPONENT_NO_AFTERATTACK|COMPONENT_CANCEL_ITEM_ATTACK

/datum/component/cell/proc/insert_cell(obj/item/cell/power_cell, mob/living/user)
if(inserted_cell)
to_chat(user, SPAN_WARNING("There's already a power cell in [parent]!"))
return

if(SEND_SIGNAL(parent, COMSIG_CELL_TRY_INSERT_CELL) & COMPONENT_CANCEL_CELL_INSERT)
return

power_cell.drop_to_floor(user)
power_cell.forceMove(parent)
inserted_cell = power_cell
charge = power_cell.charge
max_charge = power_cell.maxcharge

/datum/component/cell/proc/remove_cell(mob/living/user)
SIGNAL_HANDLER

user.put_in_hands(inserted_cell, TRUE)
to_chat(user, SPAN_NOTICE("You remove [inserted_cell] from [parent]."))
inserted_cell = null
max_charge = initial_max_charge
charge = 0

/datum/component/cell/proc/charge_from_cell(obj/item/cell/power_cell, mob/living/user)
if(max_charge == UNLIMITED_CHARGE)
to_chat(user, SPAN_WARNING("[parent] doesn't need more power."))
return

while(charge < max_charge)
if(SEND_SIGNAL(parent, COMSIG_CELL_TRY_RECHARGING, user) & COMPONENT_CELL_NO_RECHARGE)
return

if(power_cell.charge <= 0)
to_chat(user, SPAN_WARNING("[power_cell] is completely dry."))
return

if(!do_after(user, 1 SECONDS, (INTERRUPT_ALL & (~INTERRUPT_MOVED)), BUSY_ICON_BUILD, power_cell, INTERRUPT_DIFF_LOC))
to_chat(user, SPAN_WARNING("You were interrupted."))
return

if(power_cell.charge <= 0)
return

var/to_transfer = min(max_recharge_tick, power_cell.charge, (max_charge - charge))
if(power_cell.use(to_transfer))
add_charge(null, to_transfer)
to_chat(user, "You transfer some power between [power_cell] and [parent]. The gauge now reads: [round(100 * charge / max_charge)]%.")

/datum/component/cell/proc/add_charge(datum/source, charge_add = 0)
SIGNAL_HANDLER

if(max_charge == UNLIMITED_CHARGE)
return

if(!charge_add)
return

charge = clamp(charge + charge_add, 0, max_charge)

/datum/component/cell/proc/use_charge(datum/source, charge_use = 0)
SIGNAL_HANDLER

if(max_charge == UNLIMITED_CHARGE)
return

if(!charge_use)
return

if(!charge)
return COMPONENT_CELL_NO_USE_CHARGE

charge = clamp(charge - charge_use, 0, max_charge)

if(!charge)
on_charge_empty()
return

/datum/component/cell/proc/has_charge(datum/source, charge_amount = 0)
SIGNAL_HANDLER

if(!charge)
return COMPONENT_CELL_CHARGE_INSUFFICIENT

if(charge < charge_amount)
return COMPONENT_CELL_CHARGE_INSUFFICIENT

/datum/component/cell/proc/on_charge_empty()
stop_drain()
SEND_SIGNAL(parent, COMSIG_CELL_OUT_OF_CHARGE)

#undef UNLIMITED_CHARGE
#undef UNLIMITED_DISTANCE
6 changes: 4 additions & 2 deletions code/game/atoms.dm
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,9 @@ directive is properly returned.
return

/atom/proc/emp_act(severity)
return
SHOULD_CALL_PARENT(TRUE)

SEND_SIGNAL(src, COMSIG_ATOM_EMP_ACT, severity)

/atom/proc/in_contents_of(container)//can take class or object instance as argument
if(ispath(container))
Expand Down Expand Up @@ -245,8 +247,8 @@ directive is properly returned.
if(!examine_strings)
log_debug("Attempted to create an examine block with no strings! Atom : [src], user : [user]")
return
to_chat(user, examine_block(examine_strings.Join("\n")))
SEND_SIGNAL(src, COMSIG_PARENT_EXAMINE, user, examine_strings)
to_chat(user, examine_block(examine_strings.Join("\n")))

/atom/proc/get_examine_text(mob/user)
. = list()
Expand Down
4 changes: 1 addition & 3 deletions code/game/machinery/atmoalter/scrubber.dm
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,14 @@
PF.flags_can_pass_all = PASS_OVER|PASS_AROUND|PASS_UNDER

/obj/structure/machinery/portable_atmospherics/powered/scrubber/emp_act(severity)
. = ..()
if(inoperable())
..(severity)
return

if(prob(50/severity))
on = !on
update_icon()

..(severity)

/obj/structure/machinery/portable_atmospherics/powered/scrubber/update_icon()
src.overlays = 0

Expand Down
1 change: 1 addition & 0 deletions code/game/machinery/bots/bots.dm
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@


/obj/structure/machinery/bot/emp_act(severity)
. = ..()
var/was_on = on
stat |= EMPED
new /obj/effect/overlay/temp/emp_sparks (loc)
Expand Down
2 changes: 1 addition & 1 deletion code/game/machinery/bots/mulebot.dm
Original file line number Diff line number Diff line change
Expand Up @@ -916,11 +916,11 @@
post_signal_multiple(control_freq, kv)

/obj/structure/machinery/bot/mulebot/emp_act(severity)
. = ..()
if (cell)
cell.emp_act(severity)
if(load)
load.emp_act(severity)
..()


/obj/structure/machinery/bot/mulebot/explode()
Expand Down
5 changes: 4 additions & 1 deletion code/game/machinery/camera/camera.dm
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@

var/colony_camera_mapload = TRUE

/// If this camera should have innate EMP-proofing
var/emp_proof = FALSE

/obj/structure/machinery/camera/Initialize(mapload, ...)
. = ..()
WireColorToFlag = randomCameraWires()
Expand Down Expand Up @@ -72,6 +75,7 @@
if(WEST) pixel_x = 27

/obj/structure/machinery/camera/emp_act(severity)
. = ..()
if(!isEmpProof())
if(prob(100/severity))
icon_state = "[initial(icon_state)]emp"
Expand All @@ -89,7 +93,6 @@
if(can_use())
cameranet.addCamera(src)
kick_viewers()
..()


/obj/structure/machinery/camera/ex_act(severity)
Expand Down
9 changes: 3 additions & 6 deletions code/game/machinery/camera/presets.dm
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,14 @@
network = list(CAMERA_NET_LASER_TARGETS)
unslashable = TRUE
unacidable = TRUE
emp_proof = TRUE

/obj/structure/machinery/camera/laser_cam/Initialize(mapload, laser_name)
. = ..()
if(!c_tag && laser_name)
var/area/A = get_area(src)
c_tag = "[laser_name] ([A.name])"

/obj/structure/machinery/camera/laser_cam/emp_act(severity)
return //immune to EMPs, just in case

/obj/structure/machinery/camera/laser_cam/ex_act()
return
Expand Down Expand Up @@ -125,9 +124,7 @@
invisibility = 101 //fuck you init()

colony_camera_mapload = FALSE

/obj/structure/machinery/camera/autoname/lz_camera/emp_act(severity)
return //immune to EMPs, just in case
emp_proof = TRUE

/obj/structure/machinery/camera/autoname/lz_camera/ex_act()
return
Expand All @@ -137,7 +134,7 @@

/obj/structure/machinery/camera/proc/isEmpProof()
var/O = locate(/obj/item/stack/sheet/mineral/osmium) in assembly.upgrades
return O
return O || emp_proof

/obj/structure/machinery/camera/proc/isXRay()
var/obj/item/stock_parts/scanning_module/O = locate(/obj/item/stock_parts/scanning_module) in assembly.upgrades
Expand Down
2 changes: 1 addition & 1 deletion code/game/machinery/cell_charger.dm
Original file line number Diff line number Diff line change
Expand Up @@ -80,11 +80,11 @@
return

/obj/structure/machinery/cell_charger/emp_act(severity)
. = ..()
if(inoperable())
return
if(charging)
charging.emp_act(severity)
..(severity)


/obj/structure/machinery/cell_charger/process()
Expand Down
4 changes: 1 addition & 3 deletions code/game/machinery/computer/arcade.dm
Original file line number Diff line number Diff line change
Expand Up @@ -165,8 +165,8 @@
return

/obj/structure/machinery/computer/arcade/emp_act(severity)
. = ..()
if(inoperable())
..(severity)
return
var/empprize = null
var/num_of_prizes = 0
Expand All @@ -178,5 +178,3 @@
for(num_of_prizes; num_of_prizes > 0; num_of_prizes--)
empprize = pickweight(prizes)
new empprize(src.loc)

..(severity)
4 changes: 2 additions & 2 deletions code/game/machinery/computer/camera_console.dm
Original file line number Diff line number Diff line change
Expand Up @@ -354,8 +354,8 @@
exproof = TRUE
colony_camera_mapload = FALSE

/obj/structure/machinery/computer/cameras/mortar/emp_act(severity)
return FALSE
/obj/structure/machinery/computer/cameras/mortar/set_broken()
return

/obj/structure/machinery/computer/cameras/dropship
name = "abstract dropship camera computer"
Expand Down
Loading

0 comments on commit 41b2e51

Please sign in to comment.