Skip to content

Commit

Permalink
Converts arbitrary energy units to the joule. Fixes conservation of e…
Browse files Browse the repository at this point in the history
…nergy issues relating to charging cells. (tgstation#81579)

## About The Pull Request
Removes all arbitrary energy and power units in the codebase. Everything
is replaced with the joule and watt, with 1 = 1 joule, or 1 watt if you
are going to multiply by time. This is a visible change, where all
arbitrary energy units you see in the game will get proper prefixed
units of energy.

With power cells being converted to the joule, charging one joule of a
power cell will require one joule of energy.

The grid will now store energy, instead of power. When an energy usage
is described as using the watt, a power to energy conversion based on
the relevant subsystem's timing (usually multiplying by seconds_per_tick
or applying power_to_energy()) is needed before adding or removing from
the grid. Power usages that are described as the watt is really anything
you would scale by time before applying the load. If it's described as a
joule, no time conversion is needed. Players will still read the grid as
power, having no visible change.

Machines that dynamically use power with the use_power() proc will
directly drain from the grid (and apc cell if there isn't enough)
instead of just tallying it up on the dynamic power usages for the area.
This should be more robust at conserving energy as the surplus is
updated on the go, preventing charging cells from nothing.

APCs no longer consume power for the dynamic power usage channels. APCs
will consume power for static power usages. Because static power usages
are added up without checking surplus, static power consumption will be
applied before any machine processes. This will give a more truthful
surplus for dynamic power consumers.

APCs will display how much power it is using for charging the cell. APC
cell charging applies power in its own channel, which gets added up to
the total. This will prevent invisible power usage you see when looking
at the power monitoring console.

After testing in MetaStation, I found roundstart power consumption to be
around 406kW after all APCs get fully charged. During the roundstart APC
charge rush, the power consumption can get as high as over 2MW (up to
25kW per roundstart APC charging) as long as there's that much
available.

Because of the absurd potential power consumption of charging APCs near
roundstart, I have changed how APCs decide to charge. APCs will now
charge only after all other machines have processed in the machines
processing subsystem. This will make sure APC charging won't disrupt
machines taking from the grid, and should stop APCs getting their power
drained due to others demanding too much power while charging. I have
removed the delays for APC charging too, so they start charging
immediately whenever there's excess power. It also stops them turning
red when a small amount of cell gets drained (airlocks opening and shit
during APC charge rush), as they immediately become fully charged
(unless too much energy got drained somehow) before changing icon.

Engineering SMES now start at 100% charge instead of 75%. I noticed
cells were draining earlier than usual after these changes, so I am
making them start maxed to try and combat that.

These changes will fix all conservation of energy issues relating to
charging powercells.
## Why It's Good For The Game
Closes tgstation#73438
Closes tgstation#75789
Closes tgstation#80634
Closes tgstation#82031

Makes it much easier to interface with the power system in the codebase.
It's more intuitive. Removes a bunch of conservation of energy issues,
making energy and power much more meaningful. It will help the
simulation remain immersive as players won't encounter energy
duplication so easily. Arbitrary energy units getting replaced with the
joule will also tell people more meaningful information when reading it.
APC charging will feel more snappy.
## Changelog
:cl:
fix: Fixes conservation of energy issues relating to charging
powercells.
qol: APCs will display how much power they are using to charge their
cell. This is accounted for in the power monitoring console.
qol: All arbitrary power cell energy units you see are replaced with
prefixed joules.
balance: As a consequence of the conservation of energy issues getting
fixed, the power consumption for charging cells is now very significant.
balance: APCs only use surplus power from the grid after every machine
processes when charging, preventing APCs from causing others to
discharge while charging.
balance: Engineering SMES start at max charge to combat the increased
energy loss due to conservation of energy fixes.
/:cl:

---------

Co-authored-by: SyncIt21 <[email protected]>
Co-authored-by: Ghom <[email protected]>
  • Loading branch information
3 people authored Mar 23, 2024
1 parent d750fbb commit c1f11f2
Show file tree
Hide file tree
Showing 202 changed files with 1,016 additions and 776 deletions.
2 changes: 1 addition & 1 deletion .github/guides/STYLE.md
Original file line number Diff line number Diff line change
Expand Up @@ -457,7 +457,7 @@ deal_damage(10) // Fine! The proc name makes it obvious `10` is the damage...at
deal_damage(10, FIRE) // Also fine! `FIRE` makes it obvious the second parameter is damage type.
deal_damage(damage = 10) // Redundant, but not prohibited.
use_power(30) // Fine! `30` is obviously something like watts.
use_energy(30 JOULES) // Use energy in joules.
turn_on(30) // Not fine!
turn_on(power_usage = 30) // Fine!
Expand Down
2 changes: 1 addition & 1 deletion _maps/shuttles/emergency_medisim.dmm
Original file line number Diff line number Diff line change
Expand Up @@ -701,7 +701,7 @@
iron_cost = 0;
maximum_idle = 1;
name = "binoculars fabricator";
power_used = 0;
energy_used = 0;
starting_amount = 25000
},
/turf/open/floor/mineral/titanium/yellow,
Expand Down
2 changes: 1 addition & 1 deletion code/__DEFINES/apc_defines.dm
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
/// How long it takes an ethereal to drain or charge APCs. Also used as a spam limiter.
#define APC_DRAIN_TIME (7.5 SECONDS)
/// How much power ethereals gain/drain from APCs.
#define APC_POWER_GAIN 200
#define APC_POWER_GAIN (200 KILO JOULES)

// Wires & EMPs:
/// The wire value used to reset the APCs wires after one's EMPed.
Expand Down
5 changes: 5 additions & 0 deletions code/__DEFINES/combat.dm
Original file line number Diff line number Diff line change
Expand Up @@ -389,3 +389,8 @@ GLOBAL_LIST_INIT(arm_zones, list(BODY_ZONE_L_ARM, BODY_ZONE_R_ARM))
#define DEATHMATCH_NOT_PLAYING 0
#define DEATHMATCH_PRE_PLAYING 1
#define DEATHMATCH_PLAYING 2

/// The amount of energy needed to increase the burn force by 1 damage during electrocution.
#define JOULES_PER_DAMAGE (25 KILO JOULES)
/// Calculates the amount of burn force when applying this much energy to a mob via electrocution from an energy source.
#define ELECTROCUTE_DAMAGE(energy) (energy >= 1 KILO JOULES ? clamp(20 + round(energy / JOULES_PER_DAMAGE), 20, 195) + rand(-5,5) : 0)
4 changes: 2 additions & 2 deletions code/__DEFINES/devices.dm
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
#define INSPECTOR_PRINT_SOUND_MODE_FAFAFOGGY 4
#define BANANIUM_CLOWN_INSPECTOR_PRINT_SOUND_MODE_LAST 4
#define CLOWN_INSPECTOR_PRINT_SOUND_MODE_LAST 4
#define INSPECTOR_POWER_USAGE_HONK 15
#define INSPECTOR_POWER_USAGE_NORMAL 5
#define INSPECTOR_ENERGY_USAGE_HONK (15 KILO JOULES)
#define INSPECTOR_ENERGY_USAGE_NORMAL (5 KILO JOULES)
#define INSPECTOR_TIME_MODE_SLOW 1
#define INSPECTOR_TIME_MODE_FAST 2
#define INSPECTOR_TIME_MODE_HONK 3
Expand Down
7 changes: 4 additions & 3 deletions code/__DEFINES/machines.dm
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
#define AREA_USAGE_STATIC_EQUIP 4
#define AREA_USAGE_STATIC_LIGHT 5
#define AREA_USAGE_STATIC_ENVIRON 6
#define AREA_USAGE_LEN AREA_USAGE_STATIC_ENVIRON // largest idx
#define AREA_USAGE_APC_CHARGE 7
#define AREA_USAGE_LEN AREA_USAGE_APC_CHARGE // largest idx

/// Index of the first dynamic usage channel
#define AREA_USAGE_DYNAMIC_START AREA_USAGE_EQUIP
Expand All @@ -27,8 +28,8 @@
#define ACTIVE_POWER_USE 2

///Base global power consumption for idling machines
#define BASE_MACHINE_IDLE_CONSUMPTION 100
///Base global power consumption for active machines
#define BASE_MACHINE_IDLE_CONSUMPTION (100 WATTS)
///Base global power consumption for active machines. The unit is ambiguous (joules or watts) depending on the use case for dynamic users.
#define BASE_MACHINE_ACTIVE_CONSUMPTION (BASE_MACHINE_IDLE_CONSUMPTION * 10)

/// Bitflags for a machine's preferences on when it should start processing. For use with machinery's `processing_flags` var.
Expand Down
14 changes: 7 additions & 7 deletions code/__DEFINES/mobs.dm
Original file line number Diff line number Diff line change
Expand Up @@ -289,14 +289,14 @@
//Used as an upper limit for species that continuously gain nutriment
#define NUTRITION_LEVEL_ALMOST_FULL 535

//Charge levels for Ethereals
//Charge levels for Ethereals, in joules.
#define ETHEREAL_CHARGE_NONE 0
#define ETHEREAL_CHARGE_LOWPOWER 400
#define ETHEREAL_CHARGE_NORMAL 1000
#define ETHEREAL_CHARGE_ALMOSTFULL 1500
#define ETHEREAL_CHARGE_FULL 2000
#define ETHEREAL_CHARGE_OVERLOAD 2500
#define ETHEREAL_CHARGE_DANGEROUS 3000
#define ETHEREAL_CHARGE_LOWPOWER (400 KILO JOULES)
#define ETHEREAL_CHARGE_NORMAL (1 MEGA JOULES)
#define ETHEREAL_CHARGE_ALMOSTFULL (1.5 MEGA JOULES)
#define ETHEREAL_CHARGE_FULL (2 MEGA JOULES)
#define ETHEREAL_CHARGE_OVERLOAD (2.5 MEGA JOULES)
#define ETHEREAL_CHARGE_DANGEROUS (3 MEGA JOULES)


#define CRYSTALIZE_COOLDOWN_LENGTH (120 SECONDS)
Expand Down
4 changes: 2 additions & 2 deletions code/__DEFINES/mod.dm
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
/// Default value for the max_complexity var on MODsuits
#define DEFAULT_MAX_COMPLEXITY 15

/// Default cell drain per process on MODsuits
#define DEFAULT_CHARGE_DRAIN 5
/// The default cell drain of a modsuit. The standard modsuit active power usage drains this much energy per modsuit second.
#define DEFAULT_CHARGE_DRAIN (0.005 * STANDARD_CELL_CHARGE) // A standard cell lasts 200 seconds with this on active power usage, while a high power one lasts 2,000 seconds.

/// Default time for a part to seal
#define MOD_ACTIVATION_STEP_TIME (2 SECONDS)
Expand Down
17 changes: 12 additions & 5 deletions code/__DEFINES/power.dm
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,19 @@
#define SOLAR_TRACK_TIMED 1
#define SOLAR_TRACK_AUTO 2

///conversion ratio from joules to watts
#define WATTS / 0.002
///conversion ratio from watts to joules
#define JOULES * 0.002
///The watt is the standard unit of power for this codebase. Do not change this.
#define WATT 1
///The joule is the standard unit of energy for this codebase. Do not change this.
#define JOULE 1
///The watt is the standard unit of power for this codebase. You can use this with other defines to clarify that it will be multiplied by time.
#define WATTS * WATT
///The joule is the standard unit of energy for this codebase. You can use this with other defines to clarify that it will not be multiplied by time.
#define JOULES * JOULE

GLOBAL_VAR_INIT(CHARGELEVEL, 0.001) // Cap for how fast cells charge, as a percentage-per-tick (.001 means cellcharge is capped to 1% per second)
///The amount of energy, in joules, a standard powercell has.
#define STANDARD_CELL_CHARGE (1 MEGA JOULES) // 1 MJ.

GLOBAL_VAR_INIT(CHARGELEVEL, 0.01) // Cap for how fast cells charge, as a percentage per second (.01 means cellcharge is capped to 1% per second)

// Converts cable layer to its human readable name
GLOBAL_LIST_INIT(cable_layer_to_name, list(
Expand Down
5 changes: 3 additions & 2 deletions code/__DEFINES/projectiles.dm
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,9 @@
#define RETURN_POINT_VECTOR(ATOM, ANGLE, SPEED) (new /datum/point/vector(ATOM, null, null, null, null, ANGLE, SPEED))
#define RETURN_POINT_VECTOR_INCREMENT(ATOM, ANGLE, SPEED, AMT) (new /datum/point/vector(ATOM, null, null, null, null, ANGLE, SPEED, AMT))

/// The amount of energy that a standard energy weapon cell can hold
#define STANDARD_CELL_CHARGE 1000
///The self charging rate of energy guns that magically recharge themselves, in watts.
#define STANDARD_ENERGY_GUN_SELF_CHARGE_RATE (0.05 * STANDARD_CELL_CHARGE)

/// Macro to turn a number of laser shots into an energy cost, based on the above define
/// e.g. LASER_SHOTS(12, STANDARD_CELL_CHARGE) means 12 shots
#define LASER_SHOTS(X, MAX_CHARGE) (((100 * MAX_CHARGE) - ((100 * MAX_CHARGE) % X)) / (100 * X)) // I wish I could just use round, but it can't be used in datum members
4 changes: 4 additions & 0 deletions code/__DEFINES/robots.dm
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@

/// Special value to reset cyborg's lamp_cooldown
#define BORG_LAMP_CD_RESET -1
/// How many watts per lamp power is consumed while the lamp is on.
#define BORG_LAMP_POWER_CONSUMPTION (1000 WATTS)
/// The minimum power consumption of a cyborg.
#define BORG_MINIMUM_POWER_CONSUMPTION (500 WATTS)

//Module slot define
///The third module slots is disabed.
Expand Down
21 changes: 21 additions & 0 deletions code/__DEFINES/si.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Prefix values.
#define QUECTO * 1e-30
#define RONTO * 1e-27
#define YOCTO * 1e-24
#define ZEPTO * 1e-21
#define ATTO * 1e-18
#define FEMPTO * 1e-15
#define PICO * 1e-12
#define NANO * 1e-9
#define MICRO * 1e-6
#define MILLI * 1e-3
#define KILO * 1e3
#define MEGA * 1e6
#define GIGA * 1e9
#define TERA * 1e12
#define PETA * 1e15
#define EXA * 1e18
#define ZETTA * 1e21
#define YOTTA * 1e24
#define RONNA * 1e27
#define QUETTA * 1e30
5 changes: 5 additions & 0 deletions code/__DEFINES/subsystems.dm
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,11 @@
#define SSEXPLOSIONS_MOVABLES 2
#define SSEXPLOSIONS_THROWS 3

// Machines subsystem subtasks.
#define SSMACHINES_APCS_EARLY 1
#define SSMACHINES_MACHINES 2
#define SSMACHINES_APCS_LATE 3

// Wardrobe subsystem tasks
#define SSWARDROBE_STOCK 1
#define SSWARDROBE_INSPECT 2
Expand Down
31 changes: 16 additions & 15 deletions code/__HELPERS/maths.dm
Original file line number Diff line number Diff line change
Expand Up @@ -154,27 +154,28 @@
var/prefix = prefixes[prefix_index]
. = list(SI_COEFFICIENT = coefficient, SI_UNIT = " [prefix][unit]")

///Format a power value in prefixed watts.
/proc/display_power(powerused)
return siunit(powerused, "W", 3)
/**Format a power value in prefixed watts.
* Converts from energy if convert is true.
* Args:
* - power: The value of power to format.
* - convert: Whether to convert this from joules.
* Returns: The string containing the formatted power.
*/
/proc/display_power(power, convert = TRUE)
power = convert ? energy_to_power(power) : power
return siunit(power, "W", 3)

///Format an energy value in prefixed joules.
/proc/display_joules(units)
/proc/display_energy(units)
return siunit(units, "J", 3)

/proc/joules_to_energy(joules)
///Converts the joule to the watt, assuming SSmachines tick rate.
/proc/energy_to_power(joules)
return joules * (1 SECONDS) / SSmachines.wait

/proc/energy_to_joules(energy_units)
return energy_units * SSmachines.wait / (1 SECONDS)

///Format an energy value measured in Power Cell units.
/proc/display_energy(units)
// APCs process every (SSmachines.wait * 0.1) seconds, and turn 1 W of
// excess power into watts when charging cells.
// With the current configuration of wait=20 and CELLRATE=0.002, this
// means that one unit is 1 kJ.
return display_joules(energy_to_joules(units) WATTS)
///Converts the watt to the joule, assuming SSmachines tick rate.
/proc/power_to_energy(watts)
return watts * SSmachines.wait / (1 SECONDS)

///chances are 1:value. anyprob(1) will always return true
/proc/anyprob(value)
Expand Down
60 changes: 48 additions & 12 deletions code/controllers/subsystem/machines.dm
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ SUBSYSTEM_DEF(machines)

var/list/processing = list()
var/list/currentrun = list()
var/list/apc_early_processing = list()
var/list/apc_late_processing = list()
var/current_part = SSMACHINES_APCS_EARLY
///List of all powernets on the server.
var/list/datum/powernet/powernets = list()

Expand Down Expand Up @@ -43,7 +46,7 @@ SUBSYSTEM_DEF(machines)
for(var/next_type in typesof(machine_type))
var/list/found_machines = machines_by_type[next_type]
if(found_machines)
machines += found_machines
machines += found_machines
return machines


Expand Down Expand Up @@ -79,19 +82,52 @@ SUBSYSTEM_DEF(machines)
if (!resumed)
for(var/datum/powernet/powernet as anything in powernets)
powernet.reset() //reset the power state.
current_part = SSMACHINES_APCS_EARLY
src.currentrun = apc_early_processing.Copy()

//APC early processing. Draws static power usages from their grids.
if(current_part == SSMACHINES_APCS_EARLY)
//cache for sanic speed (lists are references anyways)
var/list/currentrun = src.currentrun
while(currentrun.len)
var/obj/machinery/power/apc/apc = currentrun[currentrun.len]
currentrun.len--
if(QDELETED(apc) || apc.early_process(wait * 0.1) == PROCESS_KILL)
apc_early_processing -= apc
apc.datum_flags &= ~DF_ISPROCESSING
if(MC_TICK_CHECK)
return
current_part = SSMACHINES_MACHINES
src.currentrun = processing.Copy()

//cache for sanic speed (lists are references anyways)
var/list/currentrun = src.currentrun

while(currentrun.len)
var/obj/machinery/thing = currentrun[currentrun.len]
currentrun.len--
if(QDELETED(thing) || thing.process(wait * 0.1) == PROCESS_KILL)
processing -= thing
thing.datum_flags &= ~DF_ISPROCESSING
if (MC_TICK_CHECK)
return
//General machine processing. Their power usage can be dynamic and based on surplus power, so they come after static power usage have been applied.
if(current_part == SSMACHINES_MACHINES)
//cache for sanic speed (lists are references anyways)
var/list/currentrun = src.currentrun
while(currentrun.len)
var/obj/machinery/thing = currentrun[currentrun.len]
currentrun.len--
if(QDELETED(thing) || thing.process(wait * 0.1) == PROCESS_KILL)
processing -= thing
thing.datum_flags &= ~DF_ISPROCESSING
if (MC_TICK_CHECK)
return
current_part = SSMACHINES_APCS_LATE
src.currentrun = apc_late_processing.Copy()

//APC late processing. APCs will use the remaining power on the grid to charge their cells if needed.
//This is applied at the end so charging APCs don't cause others to discharge by taking all the power from the grid before machines use power.
if(current_part == SSMACHINES_APCS_LATE)
//cache for sanic speed (lists are references anyways)
var/list/currentrun = src.currentrun
while(currentrun.len)
var/obj/machinery/power/apc/apc = currentrun[currentrun.len]
currentrun.len--
if(QDELETED(apc) || apc.late_process(wait * 0.1) == PROCESS_KILL)
apc_late_processing -= apc
apc.datum_flags &= ~DF_ISPROCESSING
if(MC_TICK_CHECK)
return

/datum/controller/subsystem/machines/proc/setup_template_powernets(list/cables)
var/obj/structure/cable/PC
Expand Down
4 changes: 2 additions & 2 deletions code/datums/components/shell.dm
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@
if(attached_circuit)
remove_circuit()
return
location.use_power(power_to_use, AREA_USAGE_EQUIP)
location.apc?.terminal?.use_energy(power_to_use, channel = AREA_USAGE_EQUIP)
power_used_in_minute += power_to_use
COOLDOWN_START(src, power_used_cooldown, 1 MINUTES)
return COMPONENT_OVERRIDE_POWER_USAGE
Expand Down Expand Up @@ -327,7 +327,7 @@
else if(circuitboard.loc != parent_atom)
circuitboard.forceMove(parent_atom)
attached_circuit.set_shell(parent_atom)

// call after set_shell() sets on to true
if(shell_flags & SHELL_FLAG_REQUIRE_ANCHOR)
attached_circuit.set_on(parent_atom.anchored)
Expand Down
6 changes: 3 additions & 3 deletions code/datums/mutations/touch.dm
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
///This var decides if the spell should chain, dictated by presence of power chromosome
var/chain = FALSE
///Affects damage, should do about 1 per limb
var/zap_power = 7500
var/zap_power = 7.5 KILO JOULES
///Range of tesla shock bounces
var/zap_range = 7
///flags that dictate what the tesla shock can interact with, Can only damage mobs, Cannot damage machines or generate energy
Expand All @@ -60,7 +60,7 @@
span_userdanger("[caster] electrocutes you!"),
)
if(chain)
tesla_zap(source = victim, zap_range = zap_range, power = zap_power, cutoff = 1e3, zap_flags = zap_flags)
tesla_zap(source = victim, zap_range = zap_range, power = zap_power, cutoff = 1 KILO JOULES, zap_flags = zap_flags)
carbon_victim.visible_message(span_danger("An arc of electricity explodes out of [victim]!"))
return TRUE

Expand All @@ -72,7 +72,7 @@
span_userdanger("[caster] electrocutes you!"),
)
if(chain)
tesla_zap(source = victim, zap_range = zap_range, power = zap_power, cutoff = 1e3, zap_flags = zap_flags)
tesla_zap(source = victim, zap_range = zap_range, power = zap_power, cutoff = 1 KILO JOULES, zap_flags = zap_flags)
living_victim.visible_message(span_danger("An arc of electricity explodes out of [victim]!"))
return TRUE

Expand Down
Loading

0 comments on commit c1f11f2

Please sign in to comment.