Files
Bubberstation/code/modules/mining/boulder_processing/_boulder_processing.dm
Pickle-Coding c1f11f26ce Converts arbitrary energy units to the joule. Fixes conservation of energy issues relating to charging cells. (#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 #73438
Closes #75789
Closes #80634
Closes #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
🆑
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.
/🆑

---------

Co-authored-by: SyncIt21 <110812394+SyncIt21@users.noreply.github.com>
Co-authored-by: Ghom <42542238+Ghommie@users.noreply.github.com>
2024-03-23 16:58:56 +01:00

424 lines
14 KiB
Plaintext

/obj/machinery/bouldertech
name = "bouldertech brand refining machine"
desc = "You shouldn't be seeing this! And bouldertech isn't even a real company!"
icon = 'icons/obj/machines/mining_machines.dmi'
icon_state = "ore_redemption"
active_power_usage = BASE_MACHINE_ACTIVE_CONSUMPTION * 0.5
anchored = TRUE
density = TRUE
/// What is the efficiency of minerals produced by the machine?
var/refining_efficiency = 1
/// How much durability of an boulder can we reduce
var/boulders_processing_count = 2
/// How many boulders can we hold maximum?
var/boulders_held_max = 1
/// What sound plays when a thing operates?
var/usage_sound = 'sound/machines/mining/wooping_teleport.ogg'
/// Silo link to it's materials list.
var/datum/component/remote_materials/silo_materials
/// Mining points held by the machine for miners.
var/points_held = 0
///The action verb to display to players
var/action = "processing"
/// Cooldown associated with the sound played for collecting mining points.
COOLDOWN_DECLARE(sound_cooldown)
/// Cooldown associated with taking in boulds.
COOLDOWN_DECLARE(accept_cooldown)
/obj/machinery/bouldertech/Initialize(mapload)
. = ..()
silo_materials = AddComponent(
/datum/component/remote_materials, \
mapload, \
mat_container_flags = MATCONTAINER_NO_INSERT \
)
register_context()
/obj/machinery/bouldertech/LateInitialize()
. = ..()
var/static/list/loc_connections = list(
COMSIG_ATOM_ENTERED = PROC_REF(on_entered),
)
AddElement(/datum/element/connect_loc, loc_connections)
/obj/machinery/bouldertech/Destroy()
silo_materials = null
return ..()
/obj/machinery/bouldertech/on_deconstruction(disassembled)
if(length(contents))
for(var/obj/item/boulder/boulder in contents)
remove_boulder(boulder)
/obj/machinery/bouldertech/add_context(atom/source, list/context, obj/item/held_item, mob/user)
. = CONTEXTUAL_SCREENTIP_SET
if(isnull(held_item))
context[SCREENTIP_CONTEXT_RMB] = "Remove Boulder"
return
if(istype(held_item, /obj/item/boulder))
context[SCREENTIP_CONTEXT_LMB] = "Insert boulder"
else if(istype(held_item, /obj/item/card/id) && points_held > 0)
context[SCREENTIP_CONTEXT_LMB] = "Claim mining points"
else if(held_item.tool_behaviour == TOOL_SCREWDRIVER)
context[SCREENTIP_CONTEXT_LMB] = "[panel_open ? "Close" : "Open"] panel"
else if(held_item.tool_behaviour == TOOL_WRENCH)
context[SCREENTIP_CONTEXT_LMB] = "[anchored ? "Un" : ""]Anchor"
else if(panel_open && held_item.tool_behaviour == TOOL_CROWBAR)
context[SCREENTIP_CONTEXT_LMB] = "Deconstruct"
/obj/machinery/bouldertech/examine(mob/user)
. = ..()
. += span_notice("The machine reads that it has [span_bold("[points_held] mining points")] stored. Swipe an ID to claim them.")
. += span_notice("Click to remove a stored boulder.")
var/boulder_count = 0
for(var/obj/item/boulder/potential_boulder in contents)
boulder_count += 1
. += span_notice("Storage capacity = <b>[boulder_count]/[boulders_held_max] boulders</b>.")
. += span_notice("Can process upto <b>[boulders_processing_count] boulders</b> at a time.")
if(anchored)
. += span_notice("Its [EXAMINE_HINT("anchored")] in place.")
else
. += span_warning("It needs to be [EXAMINE_HINT("anchored")] to start operations.")
. += span_notice("Its maintainence panel can be [EXAMINE_HINT("screwed")] [panel_open ? "closed" : "open"].")
if(panel_open)
. += span_notice("The whole machine can be [EXAMINE_HINT("pried")] apart.")
/obj/machinery/bouldertech/update_icon_state()
. = ..()
var/suffix = ""
if(!anchored || panel_open || !is_operational || (machine_stat & (BROKEN | NOPOWER)))
suffix = "-off"
icon_state ="[initial(icon_state)][suffix]"
/obj/machinery/bouldertech/CanAllowThrough(atom/movable/mover, border_dir)
if(!anchored)
return FALSE
if(istype(mover, /obj/item/boulder))
return can_process_boulder(mover)
if(isgolem(mover))
return can_process_golem(mover)
return ..()
/**
* Can we process the boulder, checks only the boulders state & machines capacity
* Arguments
*
* * obj/item/boulder/new_boulder - the boulder we are checking
*/
/obj/machinery/bouldertech/proc/can_process_boulder(obj/item/boulder/new_boulder)
PRIVATE_PROC(TRUE)
SHOULD_BE_PURE(TRUE)
//machine not operational
if(!anchored || panel_open || !is_operational || (machine_stat & (BROKEN | NOPOWER)))
return FALSE
//not a valid boulder
if(!istype(new_boulder) || QDELETED(new_boulder))
return FALSE
//someone just processed this
if(new_boulder.processed_by)
return FALSE
//no space to hold boulders
var/boulder_count = 0
for(var/obj/item/boulder/potential_boulder in contents)
boulder_count += 1
if(boulder_count >= boulders_held_max)
return FALSE
//did we cooldown enough to accept a boulder
return COOLDOWN_FINISHED(src, accept_cooldown)
/**
* Accepts a boulder into the machine. Used when a boulder is first placed into the machine.
* Arguments
*
* * obj/item/boulder/new_boulder - the boulder to accept
*/
/obj/machinery/bouldertech/proc/accept_boulder(obj/item/boulder/new_boulder)
PRIVATE_PROC(TRUE)
if(!can_process_boulder(new_boulder))
return FALSE
new_boulder.forceMove(src)
COOLDOWN_START(src, accept_cooldown, 1.5 SECONDS)
return TRUE
/**
* Can we maim this golem
* Arguments
*
* * [rockman][mob/living/carbon/human] - the golem we are trying to main
*/
/obj/machinery/bouldertech/proc/can_process_golem(mob/living/carbon/human/rockman)
PRIVATE_PROC(TRUE)
SHOULD_BE_PURE(TRUE)
//not operatinal
if(!anchored || panel_open || !is_operational || (machine_stat & (BROKEN | NOPOWER)))
return FALSE
//still in cooldown
if(!COOLDOWN_FINISHED(src, accept_cooldown))
return FALSE
//not processable
if(!istype(rockman) || QDELETED(rockman) || rockman.body_position != LYING_DOWN)
return FALSE
return TRUE
/**
* Accepts a golem to be processed, mainly for memes
* Arguments
*
* * [rockman][mob/living/carbon/human] - the golem we are trying to main
*/
/obj/machinery/bouldertech/proc/accept_golem(mob/living/carbon/human/rockman)
PRIVATE_PROC(TRUE)
if(!can_process_golem(rockman))
return
if(!use_energy(active_power_usage * 1.5, force = FALSE))
say("Not enough energy!")
return
maim_golem(rockman)
playsound(src, usage_sound, 50, TRUE, SHORT_RANGE_SOUND_EXTRARANGE)
COOLDOWN_START(src, accept_cooldown, 3 SECONDS)
/// What effects actually happens to a golem when it is "processed"
/obj/machinery/bouldertech/proc/maim_golem(mob/living/carbon/human/rockman)
PROTECTED_PROC(TRUE)
Shake(duration = 1 SECONDS)
rockman.visible_message(span_warning("[rockman] is processed by [src]!"), span_userdanger("You get processed into bits by [src]!"))
rockman.investigate_log("was gibbed by [src] for being a golem", INVESTIGATE_DEATHS)
rockman.gib(DROP_ALL_REMAINS)
/obj/machinery/bouldertech/proc/on_entered(datum/source, atom/movable/atom_movable)
SIGNAL_HANDLER
if(istype(atom_movable, /obj/item/boulder))
INVOKE_ASYNC(src, PROC_REF(accept_boulder), atom_movable)
return
if(isgolem(atom_movable))
INVOKE_ASYNC(src, PROC_REF(accept_golem), atom_movable)
return
/**
* Looks for a boost to the machine's efficiency, and applies it if found.
* Applied more on the chemistry integration but can be used for other things if desired.
*/
/obj/machinery/bouldertech/proc/check_for_boosts()
PROTECTED_PROC(TRUE)
refining_efficiency = initial(refining_efficiency) //Reset refining efficiency to 100%.
/**
* Checks if this machine can process this material
* Arguments
*
* * datum/material/mat - the material to process
*/
/obj/machinery/bouldertech/proc/can_process_material(datum/material/mat)
PROTECTED_PROC(TRUE)
return FALSE
/obj/machinery/bouldertech/item_interaction(mob/living/user, obj/item/tool, list/modifiers, is_right_clicking)
if(panel_open || user.combat_mode)
return ..()
if(istype(tool, /obj/item/boulder))
var/obj/item/boulder/my_boulder = tool
if(!accept_boulder(my_boulder))
balloon_alert_to_viewers("cannot accept!")
return ITEM_INTERACT_BLOCKING
balloon_alert_to_viewers("accepted")
return ITEM_INTERACT_SUCCESS
if(istype(tool, /obj/item/card/id))
if(points_held <= 0)
balloon_alert_to_viewers("no points to claim!")
if(!COOLDOWN_FINISHED(src, sound_cooldown))
return ITEM_INTERACT_BLOCKING
COOLDOWN_START(src, sound_cooldown, 1.5 SECONDS)
playsound(src, 'sound/machines/buzz-sigh.ogg', 30, FALSE)
return ITEM_INTERACT_BLOCKING
var/obj/item/card/id/id_card = tool
var/amount = tgui_input_number(user, "How many mining points do you wish to claim? ID Balance: [id_card.registered_account.mining_points], stored mining points: [points_held]", "Transfer Points", max_value = points_held, min_value = 0, round_value = 1)
if(!amount)
return ITEM_INTERACT_BLOCKING
if(amount > points_held)
amount = points_held
id_card.registered_account.mining_points += amount
points_held = round(points_held - amount)
to_chat(user, span_notice("You claim [amount] mining points from \the [src] to [id_card]."))
return ITEM_INTERACT_SUCCESS
return ..()
/obj/machinery/bouldertech/wrench_act(mob/living/user, obj/item/tool)
. = ITEM_INTERACT_BLOCKING
if(default_unfasten_wrench(user, tool, time = 1.5 SECONDS) == SUCCESSFUL_UNFASTEN)
if(anchored)
begin_processing()
else
end_processing()
update_appearance(UPDATE_ICON_STATE)
return ITEM_INTERACT_SUCCESS
/obj/machinery/bouldertech/screwdriver_act(mob/living/user, obj/item/tool)
. = ITEM_INTERACT_BLOCKING
if(default_deconstruction_screwdriver(user, "[initial(icon_state)]-off", initial(icon_state), tool))
update_appearance(UPDATE_ICON_STATE)
return ITEM_INTERACT_SUCCESS
/obj/machinery/bouldertech/crowbar_act(mob/living/user, obj/item/tool)
. = ITEM_INTERACT_BLOCKING
if(default_deconstruction_crowbar(tool))
return ITEM_INTERACT_SUCCESS
/obj/machinery/bouldertech/attack_hand_secondary(mob/user, list/modifiers)
. = ..()
if(. == SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN || panel_open)
return
if(!anchored)
balloon_alert(user, "anchor first!")
return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
if(panel_open)
balloon_alert(user, "close panel!")
return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
var/obj/item/boulder/boulder = locate(/obj/item/boulder) in src
if(!boulder)
balloon_alert_to_viewers("no boulders to remove!")
return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
if(!remove_boulder(boulder))
balloon_alert_to_viewers("no space to remove!")
return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
/**
* Accepts a boulder into the machinery, then converts it into minerals.
* If the boulder can be fully processed by this machine, we take the materials, insert it into the silo, and destroy the boulder.
* If the boulder has materials left, we make a copy of the boulder to hold the processable materials, take the processable parts, and eject the original boulder.
* Arguments
*
* * obj/item/boulder/chosen_boulder - The boulder to being breaking down into minerals.
*/
/obj/machinery/bouldertech/proc/breakdown_boulder(obj/item/boulder/chosen_boulder)
PRIVATE_PROC(TRUE)
if(QDELETED(chosen_boulder))
return
if(chosen_boulder.loc != src)
return
if(!use_energy(active_power_usage, force = FALSE))
say("Not enough energy!")
return
//if boulders are kept inside because there is no space to eject them, then they could be reprocessed, lets avoid that
if(!chosen_boulder.processed_by)
check_for_boosts()
//here we loop through the boulder's ores
var/list/rejected_mats = list()
for(var/datum/material/possible_mat as anything in chosen_boulder.custom_materials)
var/quantity = chosen_boulder.custom_materials[possible_mat] * refining_efficiency
if(!can_process_material(possible_mat))
rejected_mats[possible_mat] = quantity
continue
points_held = round(points_held + (quantity * possible_mat.points_per_unit * MINING_POINT_MACHINE_MULTIPLIER)) // put point total here into machine
if(!silo_materials.mat_container.insert_amount_mat(quantity, possible_mat))
rejected_mats[possible_mat] = quantity
//puts back materials that couldn't be processed
chosen_boulder.set_custom_materials(rejected_mats, refining_efficiency)
//break the boulder down if we have processed all its materials
if(!length(chosen_boulder.custom_materials))
playsound(loc, usage_sound, 50, TRUE, SHORT_RANGE_SOUND_EXTRARANGE)
if(istype(chosen_boulder, /obj/item/boulder/artifact))
points_held = round((points_held + MINER_POINT_MULTIPLIER) * MINING_POINT_MACHINE_MULTIPLIER) /// Artifacts give bonus points!
chosen_boulder.break_apart()
return//We've processed all the materials in the boulder, so we can just destroy it in break_apart.
chosen_boulder.processed_by = src
//eject the boulder since we are done with it
remove_boulder(chosen_boulder)
/obj/machinery/bouldertech/process()
if(!anchored || panel_open || !is_operational || (machine_stat & (BROKEN | NOPOWER)))
return
var/boulders_found = FALSE
var/boulders_processed = boulders_processing_count
for(var/obj/item/boulder/potential_boulder in contents)
boulders_found = TRUE
if(boulders_processed <= 0)
break //Try again next time
boulders_processed--
if(potential_boulder.durability > 0)
potential_boulder.durability -= 1
if(potential_boulder.durability > 0)
continue
breakdown_boulder(potential_boulder)
boulders_found = FALSE
//when the boulder is removed it plays sound and displays a balloon alert. don't overlap when that happens
if(boulders_found)
playsound(loc, usage_sound, 29, FALSE, SHORT_RANGE_SOUND_EXTRARANGE)
balloon_alert_to_viewers(action)
/**
* Ejects a boulder from the machine. Used when a boulder is finished processing, or when a boulder can't be processed.
* Arguments
*
* * obj/item/boulder/specific_boulder - the boulder to remove
*/
/obj/machinery/bouldertech/proc/remove_boulder(obj/item/boulder/specific_boulder)
PRIVATE_PROC(TRUE)
if(QDELETED(specific_boulder))
return TRUE
if(locate(/obj/item/boulder) in loc) //There is an boulder in our loc. it has be removed so we don't clog up our loc with even more boulders
return FALSE
//Reset durability to little random lower value cause we have crushed it so many times
var/size = specific_boulder.boulder_size
if(size == BOULDER_SIZE_SMALL)
specific_boulder.durability = rand(2, BOULDER_SIZE_SMALL - 1)
else
specific_boulder.durability = rand(BOULDER_SIZE_SMALL, size - 1)
specific_boulder.processed_by = src //so we don't take in the boulder again after we just ejected it
specific_boulder.forceMove(drop_location())
specific_boulder.processed_by = null //now since move is done we can safely clear the reference
playsound(loc, 'sound/machines/ping.ogg', 50, FALSE)
return TRUE