Files
Bubberstation/code/modules/power/power_store.dm
KingkumaArt b23b4c0fb0 Buffs the amount EMPs drain cells to be worthwhile (#92902)
Simply put, EMPs now drain cells at hopefully decent rates. Starting at
basic high capacity, a heavy emp (aka a direct ion rifle hit) drains 1/3
of the battery, with each successive tier of cell taking an extra heavy
emp to drain it. At the end, the data looks like this:

- Basic Cell: Instantly drained. Unchanged.
- High Capacity: 3 hits
- Super Capacity: 4 Hits
- Hyper Capacity: 6 Hits
- Bluespace: 8 hits.

Also potatoes take halfed emp damage, because theres not really much
silicon to emp, given its a potato.
## Why It's Good For The Game

As of now, this is the stats to drain a cell with EMPs. (Do note some
stuff just doesnt drain at all and this is for those that do just run
the proc to the cell itself)

- Basic Cell: Instantly drained.
- High Capacity: 10 hits
- Super Capacity: 20 Hits
- Hyper Capacity: 30 Hits
- Bluespace: 40 hits.

The EMP gun on the station can shoot 10 bolts before needing a recharge.
It is *physically impossible* to use the ion to depower something with
an upgraded cell without needing to reload, and even landing 10 direct
hits with the ion to bring down a stock cell is frankly a big ask.
Additionally, using emps as crew or sec runs the risk of you getting
caught in the splash damage and now your modsuit/pda/whatever doesnt
work. Hopefully, this will make the EMP gun actually useful for its
intended purpose of countering silicons/mechs.

## Changelog
🆑 WebcomicArtist
balance: EMPs now drain cells better, starting at 3 emps to drain a
basic cell, up to 8 for bluespace.
/🆑

---------

Co-authored-by: Aki Ito <11748095+ExcessiveUseOfCobblestone@users.noreply.github.com>
2025-09-14 23:32:32 -04:00

351 lines
12 KiB
Plaintext

#define ETHEREAL_CELL_DRAIN_TIME (3.5 SECONDS)
#define ETHEREAL_CELL_POWER_DRAIN (0.75 * STANDARD_CELL_CHARGE)
/// The factor by which we multiply drain to get how much we gain
#define ETHEREAL_CELL_POWER_GAIN_FACTOR 0.08
/**
* # Power store abstract type
*
* Abstract type for a stock part that holds power.
*/
/obj/item/stock_parts/power_store
name = "power store abstract"
/// The size icon overlay prefix.
var/cell_size_prefix = "cell"
///Current charge in cell units
var/charge = 0
/// Standard cell charge used for rating
var/rating_base = STANDARD_CELL_CHARGE
///Maximum charge in cell units
var/maxcharge = STANDARD_CELL_CHARGE
///If the cell has been booby-trapped by injecting it with plasma. Chance on use() to explode.
var/rigged = FALSE
///If the power cell was damaged by an explosion, chance for it to become corrupted and function the same as rigged.
var/corrupted = FALSE
///How much power is given per second in a recharger.
var/chargerate = STANDARD_CELL_RATE * 0.05
///If true, the cell will state it's maximum charge in it's description
var/ratingdesc = TRUE
///If it's a grown that acts as a battery, add a wire overlay to it.
var/grown_battery = FALSE
///What charge lige sprite to use, null if no light
var/charge_light_type = "standard"
///What connector sprite to use when in a cell charger, null if no connectors
var/connector_type = "standard"
///Does the cell start without any charge?
var/empty = FALSE
// Damage multiplier the cells take from emps to prevent stuff like bluespace cells taking 40 shots to drain.
var/emp_damage_modifier = 1
/obj/item/stock_parts/power_store/get_cell()
return src
/obj/item/stock_parts/power_store/get_save_vars()
. = ..()
. += NAMEOF(src, charge)
. += NAMEOF(src, rigged)
return .
/obj/item/stock_parts/power_store/Initialize(mapload, override_maxcharge)
. = ..()
create_reagents(5, INJECTABLE | DRAINABLE)
if (override_maxcharge)
maxcharge = override_maxcharge
rating = max(round(maxcharge / (rating_base * 10), 1), 1)
if(!charge)
charge = maxcharge
if(empty)
charge = 0
if(ratingdesc)
desc += " This one has a rating of [display_energy(maxcharge)][prob(10) ? ", and you should not swallow it" : ""]." //joke works better if it's not on every cell
update_appearance()
RegisterSignal(src, COMSIG_ITEM_MAGICALLY_CHARGED, PROC_REF(on_magic_charge))
var/static/list/loc_connections = list(
COMSIG_ITEM_MAGICALLY_CHARGED = PROC_REF(on_magic_charge),
)
AddElement(/datum/element/connect_loc, loc_connections)
/obj/item/stock_parts/power_store/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change = TRUE)
. = ..()
if(!isturf(old_loc))
update_appearance()
/**
* Signal proc for [COMSIG_ITEM_MAGICALLY_CHARGED]
*
* If we, or the item we're located in, is subject to the charge spell, gain some charge back
*/
/obj/item/stock_parts/power_store/proc/on_magic_charge(datum/source, datum/action/cooldown/spell/charge/spell, mob/living/caster)
SIGNAL_HANDLER
// This shouldn't be running if we're not being held by a mob,
// or if we're not within an object being held by a mob, but just in case...
if(!ismovable(loc))
return
. = COMPONENT_ITEM_CHARGED
if(prob(80))
maxcharge -= rating_base * 0.2
if(maxcharge <= 1) // Div by 0 protection
maxcharge = 1
. |= COMPONENT_ITEM_BURNT_OUT
charge = maxcharge
update_appearance()
// Guns need to process their chamber when we've been charged
if(isgun(loc))
var/obj/item/gun/gun_loc = loc
gun_loc.process_chamber()
// The thing we're in might have overlays or icon states for whether the cell is charged
if(!ismob(loc))
loc.update_appearance()
return .
/obj/item/stock_parts/power_store/create_reagents(max_vol, flags)
. = ..()
RegisterSignal(reagents, COMSIG_REAGENTS_HOLDER_UPDATED, PROC_REF(on_reagent_change))
/obj/item/stock_parts/power_store/update_overlays()
. = ..()
if(grown_battery)
. += mutable_appearance('icons/obj/machines/cell_charger.dmi', "grown_wires")
if((charge < 0.01) || !charge_light_type)
return
. += mutable_appearance('icons/obj/machines/cell_charger.dmi', "[cell_size_prefix]-[charge_light_type]-o[(percent() >= 99.5) ? 2 : 1]")
/obj/item/stock_parts/power_store/vv_edit_var(vname, vval)
if(vname == NAMEOF(src, charge))
charge = clamp(vval, 0, maxcharge)
return TRUE
if(vname == NAMEOF(src, maxcharge))
if(charge > vval)
charge = vval
if(vname == NAMEOF(src, corrupted) && vval && !corrupted)
corrupt(TRUE)
return TRUE
return ..()
/**
* Returns the percentage of the cell's charge.
*/
/obj/item/stock_parts/power_store/proc/percent() // return % charge of cell
return 100 * charge / maxcharge
/**
* Returns the maximum charge of the cell.
*/
/obj/item/stock_parts/power_store/proc/max_charge()
return maxcharge
/**
* Returns the current charge of the cell.
*/
/obj/item/stock_parts/power_store/proc/charge()
return charge
/**
* Returns the amount of charge used on the cell.
*/
/obj/item/stock_parts/power_store/proc/used_charge()
return maxcharge - charge
/// Use power from the cell.
/// Args:
/// - used: Amount of power in joules to use.
/// - force: If true, uses the remaining power from the cell if there isn't enough power to supply the demand.
/// Returns: The power used from the cell in joules.
/obj/item/stock_parts/power_store/use(used, force = FALSE)
var/power_used = min(used, charge)
if(rigged && power_used > 0)
explode()
return 0 // The cell decided to explode so we won't be able to use it.
if(!force && charge < used)
return 0
charge -= power_used
if(!istype(loc, /obj/machinery/power/apc))
SSblackbox.record_feedback("tally", "cell_used", 1, type)
return power_used
/// Recharge the cell.
/// Args:
/// - amount: The amount of energy to give to the cell in joules.
/// Returns: The power given to the cell in joules.
/obj/item/stock_parts/power_store/proc/give(amount)
var/power_used = min(maxcharge-charge,amount)
charge += power_used
if(rigged && amount > 0)
explode()
return power_used
/**
* Changes the charge of the cell.
* Args:
* - amount: The energy to give to the cell (can be negative).
* Returns: The energy that was given to the cell (can be negative).
*/
/obj/item/stock_parts/power_store/proc/change(amount)
var/energy_used = clamp(amount, -charge, maxcharge - charge)
charge += energy_used
if(rigged && energy_used)
explode()
return energy_used
/obj/item/stock_parts/power_store/examine(mob/user)
. = ..()
if(rigged)
. += span_danger("This [name] seems to be faulty!")
else if(!isnull(charge_light_type))
. += "The charge meter reads [CEILING(percent(), 0.1)]%." //so it doesn't say 0% charge when the overlay indicates it still has charge
/obj/item/stock_parts/power_store/proc/on_reagent_change(datum/reagents/holder)
SIGNAL_HANDLER
rigged = corrupted || !!holder.has_reagent(/datum/reagent/toxin/plasma, 5) //has_reagent returns the reagent datum
/obj/item/stock_parts/power_store/proc/explode()
if(!charge)
return
var/range_devastation = -1
var/range_heavy = round(sqrt(charge / (3.6 * rating_base)))
var/range_light = round(sqrt(charge / (0.9 * rating_base)))
var/range_flash = range_light
if(!range_light)
rigged = FALSE
corrupt()
return
message_admins("[ADMIN_LOOKUPFLW(usr)] has triggered a rigged/corrupted power cell explosion at [AREACOORD(loc)].")
usr?.log_message("triggered a rigged/corrupted power cell explosion", LOG_GAME)
usr?.log_message("triggered a rigged/corrupted power cell explosion", LOG_VICTIM, log_globally = FALSE)
explosion(src, devastation_range = range_devastation, heavy_impact_range = range_heavy, light_impact_range = range_light, flash_range = range_flash)
qdel(src)
/obj/item/stock_parts/power_store/proc/corrupt(force)
charge /= 2
maxcharge = max(maxcharge/2, chargerate)
if (force || prob(10))
rigged = TRUE //broken batterys are dangerous
corrupted = TRUE
/obj/item/stock_parts/power_store/emp_act(severity)
. = ..()
if(. & EMP_PROTECT_SELF)
return
use((STANDARD_CELL_CHARGE /severity) * emp_damage_modifier , force = TRUE)
/obj/item/stock_parts/power_store/ex_act(severity, target)
. = ..()
if(QDELETED(src))
return FALSE
switch(severity)
if(EXPLODE_HEAVY)
if(prob(50))
corrupt()
if(EXPLODE_LIGHT)
if(prob(25))
corrupt()
return TRUE
/obj/item/stock_parts/power_store/suicide_act(mob/living/user)
user.visible_message(span_suicide("[user] is licking the electrodes of [src]! It looks like [user.p_theyre()] trying to commit suicide!"))
do_sparks(2, TRUE, user)
var/eating_success = do_after(user, 5 SECONDS, src)
if(QDELETED(user))
return SHAME
if(!eating_success || QDELETED(src) || charge == 0)
user.visible_message(span_suicide("[user] chickens out!"))
return SHAME
playsound(user, 'sound/effects/sparks/sparks1.ogg', charge / maxcharge)
var/damage = charge / (1 KILO JOULES)
var/discharged_energy = charge
user.electrocute_act(damage, src, 1, SHOCK_IGNORE_IMMUNITY|SHOCK_DELAY_STUN|SHOCK_NOGLOVES)
charge = 0
update_appearance()
if(user.stat != DEAD)
to_chat(user, span_suicide("There's not enough charge in [src] to kill you!"))
return SHAME
addtimer(CALLBACK(src, PROC_REF(gib_user), user, discharged_energy), 3 SECONDS)
return MANUAL_SUICIDE
/obj/item/stock_parts/power_store/proc/gib_user(mob/living/user, discharged_energy)
if(QDELETED(user))
return
if(discharged_energy < STANDARD_BATTERY_CHARGE)
return
user.dropItemToGround(src)
user.dust(just_ash = TRUE)
playsound(src, 'sound/effects/magic/lightningshock.ogg', 50, TRUE, 10)
tesla_zap(source = src, zap_range = 10, power = discharged_energy)
/obj/item/stock_parts/power_store/attack_self(mob/user)
. = ..()
if(.)
return
if(!ishuman(user))
return
var/mob/living/carbon/human/human_user = user
var/obj/item/organ/stomach/ethereal/user_stomach = human_user.get_organ_slot(ORGAN_SLOT_STOMACH)
if(!istype(user_stomach))
return
if(user_stomach.drain_time > world.time)
return
ethereal_drain(human_user, user_stomach)
/// Handles letting an ethereal drain our charge into their stomach
/obj/item/stock_parts/power_store/proc/ethereal_drain(mob/living/carbon/human/user, obj/item/organ/stomach/ethereal/used_stomach)
if(charge() <= 0)
balloon_alert(user, "out of charge!")
return
var/obj/item/stock_parts/power_store/stomach_cell = used_stomach.cell
used_stomach.drain_time = world.time + ETHEREAL_CELL_DRAIN_TIME
to_chat(user, span_notice("You begin clumsily channeling power from [src] into your body."))
while(do_after(user, ETHEREAL_CELL_DRAIN_TIME, target = src))
if(isnull(used_stomach) || (used_stomach != user.get_organ_slot(ORGAN_SLOT_STOMACH)))
balloon_alert(user, "stomach removed!?")
return
var/our_charge = charge()
var/scaled_stomach_used_charge = stomach_cell.used_charge() / ETHEREAL_CELL_POWER_GAIN_FACTOR
var/potential_charge = min(our_charge, scaled_stomach_used_charge)
var/to_drain = min(ETHEREAL_CELL_POWER_DRAIN, potential_charge)
var/energy_drained = use(to_drain, force = TRUE)
used_stomach.adjust_charge(energy_drained * ETHEREAL_CELL_POWER_GAIN_FACTOR)
update_appearance(UPDATE_OVERLAYS)
if(stomach_cell.used_charge() <= 0)
balloon_alert(user, "your charge is full!")
return
if(charge() <= 0)
balloon_alert(user, "out of charge!")
return
/obj/item/stock_parts/power_store/blob_act(obj/structure/blob/B)
SSexplosions.high_mov_atom += src
/obj/item/stock_parts/power_store/proc/get_electrocute_damage()
return ELECTROCUTE_DAMAGE(charge / max(0.001 * STANDARD_CELL_CHARGE, 1)) // Wouldn't want it to consider more energy than whatever is actually in the cell if for some strange reason someone set the STANDARD_CELL_CHARGE to below 1kJ.
/obj/item/stock_parts/power_store/get_part_rating()
return maxcharge * 10 + charge
#undef ETHEREAL_CELL_DRAIN_TIME
#undef ETHEREAL_CELL_POWER_DRAIN
#undef ETHEREAL_CELL_POWER_GAIN_FACTOR