Files
Bubberstation/code/modules/mining/boulder_processing/_boulder_processing.dm
SyncIt21 df2ce692ee General maintenance for all things boulder related. (#81358)
## About The Pull Request
**1. Qol**
- Adds screen tips & examines for screwdriver & crowbar acts on BRM,
Refinery & Smelter
- Adds examines to display number of boulders stored inside a refinery &
maximum number of boulders it can hold. Right click screentip to remove
boulders
- Adds examines to display maximum number of boulders than can be
teleported by a BRM & screentips for interacting with wires
- More audio & visual feedback for refinery processing. If a boulder
requires multiple steps you will get a balloon alert saying "crushing"
for refineries & "smelting" for smelters along with a sound per process
tick(which is every 2 seconds so no need for cooldown) giving you a
better idea of what's happening in the pipeline
- BRM now will display all lights when the "Automatic boulder retrieval"
is on & turn off the lights when disabled along with examines giving you
a visual indicator of its state

**2. Code Improvements**
- Splits types of boulders into its own file `boulder_types.dm` for easy
maintainability
- Moves beacon for refinery machines into its own file
`boulder_processing/beacon.dm` for easy maintainability
- Moves the cooldown for processing a boulder `processing_cooldown` into
the refinery machine itself. Since 100's of boulders can be created per
round this var can take up memory quickly so by moving them into the
refinery machine it gives us some savings
- Compressed & merged procs such as `create_mineral_contents()` ,
`flavour_boulder()` etc with the vent code. These procs were only used
by the vent 1 time & by merging the code we removed if conditions to
check if a parent vent was passed or not(since now that's always the
case). Helped in removing boilder plate code

**3. Fixes**
- **Fixes vents always spawning "Small size boulders" & not medium, nor
large boulders.**
 Once a vent generates a boulder it calls  `flavour_boulder()`

084f56938c/code/game/objects/structures/lavaland/ore_vent.dm (L385)
however this proc also accepts 2 more params `size` which would always
default to `BOULDER_SIZE_SMALL` and `is_artifact` which is simply unused
in the proc

fb83617ff9/code/modules/mining/boulder_processing/boulder.dm (L219)
Therefore vents would always generate small boulders giving us no
varity. Now the boulder size is set depending on the vent size &
durability for each boulder is set to a random value between 2 & the
boulder max size giving us the flavour we actually wanted

- **Fixes "Expanded Gulag boulders" using "normal gulag material list"
when setting its custom materials.**
If you look at the `add_gulag_minerals()` proc it always picks from the
`gulag_minerals` list & accepts no params

fb83617ff9/code/modules/mining/boulder_processing/boulder.dm (L235-L236)
So when we try to pass params to this proc which in reality doesn't
accept any we were wasting our time doing this

fb83617ff9/code/modules/mining/boulder_processing/boulder.dm (L274)
And for our case `expanded_gulag_minerals` list was simply unused
because our proc doesn't care about it and it went back to just using
`gulag_minerals` list thus ignoring our list

fb83617ff9/code/modules/mining/boulder_processing/boulder.dm (L282)
As i said in the "Code Improvement` section when i moved boulder types
into it's own unique file this was fixed & now expanded gulag boulders
actually has a chance to spawn with bluespace crystals inside them

- **Fixes manual tapping of ore vents by hand not using a cooldown**
`produce_boulder()` accepts a cooldown var for when you need to manually
tap the vent by hand.

e8b5b52d54/code/game/objects/structures/lavaland/ore_vent.dm (L374)
This var was always set to FALSE because we never passed `TRUE` into it.
Not once here

e8b5b52d54/code/game/objects/structures/lavaland/ore_vent.dm (L124)
Nor here

e8b5b52d54/code/game/objects/structures/lavaland/ore_vent.dm (L131)
Now we just pass `TRUE` so tapping these vents by hand have a cooldown

- **Fixes BRM off icon state never being used**
When the room ran out of power it would still look on. Now we use that
state correctly

- **Fixes Automatic Boulder Retrieval by the BRM not actually being
automatic**
You must have noticed that once you do "Right click" and wait for all
the boulders it can teleport (determined by `boulder_processing_max`) to
be teleported it stops permanently after that. Even if more boulders get
generated it won't do anything, You have to again "Right click" &
retoggle automatic boulder retrieval on again, thus forcing someone to
stand there & monitor the BRM

Now once you set Automatic Boulder Retrieval on you can leave & forget.
It will teleport boulders as & when available thus enabling automation
properly.

- **Fixes boulders ejected from refineries via right click from getting
teleported back into the machines loc**
Fixes
https://github.com/tgstation/tgstation/pull/78524#issuecomment-1911666995.
The problem is refinery machines & the BRM keep track of all the
boulders that entered into it via the `boulders_contained` list.

Now we directly check `contents` for boulders so we don't have to
maintain 2 seperate lists to keep track of boulders. It also now uses
`processed_by` var of boulders to ensure refinerries don't retake in the
same boulder it just processed. Not sure where exactly the problem got
fixed but implementing these 2 measures fixed it regardless.

- **Fixes boulders with 0 durability[a.k.a steps] from getting ejected
out**
Fixes
https://github.com/tgstation/tgstation/pull/78524#issuecomment-1914551952.
So inside `process()` we constantly decrease the durability of the
boulder till it becomes 0.

0a496f180c/code/modules/mining/boulder_processing/_boulder_processing.dm (L159)

  When it reaches 0 it calls `breakdown_boulder()`

0a496f180c/code/modules/mining/boulder_processing/_boulder_processing.dm (L164-L165)

This proc has a chance to reject the boulder if it could not process any
materials

0a496f180c/code/modules/mining/boulder_processing/_boulder_processing.dm (L219-L222)
  
  **"Without resetting its durability"** over here

0a496f180c/code/modules/mining/boulder_processing/_boulder_processing.dm (L241)

So it ends up rejecting a "0" or worse -1 durability boulder. Now we set
the durability in `remove_boulder()` so regardless of what circumstances
the boulder is ejected it always gets a positive durability

- **Fixes BRM & Refinery from rapidly spitting out boulders in their loc
which causes lag in the long terms**
Fixes #81404. Basically even if there is 1 boulder sitting at a BRM's
loc or an refineries loc. Operations are haulted i.e. the BRM will not
teleport any more boulders & the refinery will keep their already
processed boulders inside till their locs are cleared from boulders.
This prevents large number of boulders from pilling up in long rounds

- **[Priority : High] Fixes refineries incorrectly removing materials
from processed boulders**
Fixes #81109. This bug is quite serious because it can't literarily
affect any random item with custom materials in game. This one line of
code over here can break the entire material economy as we know it

0a496f180c/code/modules/mining/boulder_processing/_boulder_processing.dm (L217)
  
**"DONT DO THIS"**. The `custom_materials` list is a **"read only"**
list & if you ever want to change it call the `set_custom_materials()`
proc with your new values but do not edit this list manually as it is
done here.
  
All lists related to materials are cached by the `SSmaterials`
subsystem. List values are cached & shared across multiple objects so
when you edit those values like here, you might end up effecting an
item/multiple items in some random corner of the map that shares this
list.

This also causes boulders with empty list of materials to get spawned at
random so yeah again plzz don't do this

**4. Refactors**
- Repathes `obj/machinery/boulder_processing/brm` -> just
`obj/machinery/brm`.
Even though semantically it looks nice that the brm is a subtype of
`obj/machinery/boulder_processing` from a code & operation perspective
they have 0 similarities.

1) The BRM does not accept boulders feed into it from a conveyer belt
unlike a refinery but instead picks boulders from `SSore` subsystem &
put it on the conveyer belt. This means procs for accepting boulders
such `CanAllowThrough()`, `breakdown_boulder()`, `accept_boulder()` etc
have no use in the BRM. Their just code clutter at this point
2) The BRM overrides `process()` & does not call its parent proc making
that code wasted
3) It has no use for silo materials & mining points making those vars go
to waste

With so much wasted code its better to just let go off all of it & just
make it a basic instance of `obj/machinery` making maintainence easy

- BRM now teleports boulders in a batch (batch size determined by
`boulders_processing_max` max value from upgraded parts is 7) with a
boulder appearing every 1.5 seconds rather than spawning all at once.
After a batch is processed it has a cooldown of 3 seconds before
repeating the process if automatic boulder retrieval is on. This stops
the conveyer belt from getting crowded with boulders and makes the
refining process more efficient.

With this BRM wires are removed because only it had only 1 wire
responsible for toggling boulder retrieval but now since this process is
automatic, we have true control over the timing of boulders spawned &
don't want to leave it in the hands of players

## Changelog
🆑
qol: adds examines & screentips for crowbar, screwdriver acts to BRM &
refinery machines
qol: adds examines about the number of boulders stored & processed to
BRM & refinery machines
qol: BRM now has its lights turn on/off depending on wether automatic
boulder retrieval is on/off for visual clarity along with examines
qol: refinery machines now display ballon alerts & plays sounds more
frequently when processing boulders for better feedback
fix: vents now spawn boulders of all sizes & not just small ones
fix: expanded gulag boulders now have correct materials in them.
fix: manual tapping of vents now has a cooldown applied as intended.
fix: BRM has its light turned off when area power goes off
fix: boulders ejected from refineries by hand no longer teleport all
over the place occasionally.
fix: refineries no longer eject boulders with 0 durability
fix: Boulders & refineries no longer pile up on top of BRM's &
refineries in long rounds. Their locs have to be clear of boulders
before they spit out more boulders to prevent a large pile of boulders
from causing lag
fix: sheets ejected from lathes no longer get rejected when inserted
back which could happen at random, no more boulders with empty materials
code: splits boulder types into its own file along with other items
code: merges & autodocs procs, vars related to boulders
refactor: repaths BRM to a simpler subtype
refactor: BRM now spawns boulders in batches(batch size can be increased
with upgraded parts) with a boulder appearing every second. After a
batch is processed a 3 second cooldown is applied to stop the conveyer
belt from clogging up, With this BRM wires are removed as there is no
need for timers to be attached to wires which intefers without our batch
processing timings.
/🆑

---------

Co-authored-by: Ghom <42542238+Ghommie@users.noreply.github.com>
2024-02-18 03:40:57 +01:00

362 lines
13 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 || !is_operational || (machine_stat & (BROKEN | NOPOWER)) || panel_open)
suffix = "-off"
icon_state ="[initial(icon_state)][suffix]"
/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/CanAllowThrough(atom/movable/mover, border_dir)
if(!anchored)
return FALSE
if(istype(mover, /obj/item/boulder))
var/obj/item/boulder/boulder = mover
return can_process_boulder(boulder)
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)
if(!can_process_boulder(new_boulder))
return FALSE
new_boulder.forceMove(src)
COOLDOWN_START(src, accept_cooldown, 1.5 SECONDS)
return TRUE
/obj/machinery/bouldertech/proc/on_entered(datum/source, atom/movable/atom_movable)
SIGNAL_HANDLER
if(!can_process_boulder(atom_movable))
return
INVOKE_ASYNC(src, PROC_REF(accept_boulder), atom_movable)
/**
* 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/attackby(obj/item/attacking_item, mob/user, params)
if(panel_open)
return ..()
if(istype(attacking_item, /obj/item/boulder))
. = TRUE
var/obj/item/boulder/my_boulder = attacking_item
if(!accept_boulder(my_boulder))
balloon_alert_to_viewers("cannot accept!")
return
balloon_alert_to_viewers("accepted")
return
if(istype(attacking_item, /obj/item/card/id))
. = TRUE
if(points_held <= 0)
balloon_alert_to_viewers("no points to claim!")
if(!COOLDOWN_FINISHED(src, sound_cooldown))
return
COOLDOWN_START(src, sound_cooldown, 1.5 SECONDS)
playsound(src, 'sound/machines/buzz-sigh.ogg', 30, FALSE)
return
var/obj/item/card/id/id_card = attacking_item
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
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
return ..()
/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.
* @param 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 FALSE
if(chosen_boulder.loc != src)
return FALSE
//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
use_power(active_power_usage)
//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 TRUE //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