diff --git a/code/datums/trading/traders/ai.dm b/code/datums/trading/traders/ai.dm
index 2166a7ba1cf..ca86637d32d 100644
--- a/code/datums/trading/traders/ai.dm
+++ b/code/datums/trading/traders/ai.dm
@@ -90,7 +90,8 @@ They sell generic supplies and ask for generic supplies.
/obj/item/stack/material/ingot/mapped/osmium = TRADER_THIS_TYPE,
/obj/item/stack/material/sheet/mapped/steel = TRADER_THIS_TYPE,
/obj/item/stack/material/sheet/reinforced/mapped/plasteel = TRADER_THIS_TYPE,
- /obj/machinery/mining = TRADER_SUBTYPES_ONLY
+ /obj/machinery/mining_drill = TRADER_THIS_TYPE,
+ /obj/structure/drill_brace = TRADER_THIS_TYPE
)
/datum/trader/trading_beacon/manufacturing
diff --git a/code/datums/trading/traders/goods.dm b/code/datums/trading/traders/goods.dm
index 5935889e632..d260685de19 100644
--- a/code/datums/trading/traders/goods.dm
+++ b/code/datums/trading/traders/goods.dm
@@ -454,8 +454,8 @@ Sells devices, odds and ends, and medical stuff
)
possible_trading_items = list(
- /obj/machinery/mining/drill = TRADER_THIS_TYPE,
- /obj/machinery/mining/brace = TRADER_THIS_TYPE,
+ /obj/machinery/mining_drill = TRADER_THIS_TYPE,
+ /obj/structure/drill_brace = TRADER_THIS_TYPE,
/obj/machinery/floodlight = TRADER_THIS_TYPE,
/obj/item/box/greenglowsticks = TRADER_THIS_TYPE,
/obj/item/clothing/suit/space/void/engineering/salvage/prepared = TRADER_THIS_TYPE,
diff --git a/code/game/objects/items/weapons/circuitboards/machinery/mining_drill.dm b/code/game/objects/items/weapons/circuitboards/machinery/mining_drill.dm
index a625fb1edaf..6835b272aa6 100644
--- a/code/game/objects/items/weapons/circuitboards/machinery/mining_drill.dm
+++ b/code/game/objects/items/weapons/circuitboards/machinery/mining_drill.dm
@@ -1,21 +1,15 @@
/obj/item/stock_parts/circuitboard/miningdrill
name = "circuitboard (mining drill head)"
- build_path = /obj/machinery/mining/drill
+ build_path = /obj/machinery/mining_drill
board_type = "machine"
origin_tech = @'{"programming":1,"engineering":1}'
req_components = list(
/obj/item/stock_parts/capacitor = 1,
/obj/item/stock_parts/matter_bin = 1,
- /obj/item/stock_parts/micro_laser = 1)
+ /obj/item/stock_parts/micro_laser = 1,
+ /obj/item/stock_parts/scanning_module = 1
+ )
additional_spawn_components = list(
/obj/item/stock_parts/power/battery/buildable/stock,
/obj/item/cell = 1
)
-
-/obj/item/stock_parts/circuitboard/miningdrillbrace
- name = "circuitboard (mining drill brace)"
- build_path = /obj/machinery/mining/brace
- board_type = "machine"
- origin_tech = @'{"programming":1,"engineering":1}'
- req_components = list()
- additional_spawn_components = null
\ No newline at end of file
diff --git a/code/game/turfs/flooring/flooring_reinforced.dm b/code/game/turfs/flooring/flooring_reinforced.dm
index 88756a989e3..826db236938 100644
--- a/code/game/turfs/flooring/flooring_reinforced.dm
+++ b/code/game/turfs/flooring/flooring_reinforced.dm
@@ -15,22 +15,28 @@
gender = NEUTER
/decl/flooring/reinforced/circuit
- name = "processing strata"
- desc = "A complex network of circuits beneath reinforced glass."
- icon = 'icons/turf/flooring/circuit.dmi'
- icon_base = "bcircuit"
- build_type = null
- flooring_flags = TURF_ACID_IMMUNE | TURF_CAN_BREAK | TURF_REMOVE_WRENCH
- can_paint = 1
- can_engrave = FALSE
+ name = "processing strata"
+ desc = "A complex network of circuits beneath reinforced glass."
+ icon = 'icons/turf/flooring/circuit.dmi'
+ icon_base = "bcircuit"
+ build_type = null
+ flooring_flags = TURF_ACID_IMMUNE | TURF_CAN_BREAK | TURF_REMOVE_WRENCH
+ can_paint = 1
+ can_engrave = FALSE
+ turf_light_range = 2
+ turf_light_power = 3
+ turf_light_color = COLOR_BLUE
/decl/flooring/reinforced/circuit/green
- icon_base = "gcircuit"
+ icon_base = "gcircuit"
+ turf_light_color = COLOR_GREEN
/decl/flooring/reinforced/circuit/red
- icon_base = "rcircuit"
- flooring_flags = TURF_ACID_IMMUNE
- can_paint = 0
+ icon_base = "rcircuit"
+ flooring_flags = TURF_ACID_IMMUNE
+ can_paint = 0
+ turf_light_power = 2
+ turf_light_color = COLOR_RED
/decl/flooring/reinforced/shuttle
name = "floor"
diff --git a/code/game/turfs/floors/subtypes/floor_circuit.dm b/code/game/turfs/floors/subtypes/floor_circuit.dm
index 7f934481deb..5d458a43d9a 100644
--- a/code/game/turfs/floors/subtypes/floor_circuit.dm
+++ b/code/game/turfs/floors/subtypes/floor_circuit.dm
@@ -3,9 +3,6 @@
icon = 'icons/turf/flooring/circuit.dmi'
icon_state = "bcircuit"
_flooring = /decl/flooring/reinforced/circuit
- light_range = 2
- light_power = 3
- light_color = COLOR_BLUE
/turf/floor/bluegrid/airless
name = "airless floor"
@@ -17,9 +14,6 @@
icon = 'icons/turf/flooring/circuit.dmi'
icon_state = "gcircuit"
_flooring = /decl/flooring/reinforced/circuit/green
- light_range = 2
- light_power = 3
- light_color = COLOR_GREEN
/turf/floor/greengrid/airless
name = "airless floor"
@@ -34,6 +28,3 @@
icon = 'icons/turf/flooring/circuit.dmi'
icon_state = "rcircuit"
_flooring = /decl/flooring/reinforced/circuit/red
- light_range = 2
- light_power = 2
- light_color = COLOR_RED
diff --git a/code/modules/ZAS/Turf.dm b/code/modules/ZAS/Turf.dm
index e0a7767b212..82312858622 100644
--- a/code/modules/ZAS/Turf.dm
+++ b/code/modules/ZAS/Turf.dm
@@ -231,6 +231,10 @@ var/global/list/STANDARD_AIRMIX = list(
/turf/return_air()
RETURN_TYPE(/datum/gas_mixture)
+ // TODO: immutable gas mixtures for stuff like this, to avoid creating new datums every time.
+ if(!simulated)
+ return make_air()
+
// ZAS participation
if(zone && !zone.invalid)
SSair.mark_zone_update(zone)
@@ -261,7 +265,7 @@ var/global/list/STANDARD_AIRMIX = list(
return FALSE
/turf/proc/make_air()
- air = new/datum/gas_mixture
+ air = new /datum/gas_mixture
air.temperature = temperature
if(initial_gas)
if(initial_gas == GAS_STANDARD_AIRMIX)
diff --git a/code/modules/codex/entries/machinery.dm b/code/modules/codex/entries/machinery.dm
index 6854c68862d..62f34de3efe 100644
--- a/code/modules/codex/entries/machinery.dm
+++ b/code/modules/codex/entries/machinery.dm
@@ -150,4 +150,16 @@
lore_text = "A signal repeater, capable of transmitting and decoding hyperintense radio waves to and from PLEXUS uplinks."
mechanics_text = "Allows for network devices in its sector to connect to and communicate with distant networks over PLEXUS.
Networks requires a modem to utilize PLEXUS connections."
disambiguator = "machine"
+ available_to_map_tech_level = MAP_TECH_LEVEL_SPACE
+
+/datum/codex_entry/mining_drill
+ associated_paths = list(/obj/machinery/mining_drill)
+ mechanics_text = "When properly supported by two adjacent braces, the mining drill can automatically mine underground mineral deposits.
\
+ You can empty the ore storage by click-dragging the drill onto an ore box, or using the Unload Drill verb.
\
+ The drill head can be upgraded using a number of different components:
\
+ - Micro lasers control the drill's mining speed. The drill's energy usage proportionally increases with faster speed.
\
+ - Matter bins expand the drill's internal ore storage, allowing it to mine for longer before it gets fill.
\
+ - Scanning modules expand the drill's ore scanner radius, allowing it to mine from farther away.
\
+ - Capacitors improve the drill's energy efficiency, reducing how much energy is required to extract a piece of ore from the ground."
+ disambiguator = "machine"
available_to_map_tech_level = MAP_TECH_LEVEL_SPACE
\ No newline at end of file
diff --git a/code/modules/crafting/stack_recipes/recipes_steel.dm b/code/modules/crafting/stack_recipes/recipes_steel.dm
index 8175e180629..8f15300c367 100644
--- a/code/modules/crafting/stack_recipes/recipes_steel.dm
+++ b/code/modules/crafting/stack_recipes/recipes_steel.dm
@@ -105,3 +105,6 @@
/decl/stack_recipe/steel/furniture/tank
result_type = /obj/item/pipe/tank
+
+/decl/stack_recipe/steel/furniture/drill_brace
+ result_type = /obj/structure/drill_brace
diff --git a/code/modules/fabrication/designs/imprinter/designs_misc_circuits.dm b/code/modules/fabrication/designs/imprinter/designs_misc_circuits.dm
index 2f251c975da..c817352197f 100644
--- a/code/modules/fabrication/designs/imprinter/designs_misc_circuits.dm
+++ b/code/modules/fabrication/designs/imprinter/designs_misc_circuits.dm
@@ -302,9 +302,6 @@
/datum/fabricator_recipe/imprinter/circuit/miningdrill
path = /obj/item/stock_parts/circuitboard/miningdrill
-/datum/fabricator_recipe/imprinter/circuit/miningdrillbrace
- path = /obj/item/stock_parts/circuitboard/miningdrillbrace
-
/datum/fabricator_recipe/imprinter/circuit/floodlight
path = /obj/item/stock_parts/circuitboard/floodlight
diff --git a/code/modules/maps/template_types/random_exoplanet/random_planet_level_data.dm b/code/modules/maps/template_types/random_exoplanet/random_planet_level_data.dm
index 2a5173d788d..26ad0dee2a5 100644
--- a/code/modules/maps/template_types/random_exoplanet/random_planet_level_data.dm
+++ b/code/modules/maps/template_types/random_exoplanet/random_planet_level_data.dm
@@ -82,11 +82,12 @@
//Rename the surface area if we have one yet
adapt_location_name(parent_planetoid.name)
-///If we're getting atmos from our parent planet, decide if we're going to apply it, or ignore it
+///If we're getting atmos from our parent planet, apply it.
/datum/level_data/planetoid/proc/apply_planet_atmosphere(var/datum/planetoid_data/P)
- if(istype(exterior_atmosphere))
- return //level atmos takes priority over planet atmos
- exterior_atmosphere = P.atmosphere.Clone() //Make sure we get one instance per level
+ if(istype(P) && istype(P.atmosphere))
+ exterior_atmosphere = P.atmosphere.Clone()
+ exterior_atmosphere.update_values()
+ exterior_atmosphere.check_tile_graphic()
///Apply our parent planet's ambient lighting settings if we want to.
/datum/level_data/planetoid/proc/apply_planet_ambient_lighting(var/datum/planetoid_data/P)
diff --git a/code/modules/mining/drilling/brace.dm b/code/modules/mining/drilling/brace.dm
new file mode 100644
index 00000000000..44441e2f834
--- /dev/null
+++ b/code/modules/mining/drilling/brace.dm
@@ -0,0 +1,56 @@
+/obj/structure/drill_brace
+ name = "mining drill brace"
+ desc = "A machinery brace for an industrial drill. It looks like it's about half a metre thick."
+ icon = 'icons/obj/mining_drill.dmi'
+ icon_state = "mining_brace"
+ density = TRUE
+ layer = ABOVE_HUMAN_LAYER
+ obj_flags = OBJ_FLAG_ROTATABLE|OBJ_FLAG_ANCHORABLE
+ var/obj/machinery/mining_drill/connected = null
+
+/obj/structure/drill_brace/Destroy()
+ if(connected)
+ disconnect_from_drill()
+ return ..()
+
+/obj/structure/drill_brace/on_update_icon()
+ icon_state = "mining_brace[connected ? "_active" : ""]"
+ return ..()
+
+/obj/structure/drill_brace/wrench_floor_bolts(mob/user, delay, obj/item/tool)
+ if(connected && connected.use_power != POWER_USE_OFF)
+ to_chat(user, SPAN_NOTICE("You can't work with the brace of a running drill!"))
+ return
+ if(isspaceturf(get_turf(src)))
+ to_chat(user, SPAN_NOTICE("You can't anchor something to empty space. Idiot."))
+ return
+
+ var/old_anchored = anchored
+ ..() // Call parent to try to actually anchor/unanchor it.
+ if(anchored != old_anchored)
+ if(anchored && connect_to_drill())
+ to_chat(user, SPAN_NOTICE("You attach \the [src] to \the [connected]."))
+ else if(disconnect_from_drill())
+ to_chat(user, SPAN_NOTICE("You detatch \the [src]."))
+
+/obj/structure/drill_brace/proc/connect_to_drill()
+ var/turf/front_turf = get_step(get_turf(src), dir)
+ if(!istype(front_turf))
+ return FALSE
+ var/obj/machinery/mining_drill/drill = locate(/obj/machinery/mining_drill) in front_turf
+ if(drill)
+ connected = drill
+ connected.supports += src
+ connected.handle_supports()
+ update_icon()
+ return TRUE
+ return FALSE
+
+/obj/structure/drill_brace/proc/disconnect_from_drill()
+ if(!connected)
+ return FALSE
+ connected.supports -= src
+ connected.handle_supports()
+ connected = null
+ update_icon()
+ return TRUE
diff --git a/code/modules/mining/drilling/drill.dm b/code/modules/mining/drilling/drill.dm
index fea0961004f..7dd751cd744 100644
--- a/code/modules/mining/drilling/drill.dm
+++ b/code/modules/mining/drilling/drill.dm
@@ -1,299 +1,203 @@
-/obj/machinery/mining
+/obj/machinery/mining_drill
+ name = "mining drill head"
+ desc = "An enormous drill."
icon = 'icons/obj/mining_drill.dmi'
+ icon_state = "mining_drill_off"
+ layer = ABOVE_HUMAN_LAYER
anchored = FALSE
- use_power = POWER_USE_OFF //The drill takes power directly from a cell.
density = TRUE
- layer = ABOVE_HUMAN_LAYER //So it draws over mobs in the tile north of it.
+ use_power = POWER_USE_OFF
+ power_channel = LOCAL
+ active_power_usage = 10 KILOWATTS
+ idle_power_usage = 500
construct_state = /decl/machine_construction/default/panel_closed
uncreated_component_parts = null
stat_immune = 0
+ base_type = /obj/machinery/mining_drill
-/obj/machinery/mining/drill
- name = "mining drill head"
- desc = "An enormous drill."
- icon_state = "mining_drill"
- power_channel = LOCAL
- active_power_usage = 10 KILOWATTS
- base_type = /obj/machinery/mining/drill
- var/list/generated_ore = list()
- var/braces_needed = 2
- var/list/supports = list()
- var/supported = 0
- var/active = FALSE
- var/list/resource_field = list()
-
- //Upgrades
- var/harvest_speed
- var/capacity
-
- //Flags
- var/need_update_field = 0
- var/need_player_check = 0
+ /// The drill's FSM, keeping track of which state the drill is currently in.
+ var/datum/state_machine/drill/state_machine = null
-/obj/machinery/mining/drill/Process()
- if(need_player_check)
- return
-
- check_supports()
+ /// Ore that is presently inside of the drill, ready to be extracted.
+ var/list/contained_ore = list()
- if(!active) return
-
- if(!anchored)
- system_error("system configuration error")
- return
-
- if(stat & NOPOWER)
- system_error("insufficient charge")
- return
+ /// Drill supports presently connected to the drill head.
+ var/list/supports = list()
- if(need_update_field)
- get_resource_field()
+ /// How many braces are required for the drill to operate.
+ var/const/MINIMUM_SUPPORT_NUMBER = 2
- if(world.time % 10 == 0)
- update_icon()
+ /// List of turfs that the drill will attempt to mine.
+ var/list/turfs_to_mine = list()
- if(!active)
- return
+ /// The turf that the drill is presently mining.
+ var/turf/current_turf = null
- //Drill through the flooring, if any.
- var/turf/T = get_turf(src)
- if(T)
- T.drill_act()
-
- while(length(resource_field))
- var/turf/harvesting = pick(resource_field)
- var/datum/extension/buried_resources/resources = get_extension(harvesting, /datum/extension/buried_resources)
- if(!length(resources?.resources))
- if(resources)
- remove_extension(harvesting, /datum/extension/buried_resources)
- resource_field -= harvesting
- continue
- break
-
- if(!length(resource_field))
- set_active(FALSE)
- need_player_check = 1
- update_icon()
- return
+ //Upgrades
+ /// The radius for the drill to use when populating `turfs_to_mine`. Upgraded with scanning modules.
+ var/drill_radius = 2
- var/turf/harvesting = pick(resource_field)
- var/datum/extension/buried_resources/resources = get_extension(harvesting, /datum/extension/buried_resources)
- var/harvested = 0
- for(var/metal in resources.resources)
+ /// The ore capacity for the drill. The drill will stop mining if it gets full. Upgraded with matter bins.
+ var/ore_capacity = 200
- if(length(generated_ore) >= capacity)
- system_error("insufficient storage space")
- set_active(FALSE)
- need_player_check = 1
- update_icon()
- return
+ /// How fast the drill mines out the ore contained within `turfs_to_mine`. Faster speed requires more power. Upgraded with micro lasers.
+ var/mining_speed = 1
- var/generating_ore = min(capacity - length(generated_ore), resources.resources[metal])
- resources.resources[metal] -= generating_ore
- if(resources.resources[metal] <= 0)
- resources.resources -= metal
-
- for(var/i=1, i <= generating_ore, i++)
- harvested++
- if(harvested >= harvest_speed)
- break
- generated_ore += new /obj/item/stack/material/ore(src, metal)
- if(harvested >= harvest_speed)
- break
+ /// Modifies how much energy is required to extract one piece of ore, with diminishing returns for higher values. Upgraded with capacitors.
+ var/efficiency_rating = 1
- if(!length(resources.resources))
- remove_extension(harvesting, /datum/extension/buried_resources)
- resource_field -= harvesting
+ /// Determines how much less energy each capacitor rating reduces. Every capacitor after the first reduces the power draw by this amount each time.
+ var/const/EFFICIENCY_EXPONENT = 0.8 // Raise this closer to 1 to make capacitors less powerful.
-/obj/machinery/mining/drill/proc/set_active(var/new_active)
- if(active != new_active)
- active = new_active
- update_use_power(active ? POWER_USE_ACTIVE : POWER_USE_OFF)
+/obj/machinery/mining_drill/Initialize()
+ state_machine = add_state_machine(src, /datum/state_machine/drill)
+ return ..()
-/obj/machinery/mining/drill/cannot_transition_to(state_path)
- if(active)
- return SPAN_NOTICE("You must turn \the [src] off first.")
+/obj/machinery/mining_drill/Destroy()
+ remove_state_machine(src, /datum/state_machine/drill)
+ turfs_to_mine.Cut()
+ current_turf = null
+ QDEL_NULL_LIST(contained_ore)
+ for(var/thing in supports)
+ var/obj/structure/drill_brace/B = thing
+ B.disconnect_from_drill()
return ..()
-/obj/machinery/mining/drill/components_are_accessible(path)
- return !active && ..()
-
-/obj/machinery/mining/drill/physical_attack_hand(mob/user)
- check_supports()
- if(need_player_check)
- if(can_use_power_oneoff(10 KILOWATTS) > 0)
- system_error("insufficient charge")
- else if(anchored)
- get_resource_field()
- to_chat(user, "You hit the manual override and reset the drill's error checking.")
- need_player_check = 0
- update_icon()
- return TRUE
- if(supported && !panel_open)
- if(!(stat & NOPOWER))
- set_active(!active)
- if(active)
- visible_message("\The [src] lurches downwards, grinding noisily.")
- need_update_field = 1
- else
- visible_message("\The [src] shudders to a grinding halt.")
+/obj/machinery/mining_drill/Process()
+ state_machine.evaluate()
+ var/decl/state/drill/current_state = state_machine.current_state
+ current_state.process(src)
+
+/obj/machinery/mining_drill/physical_attack_hand(mob/user)
+ if(!panel_open)
+ var/on = use_power ? TRUE : FALSE
+ on = !on
+ if(on)
+ update_use_power(POWER_USE_IDLE)
else
- to_chat(user, "The drill is unpowered.")
+ update_use_power(POWER_USE_OFF)
+ playsound(src, "button", 60)
+ to_chat(user, SPAN_NOTICE("You turn \the [src] [use_power ? "on" : "off"]."))
+ state_machine.evaluate()
+
+/obj/machinery/mining_drill/on_update_icon()
+ icon_state = "mining_drill_[use_power == POWER_USE_ACTIVE ? "on" : "off"]"
+ z_flags &= ~ZMM_MANGLE_PLANES
+ cut_overlays()
+ var/decl/state/drill/current_state = state_machine.current_state
+ if(current_state.light_icon_state)
+ add_overlay(emissive_overlay(icon, current_state.light_icon_state, src, SOUTH, current_state.light_color))
+ z_flags |= ZMM_MANGLE_PLANES
+ set_light(2, 0.4, current_state.light_color)
else
- to_chat(user, "Turning on a piece of industrial machinery without sufficient bracing or wires exposed is a bad idea.")
-
- update_icon()
- return TRUE
-
-/obj/machinery/mining/drill/on_update_icon()
- if(need_player_check)
- icon_state = "mining_drill_error"
- else if(active)
- var/status = clamp(round( (length(generated_ore) / capacity) * 4 ), 0, 3)
- icon_state = "mining_drill_active[status]"
- else if(supported)
- icon_state = "mining_drill_braced"
- else
- icon_state = "mining_drill"
- return
+ set_light(0)
+ return ..()
-/obj/machinery/mining/drill/RefreshParts()
- ..()
- harvest_speed = clamp(total_component_rating_of_type(/obj/item/stock_parts/micro_laser), 0, 10)
- capacity = 200 * clamp(total_component_rating_of_type(/obj/item/stock_parts/matter_bin), 0, 10)
- var/charge_multiplier = clamp(total_component_rating_of_type(/obj/item/stock_parts/capacitor), 0.1, 10)
- change_power_consumption(initial(active_power_usage) / charge_multiplier, POWER_USE_ACTIVE)
+/obj/machinery/mining_drill/proc/handle_supports()
+ state_machine.evaluate()
+ anchored = length(supports) >= 1 ? TRUE : FALSE
+ if(can_fall())
+ fall()
-/obj/machinery/mining/drill/proc/check_supports()
- anchored = initial(anchored)
- if(length(supports) <= 0)
- set_active(FALSE)
- else
- anchored = TRUE
+/obj/machinery/mining_drill/proc/reset_drill()
+ turfs_to_mine.Cut()
+ current_turf = null
- var/last_supported = supported
- supported = (length(supports) >= braces_needed)
- if(supported != last_supported && !supported && can_fall())
- fall()
+/obj/machinery/mining_drill/cannot_transition_to(state_path)
+ if(use_power != POWER_USE_OFF)
+ return SPAN_NOTICE("You must turn \the [src] off first.")
+ return ..()
- update_icon()
+/obj/machinery/mining_drill/components_are_accessible(path)
+ return (use_power == POWER_USE_OFF) && ..()
-/obj/machinery/mining/drill/can_fall()
- . = (length(supports) <= 0)
-/obj/machinery/mining/drill/proc/system_error(var/error)
+/obj/machinery/mining_drill/RefreshParts()
+ . = ..()
+ drill_radius = 1 + total_component_rating_of_type(/obj/item/stock_parts/scanning_module)
+ ore_capacity = 200 * total_component_rating_of_type(/obj/item/stock_parts/matter_bin)
+ mining_speed = total_component_rating_of_type(/obj/item/stock_parts/micro_laser)
+ efficiency_rating = total_component_rating_of_type(/obj/item/stock_parts/capacitor)
- if(error)
- src.visible_message("\The [src] flashes a '[error]' warning.")
- need_player_check = 1
- set_active(FALSE)
- update_icon()
+ var/efficiency = EFFICIENCY_EXPONENT ** (efficiency_rating - 1)
+ change_power_consumption(initial(active_power_usage) * efficiency * mining_speed, POWER_USE_ACTIVE)
-/obj/machinery/mining/drill/proc/get_resource_field()
+/obj/machinery/mining_drill/proc/populate_turfs_to_mine()
+ turfs_to_mine.Cut()
+ var/list/turf_candidates = RANGE_TURFS(src, drill_radius)
+ for(var/thing in turf_candidates)
+ var/turf/T = thing
+ if(turf_has_ore(T))
+ turfs_to_mine += T
- resource_field = list()
- need_update_field = 0
+/obj/machinery/mining_drill/proc/scan_visuals()
+ for(var/thing in RANGE_TURFS(src, drill_radius))
+ var/turf/T = thing
+ var/delay = (get_dist(get_turf(src), T) + 1) * 3
+ addtimer(CALLBACK(src, PROC_REF(scan_visual_tile), T), delay)
- var/turf/T = get_turf(src)
- if(!istype(T)) return
- var/tx = T.x - 2
- var/ty = T.y - 2
- var/turf/mine_turf
- for(var/iy = 0,iy < 5, iy++)
- for(var/ix = 0, ix < 5, ix++)
- mine_turf = locate(tx + ix, ty + iy, T.z)
- if(mine_turf && has_extension(mine_turf, /datum/extension/buried_resources))
- resource_field += mine_turf
+/obj/machinery/mining_drill/proc/scan_visual_tile(turf/T)
+ var/obj/effect/temporary/temp = new(T, 1 SECOND, 'icons/effects/effects.dmi', "sonar_ping")
+ temp.color = "#00ffff77"
- if(!resource_field.len)
- system_error("resources depleted")
+/obj/machinery/mining_drill/proc/turf_has_ore(turf/T)
+ if(!istype(T) || !has_extension(T, /datum/extension/buried_resources))
+ return FALSE
+ var/datum/extension/buried_resources/resources = get_extension(T, /datum/extension/buried_resources)
+ return length(resources?.resources)
-/obj/machinery/mining/drill/verb/unload()
+/obj/machinery/mining_drill/proc/mine_ore(turf/T)
+ if(!T)
+ return
+ // Was tempted to add a drilling sound but it was awful.
+ var/datum/extension/buried_resources/resources = get_extension(T, /datum/extension/buried_resources)
+ for(var/i in 1 to mining_speed)
+ if(!length(resources.resources))
+ break
+ var/material_typepath = pick(resources.resources)
+ contained_ore += new /obj/item/stack/material/ore(src, 1, material_typepath)
+ resources.resources[material_typepath] -= 1
+ if(resources.resources[material_typepath] <= 0)
+ // Remove the typepath if it ran out.
+ resources.resources -= material_typepath
+
+/obj/machinery/mining_drill/proc/deplete_turf(turf/T)
+ if(!turf_has_ore(T))
+ if(istype(T))
+ turfs_to_mine -= T
+ if(has_extension(T, /datum/extension/buried_resources))
+ remove_extension(T, /datum/extension/buried_resources)
+
+/obj/machinery/mining_drill/proc/choose_turf_to_mine()
+ current_turf = turfs_to_mine[1]
+
+/obj/machinery/mining_drill/verb/unload_verb()
set name = "Unload Drill"
set category = "Object"
set src in oview(1)
- if(usr.stat) return
-
var/obj/structure/ore_box/B = locate() in orange(1)
if(B)
- B.insert_ores(generated_ore, usr)
- generated_ore.Cut()
- to_chat(usr, "You unload the drill's storage cache into the ore box.")
- else
- to_chat(usr, "You must move an ore box up to the drill before you can unload it.")
-
-
-/obj/machinery/mining/brace
- name = "mining drill brace"
- desc = "A machinery brace for an industrial drill. It looks easily two feet thick."
- icon_state = "mining_brace"
- obj_flags = OBJ_FLAG_ROTATABLE
- interact_offline = 1
+ unload_into_box(B, usr)
- var/obj/machinery/mining/drill/connected
-
-/obj/machinery/mining/brace/cannot_transition_to(state_path)
- if(connected && connected.active)
- return SPAN_NOTICE("You can't work with the brace of a running drill!")
- return ..()
-
-/obj/machinery/mining/brace/attackby(obj/item/W, mob/user)
- if(connected && connected.active)
- to_chat(user, "You can't work with the brace of a running drill!")
- return TRUE
- if(component_attackby(W, user))
- return TRUE
- if(IS_WRENCH(W))
-
- if(isspaceturf(get_turf(src)))
- to_chat(user, "You can't anchor something to empty space. Idiot.")
- return
-
- playsound(src.loc, 'sound/items/Ratchet.ogg', 100, 1)
- to_chat(user, "You [anchored ? "un" : ""]anchor the brace.")
-
- anchored = !anchored
- if(anchored)
- connect()
- else
- disconnect()
-
-/obj/machinery/mining/brace/proc/connect()
-
- var/turf/T = get_step(get_turf(src), src.dir)
-
- for(var/thing in T.contents)
- if(istype(thing, /obj/machinery/mining/drill))
- connected = thing
- break
-
- if(!connected)
+/obj/machinery/mining_drill/proc/unload_into_box(obj/structure/ore_box/box, mob/user)
+ if(!CanPhysicallyInteract(user))
return
- if(!connected.supports)
- connected.supports = list()
-
- icon_state = "mining_brace_active"
-
- connected.supports += src
- connected.check_supports()
-
-/obj/machinery/mining/brace/proc/disconnect()
-
- if(!connected) return
-
- if(!connected.supports) connected.supports = list()
-
- icon_state = "mining_brace"
-
- connected.supports -= src
- connected.check_supports()
- connected = null
-
-/obj/machinery/mining/brace/dismantle()
- if(connected)
- disconnect()
- ..()
\ No newline at end of file
+ if(box?.Adjacent(src))
+ if(!length(contained_ore))
+ to_chat(user, SPAN_NOTICE("\The [src]'s storage cache is empty."))
+ return
+ box.insert_ores(contained_ore, user)
+ contained_ore.Cut()
+ playsound(src, 'sound/machines/vending_machine.ogg', 60, 1)
+ playsound(box, 'sound/effects/rockcrumble.ogg', 60, 1)
+ visible_message(
+ SPAN_NOTICE("\The [user] unloads \the [src]'s storage cache into \the [box]."),
+ SPAN_NOTICE("You unload \the [src]'s storage cache into \the [box]."),
+ SPAN_NOTICE("You hear rocks falling into a container.")
+ )
+ else
+ to_chat(user, SPAN_NOTICE("You must move an ore box up to \the [src] before you can unload it."))
diff --git a/code/modules/mining/drilling/drill_fsm.dm b/code/modules/mining/drilling/drill_fsm.dm
new file mode 100644
index 00000000000..de129e235ed
--- /dev/null
+++ b/code/modules/mining/drilling/drill_fsm.dm
@@ -0,0 +1,212 @@
+/datum/state_machine/drill
+ current_state = /decl/state/drill/unpowered
+ expected_type = /obj/machinery/mining_drill
+ base_type = /datum/state_machine/drill
+
+
+/decl/state/drill
+ var/light_color = null
+ var/light_icon_state = "blink_slow"
+ var/entered_sound = null
+ var/exited_sound = null
+ var/power_usage = POWER_USE_IDLE
+
+/decl/state/drill/entered_state(obj/machinery/mining_drill/drill)
+ drill.queue_icon_update()
+ if(entered_sound)
+ playsound(drill, entered_sound, 40, FALSE)
+ drill.update_use_power(power_usage)
+
+/decl/state/drill/exited_state(obj/machinery/mining_drill/drill)
+ if(exited_sound)
+ playsound(drill, exited_sound, 40, FALSE)
+
+/decl/state/drill/proc/process(obj/machinery/mining_drill/drill)
+
+
+/decl/state_transition/drill/is_open(obj/machinery/mining_drill/drill)
+ return drill.operable()
+
+
+/// Unpowered state. Occurs when the battery dies or when turned off.
+/decl/state/drill/unpowered
+ light_color = "#00000000"
+ power_usage = POWER_USE_OFF
+ light_icon_state = null
+ entered_sound = 'sound/mecha/mech-shutdown.ogg'
+ exited_sound = 'sound/mecha/powerup.ogg'
+ transitions = list(
+ /decl/state_transition/drill/recover_from_unpowered
+ )
+
+/decl/state_transition/drill/unpowered
+ target = /decl/state/drill/unpowered
+
+/decl/state_transition/drill/unpowered/is_open(obj/machinery/mining_drill/drill)
+ return drill.inoperable() || drill.use_power == POWER_USE_OFF
+
+
+/decl/state_transition/drill/recover_from_unpowered
+ target = /decl/state/drill/idle
+
+/decl/state_transition/drill/recover_from_unpowered/is_open(obj/machinery/mining_drill/drill)
+ return drill.operable() && drill.use_power != POWER_USE_OFF
+
+
+/// Starting state for drills that are turned on or recovered from an issue.
+/decl/state/drill/idle
+ light_color = "#ffffff"
+ transitions = list(
+ /decl/state_transition/drill/unpowered,
+ /decl/state_transition/drill/error,
+ /decl/state_transition/drill/storage_full,
+ /decl/state_transition/drill/scanning,
+ /decl/state_transition/drill/switching_target,
+ /decl/state_transition/drill/mining,
+ /decl/state_transition/drill/finished
+ )
+
+/decl/state/drill/idle/entered_state(obj/machinery/mining_drill/drill)
+ . = ..()
+ drill.reset_drill()
+
+
+/// State that occurs if there is a problem with the drill setup, such as lacking braces.
+/decl/state/drill/error
+ light_color = "#ff0000"
+ entered_sound = 'sound/machines/buzz-sigh.ogg'
+ transitions = list(
+ /decl/state_transition/drill/unpowered,
+ /decl/state_transition/drill/recover_from_error
+ )
+
+/decl/state_transition/drill/error
+ target = /decl/state/drill/error
+
+/decl/state_transition/drill/error/is_open(obj/machinery/mining_drill/drill)
+ return ..() && length(drill.supports) < drill.MINIMUM_SUPPORT_NUMBER
+
+
+/decl/state_transition/drill/recover_from_error
+ target = /decl/state/drill/idle
+
+/decl/state_transition/drill/recover_from_error/is_open(obj/machinery/mining_drill/drill)
+ return ..() && length(drill.supports) >= drill.MINIMUM_SUPPORT_NUMBER
+
+
+/// State that follows the starting state, where it determines which turfs to mine, and gives a visual effect of it scanning the surrounding ground.
+/decl/state/drill/scanning
+ light_color = "#00ffff"
+ entered_sound = 'sound/effects/scanbeep.ogg'
+ transitions = list(
+ /decl/state_transition/drill/unpowered,
+ /decl/state_transition/drill/error,
+ /decl/state_transition/drill/switching_target,
+ /decl/state_transition/drill/finished
+ )
+
+/decl/state/drill/scanning/process(obj/machinery/mining_drill/drill)
+ drill.populate_turfs_to_mine()
+ drill.scan_visuals()
+
+
+/decl/state_transition/drill/scanning
+ target = /decl/state/drill/scanning
+
+/decl/state_transition/drill/scanning/is_open(obj/machinery/mining_drill/drill)
+ return ..() && !length(drill.turfs_to_mine)
+
+
+/// State where the drill is actively mining a specific turf.
+/decl/state/drill/mining
+ light_color = "#00ff00"
+ light_icon_state = "blink_fast"
+ power_usage = POWER_USE_ACTIVE
+ transitions = list(
+ /decl/state_transition/drill/unpowered,
+ /decl/state_transition/drill/error,
+ /decl/state_transition/drill/storage_full,
+ /decl/state_transition/drill/switching_target,
+ /decl/state_transition/drill/finished
+ )
+
+/decl/state/drill/mining/process(obj/machinery/mining_drill/drill)
+ drill.mine_ore(drill.current_turf)
+
+/decl/state_transition/drill/mining
+ target = /decl/state/drill/mining
+
+/decl/state_transition/drill/mining/is_open(obj/machinery/mining_drill/drill)
+ return ..() && length(drill.turfs_to_mine) && drill.current_turf
+
+
+/// State which occurs when the currently mined turf is depleted, and there is another turf to mine from,
+/// thus the drill visually targets the next spot and provides some feedback to the player on how fast the mining is going.
+/decl/state/drill/switching_target
+ light_color = "#008800"
+ light_icon_state = "blink_fast"
+ power_usage = POWER_USE_IDLE
+ entered_sound = 'sound/machines/airlock_open_force.ogg'
+ exited_sound = 'sound/machines/airlock_close_force.ogg'
+ transitions = list(
+ /decl/state_transition/drill/unpowered,
+ /decl/state_transition/drill/error,
+ /decl/state_transition/drill/mining,
+ /decl/state_transition/drill/finished
+ )
+
+/decl/state/drill/switching_target/process(obj/machinery/mining_drill/drill)
+ if(!drill.turf_has_ore(drill.current_turf))
+ if(istype(drill.current_turf))
+ drill.turfs_to_mine -= drill.current_turf
+ if(has_extension(drill.current_turf, /datum/extension/buried_resources))
+ remove_extension(drill.current_turf, /datum/extension/buried_resources)
+ if(length(drill.turfs_to_mine))
+ drill.current_turf = drill.turfs_to_mine[1]
+ else
+ drill.current_turf = null
+
+/decl/state_transition/drill/switching_target
+ target = /decl/state/drill/switching_target
+
+/decl/state_transition/drill/switching_target/is_open(obj/machinery/mining_drill/drill)
+ return ..() && length(drill.turfs_to_mine) && !drill.turf_has_ore(drill.current_turf)
+
+
+/// State which occurs when the ore storage is full, and the player needs to unload the ore for it to resume mining.
+/decl/state/drill/storage_full
+ light_color = "#ffff00"
+ entered_sound = 'sound/machines/buzz-two.ogg'
+ transitions = list(
+ /decl/state_transition/drill/unpowered,
+ /decl/state_transition/drill/error,
+ /decl/state_transition/drill/recover_from_storage_full
+ )
+
+/decl/state_transition/drill/storage_full
+ target = /decl/state/drill/storage_full
+
+/decl/state_transition/drill/storage_full/is_open(obj/machinery/mining_drill/drill)
+ return ..() && length(drill.contained_ore) >= drill.ore_capacity
+
+/decl/state_transition/drill/recover_from_storage_full
+ target = /decl/state/drill/idle
+
+/decl/state_transition/drill/recover_from_storage_full/is_open(obj/machinery/mining_drill/drill)
+ return ..() && length(drill.contained_ore) < drill.ore_capacity
+
+
+/// State which occurs when there is no more ore to mine from the surrounding tiles.
+/decl/state/drill/finished
+ light_color = "#0000ff"
+ entered_sound = 'sound/machines/ping.ogg'
+ transitions = list(
+ /decl/state_transition/drill/unpowered,
+ /decl/state_transition/drill/error
+ )
+
+/decl/state_transition/drill/finished
+ target = /decl/state/drill/finished
+
+/decl/state_transition/drill/finished/is_open(obj/machinery/mining_drill/drill)
+ return ..() && !drill.current_turf && !length(drill.turfs_to_mine)
\ No newline at end of file
diff --git a/code/modules/mining/ore_box.dm b/code/modules/mining/ore_box.dm
index 7c0adf71070..7dff5769a88 100644
--- a/code/modules/mining/ore_box.dm
+++ b/code/modules/mining/ore_box.dm
@@ -134,6 +134,13 @@
if(. && !QDELETED(src) && (severity == 1 || prob(50)))
physically_destroyed()
+/obj/structure/ore_box/receive_mouse_drop(atom/dropping, mob/user, params)
+ . = ..()
+ if(!. && istype(dropping, /obj/machinery/mining_drill))
+ var/obj/machinery/mining_drill/D = dropping
+ D.unload_into_box(src, user)
+
+
/obj/structure/ore_box/get_alt_interactions(mob/user)
. = ..()
LAZYADD(., /decl/interaction_handler/empty/ore_box)
diff --git a/code/modules/reagents/Chemistry-Holder.dm b/code/modules/reagents/Chemistry-Holder.dm
index 16fd08aed18..4357110ccbb 100644
--- a/code/modules/reagents/Chemistry-Holder.dm
+++ b/code/modules/reagents/Chemistry-Holder.dm
@@ -761,7 +761,7 @@ var/global/datum/reagents/sink/infinite_reagent_sink = new
if(transferred_phases & MAT_PHASE_SOLID)
var/solid_transferred = NONUNIT_FLOOR(min(amount_remaining, SOLID_VOLUME(src, type)), MINIMUM_CHEMICAL_VOLUME)
F.add_reagent(type, solid_transferred, REAGENT_DATA(src, type), defer_update = TRUE, phase = MAT_PHASE_SOLID)
- remove_reagent(type, solid_transferred, defer_update = TRUE, removed_phases = MAT_PHASE_LIQUID)
+ remove_reagent(type, solid_transferred, defer_update = TRUE, removed_phases = MAT_PHASE_SOLID)
amount_remaining -= solid_transferred
// Now that both liquid and solid components are removed, we can update if necessary.
diff --git a/html/changelog.html b/html/changelog.html
index 3fa1b06d356..b652d7d7973 100644
--- a/html/changelog.html
+++ b/html/changelog.html
@@ -52,6 +52,14 @@
-->