diff --git a/code/__DEFINES/dcs/signals.dm b/code/__DEFINES/dcs/signals.dm
index d19d1f7e9633..2048d0a178a7 100644
--- a/code/__DEFINES/dcs/signals.dm
+++ b/code/__DEFINES/dcs/signals.dm
@@ -727,6 +727,10 @@
/// From overmap Undock(): (datum/overmap)
#define COMSIG_OVERMAP_UNDOCK "overmap_undock"
+// /datum/component/spawner signals
+// Called by parent when pausing spawning, returns bool: (datum/source, spawning_started)
+#define COMSIG_SPAWNER_TOGGLE_SPAWNING "spawner_toggle"
+
///Beam Signals
/// Called before beam is redrawn
#define COMSIG_BEAM_BEFORE_DRAW "beam_before_draw"
diff --git a/code/__DEFINES/stock_parts.dm b/code/__DEFINES/stock_parts.dm
new file mode 100644
index 000000000000..d4ecb54a86a3
--- /dev/null
+++ b/code/__DEFINES/stock_parts.dm
@@ -0,0 +1,6 @@
+//Stock part types (like tool behaviour but for stock parts)
+#define PART_CAPACITOR "capacitor"
+#define PART_SCANNER "scanning module"
+#define PART_MANIPULATOR "manipulator"
+#define PART_LASER "micro-laser"
+#define PART_BIN "matter bin"
diff --git a/code/__HELPERS/game.dm b/code/__HELPERS/game.dm
index b249fede86ed..e7af7f31884d 100644
--- a/code/__HELPERS/game.dm
+++ b/code/__HELPERS/game.dm
@@ -143,6 +143,28 @@ block( \
//turfs += centerturf
return atoms
+/**
+ * Behaves like the orange() proc, but only looks in the outer range of the function (The "peel" of the orange).
+ * Credit to ArcaneMusic for this one
+ */
+/proc/turf_peel(outer_range, inner_range, center, view_based = FALSE)
+ var/list/peel = list()
+ var/list/outer
+ var/list/inner
+ if(view_based)
+ outer = circleviewturfs(center, outer_range)
+ inner = circleviewturfs(center, inner_range)
+ else
+ outer = circlerangeturfs(center, outer_range)
+ inner = circlerangeturfs(center, inner_range)
+ for(var/turf/possible_spawn in outer)
+ if(possible_spawn in inner)
+ continue
+ if(istype(possible_spawn, /turf/closed))
+ continue
+ peel += possible_spawn
+ return peel
+
/proc/get_dist_euclidian(atom/Loc1 as turf|mob|obj,atom/Loc2 as turf|mob|obj)
var/dx = Loc1.x - Loc2.x
var/dy = Loc1.y - Loc2.y
diff --git a/code/datums/components/spawner.dm b/code/datums/components/spawner.dm
index 42456ccf88e9..aab5bb6ea08a 100644
--- a/code/datums/components/spawner.dm
+++ b/code/datums/components/spawner.dm
@@ -7,9 +7,15 @@
var/list/spawn_text = list("emerges from")
var/list/faction = list("mining")
var/list/spawn_sound = list()
+ var/spawn_distance_min = 1
+ var/spawn_distance_max = 1
+ var/wave_length //Average time until break in spawning
+ var/wave_downtime //Average time until spawning starts again
+ var/wave_timer
+ var/current_timerid
-/datum/component/spawner/Initialize(_mob_types, _spawn_time, _faction, _spawn_text, _max_mobs, _spawn_sound)
+/datum/component/spawner/Initialize(_mob_types, _spawn_time, _faction, _spawn_text, _max_mobs, _spawn_sound, _spawn_distance_min, _spawn_distance_max, _wave_length, _wave_downtime)
if(_spawn_time)
spawn_time=_spawn_time
if(_mob_types)
@@ -22,36 +28,99 @@
max_mobs=_max_mobs
if(_spawn_sound)
spawn_sound=_spawn_sound
+ if(_spawn_distance_min)
+ spawn_distance_min=_spawn_distance_min
+ if(_spawn_distance_max)
+ spawn_distance_max=_spawn_distance_max
+ if(_wave_length)
+ wave_length = _wave_length
+ if(_wave_downtime)
+ wave_downtime = _wave_downtime
RegisterSignal(parent, list(COMSIG_PARENT_QDELETING), PROC_REF(stop_spawning))
+ RegisterSignal(parent, list(COMSIG_SPAWNER_TOGGLE_SPAWNING), PROC_REF(toggle_spawning))
START_PROCESSING(SSprocessing, src)
/datum/component/spawner/process()
+ if(!parent) //Sanity check for instances where the spawner may be sleeping while the parent is destroyed
+ qdel(src)
+ return
try_spawn_mob()
-
/datum/component/spawner/proc/stop_spawning(force)
SIGNAL_HANDLER
STOP_PROCESSING(SSprocessing, src)
+ deltimer(current_timerid)
for(var/mob/living/simple_animal/L in spawned_mobs)
if(L.nest == src)
L.nest = null
spawned_mobs = null
+//Different from stop_spawning() as it doesn't untether all mobs from it and is meant for temporarily stopping spawning
+/datum/component/spawner/proc/toggle_spawning(datum/source, spawning_started)
+ SIGNAL_HANDLER
+
+ if(spawning_started)
+ STOP_PROCESSING(SSprocessing, src)
+ deltimer(current_timerid) //Otherwise if spawning is paused while the wave timer is loose it'll just unpause on its own
+ COOLDOWN_RESET(src, wave_timer)
+ return FALSE
+ else
+ START_PROCESSING(SSprocessing, src)
+ return TRUE
+
/datum/component/spawner/proc/try_spawn_mob()
var/atom/P = parent
- if(spawned_mobs.len >= max_mobs)
- return 0
- if(spawn_delay > world.time)
- return 0
- spawn_delay = world.time + spawn_time
- var/chosen_mob_type = pickweight(mob_types)
- var/mob/living/simple_animal/L = new chosen_mob_type(P.loc)
- L.flags_1 |= (P.flags_1 & ADMIN_SPAWNED_1)
- spawned_mobs += L
- L.nest = src
- L.faction = src.faction
- P.visible_message("[L] [pick(spawn_text)] [P].")
- if(length(spawn_sound))
- playsound(P, pick(spawn_sound), 50, TRUE)
+ var/turf/spot = get_turf(P)
+ //Checks for handling the wave-based pausing and unpausing of spawning
+ //Almost certainly a better way to do this, but until then this technically works
+ if(wave_length)
+ if(!wave_timer)
+ COOLDOWN_START(src, wave_timer, wave_length)
+ if(wave_timer && COOLDOWN_FINISHED(src, wave_timer))
+ COOLDOWN_RESET(src, wave_timer)
+ STOP_PROCESSING(SSprocessing, src)
+ current_timerid = addtimer(CALLBACK(src, PROC_REF(toggle_spawning)), wave_downtime, TIMER_STOPPABLE)
+ return
+ ////////////////////////////////
+ if(length(spawned_mobs) >= max_mobs)
+ return
+ if(!COOLDOWN_FINISHED(src, spawn_delay))
+ return
+ COOLDOWN_START(src, spawn_delay, spawn_time)
+ var/spawn_multiplier = 1
+ //Avoid using this with spawners that add this component on initialize
+ //It causes numerous runtime errors during planet generation
+ if(spawn_distance_max > 1)
+ var/player_count = 0
+ for(var/mob/player as anything in GLOB.player_list)
+ if(player.virtual_z() != spot.virtual_z())
+ continue
+ if(!isliving(player))
+ continue
+ if(player.stat != CONSCIOUS)
+ continue
+ if(get_dist(get_turf(player), spot) > spawn_distance_max)
+ continue
+ player_count++
+ if(player_count > 3)
+ spawn_multiplier = round(player_count/2)
+ spawn_multiplier = clamp(spawn_multiplier, 1, max_mobs - length(spawned_mobs))
+ for(var/mob_index in 1 to spawn_multiplier)
+ if(spawn_distance_max > 1)
+ var/origin = spot
+ var/list/peel = turf_peel(spawn_distance_max, spawn_distance_min, origin, view_based = TRUE)
+ if(length(peel))
+ spot = pick(peel)
+ else
+ spot = pick(circleviewturfs(origin, spawn_distance_max))
+ var/chosen_mob_type = pickweight(mob_types)
+ var/mob/living/simple_animal/L = new chosen_mob_type(spot)
+ L.flags_1 |= (P.flags_1 & ADMIN_SPAWNED_1)
+ spawned_mobs += L
+ L.nest = src
+ L.faction = src.faction
+ P.visible_message("[L] [pick(spawn_text)] [P].")
+ if(length(spawn_sound))
+ playsound(P, pick(spawn_sound), 50, TRUE)
diff --git a/code/datums/looping_sounds/machinery_sounds.dm b/code/datums/looping_sounds/machinery_sounds.dm
index 743550be16db..dd05aca8ed44 100644
--- a/code/datums/looping_sounds/machinery_sounds.dm
+++ b/code/datums/looping_sounds/machinery_sounds.dm
@@ -72,3 +72,10 @@
volume = 85
vary = TRUE
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/datum/looping_sound/drill
+ mid_sounds = list('sound/machines/gravgen/gravgen_mid1.ogg'=1, 'sound/machines/gravgen/gravgen_mid2.ogg'=1, 'sound/machines/gravgen/gravgen_mid3.ogg'=1, 'sound/machines/gravgen/gravgen_mid4.ogg'=1)
+ mid_length = 4
+ volume = 50
+ extra_range = 6
diff --git a/code/datums/mapgen/planetary/LavaGenerator.dm b/code/datums/mapgen/planetary/LavaGenerator.dm
index 6e6d4d898ac1..c244f3ef2560 100644
--- a/code/datums/mapgen/planetary/LavaGenerator.dm
+++ b/code/datums/mapgen/planetary/LavaGenerator.dm
@@ -96,9 +96,12 @@
)
feature_spawn_chance = 0.3
feature_spawn_list = list(
- /obj/structure/flora/rock/hell = 10,
+ /obj/structure/flora/rock/hell = 14,
+ /obj/structure/vein = 5,
+ /obj/structure/vein/classtwo = 2,
/obj/structure/elite_tumor = 2,
/obj/structure/geyser/random = 2,
+ /obj/structure/vein/classthree = 1,
/obj/effect/spawner/lootdrop/anomaly/lava = 1,
)
@@ -112,9 +115,9 @@
/mob/living/simple_animal/hostile/asteroid/goliath/beast/ancient/crystal = 1,
/mob/living/simple_animal/hostile/asteroid/basilisk/watcher/forgotten = 1,
/mob/living/simple_animal/hostile/asteroid/hivelord/legion/crystal = 1,
- /obj/structure/spawner/lavaland/low_threat = 12,
- /obj/structure/spawner/lavaland/medium_threat = 4,
- /obj/structure/spawner/lavaland/high_threat = 2,
+ /obj/structure/spawner/lavaland/low_threat = 8,
+ /obj/structure/spawner/lavaland/medium_threat = 3,
+ /obj/structure/spawner/lavaland/high_threat = 1,
)
/datum/biome/lavaland/forest
@@ -161,7 +164,10 @@
feature_spawn_list = list(
/obj/structure/flora/tree/dead/barren = 50,
/obj/structure/flora/tree/dead/tall/grey = 45,
- /obj/effect/spawner/lootdrop/anomaly/lava = 5
+ /obj/effect/spawner/lootdrop/anomaly/lava = 10,
+ /obj/structure/vein = 5,
+ /obj/structure/vein/classtwo = 2,
+ /obj/structure/vein/classthree = 1,
)
/datum/biome/lavaland/plains/dense/mixed
@@ -231,8 +237,8 @@
/mob/living/simple_animal/hostile/asteroid/basilisk/watcher/random = 40,
/mob/living/simple_animal/hostile/asteroid/hivelord/legion/random = 30,
/mob/living/simple_animal/hostile/asteroid/goldgrub = 10,
- /obj/structure/spawner/lavaland/low_threat = 12,
- /obj/structure/spawner/lavaland/medium_threat = 4,
+ /obj/structure/spawner/lavaland/low_threat = 8,
+ /obj/structure/spawner/lavaland/medium_threat = 3,
/obj/structure/spawner/lavaland/high_threat = 2,
/obj/structure/spawner/lavaland/extreme_threat = 1
)
diff --git a/code/datums/mapgen/planetary/RockGenerator.dm b/code/datums/mapgen/planetary/RockGenerator.dm
index 61578c7a3ace..95e1273bb9d7 100644
--- a/code/datums/mapgen/planetary/RockGenerator.dm
+++ b/code/datums/mapgen/planetary/RockGenerator.dm
@@ -86,8 +86,11 @@
feature_spawn_chance = 0.25
feature_spawn_list = list(
/obj/structure/geyser/random = 80,
+ /obj/structure/vein = 60,
/obj/structure/elite_tumor = 40,
+ /obj/structure/vein/classtwo = 40,
/obj/effect/spawner/lootdrop/anomaly/rock = 10,
+ /obj/structure/vein/classthree = 10,
/obj/effect/spawner/lootdrop/anomaly/big = 1 //get out of here stalker
)
@@ -147,8 +150,11 @@
)
feature_spawn_chance = 0.5
feature_spawn_list = list(
+ /obj/structure/vein = 3,
/obj/structure/geyser/random = 2,
+ /obj/structure/vein/classtwo = 2,
/obj/structure/elite_tumor = 1,
+ /obj/structure/vein/classthree = 1,
/obj/structure/spawner/ice_moon/rockplanet = 4,
/obj/effect/spawner/lootdrop/anomaly/rock/cave = 1,
)
diff --git a/code/datums/mapgen/planetary/SandGenerator.dm b/code/datums/mapgen/planetary/SandGenerator.dm
index e50223744a7c..442daa0c7705 100644
--- a/code/datums/mapgen/planetary/SandGenerator.dm
+++ b/code/datums/mapgen/planetary/SandGenerator.dm
@@ -92,7 +92,10 @@
feature_spawn_chance = 0.1
feature_spawn_list = list(
/obj/structure/geyser/random = 8,
+ /obj/structure/vein = 8,
+ /obj/structure/vein/classtwo = 4,
/obj/structure/elite_tumor = 4,
+ /obj/structure/vein/classthree = 2,
/obj/effect/spawner/lootdrop/anomaly/sand = 1,
)
mob_spawn_chance = 4
@@ -192,7 +195,9 @@
/obj/structure/flora/ash/puce = 1,
)
feature_spawn_list = list(
+ /obj/structure/vein = 8,
/obj/structure/geyser/random = 4,
+ /obj/structure/vein/classtwo = 4,
/obj/structure/elite_tumor = 4,
/obj/effect/spawner/lootdrop/anomaly/sand/cave = 1
)
diff --git a/code/datums/mapgen/planetary/SnowGenerator.dm b/code/datums/mapgen/planetary/SnowGenerator.dm
index 05661009b52d..a066647a0e4d 100644
--- a/code/datums/mapgen/planetary/SnowGenerator.dm
+++ b/code/datums/mapgen/planetary/SnowGenerator.dm
@@ -101,8 +101,8 @@
mob_spawn_chance = 1
mob_spawn_list = list(
/mob/living/simple_animal/hostile/asteroid/wolf/random = 30,
- /obj/structure/spawner/ice_moon = 3,
- /obj/structure/spawner/ice_moon/polarbear = 3,
+ /obj/structure/spawner/ice_moon = 2,
+ /obj/structure/spawner/ice_moon/polarbear = 2,
/mob/living/simple_animal/hostile/asteroid/polarbear/random = 30,
/mob/living/simple_animal/hostile/asteroid/hivelord/legion/snow = 50,
/mob/living/simple_animal/hostile/asteroid/goldgrub = 10,
@@ -116,7 +116,10 @@
/obj/effect/spawner/lootdrop/anomaly/big = 1,
/obj/structure/spawner/ice_moon/demonic_portal/low_threat = 25,
/obj/structure/spawner/ice_moon/demonic_portal/medium_threat = 50,
- /obj/structure/spawner/ice_moon/demonic_portal/high_threat = 13
+ /obj/structure/spawner/ice_moon/demonic_portal/high_threat = 13,
+ /obj/structure/vein/ice = 25,
+ /obj/structure/vein/ice/classtwo = 50,
+ /obj/structure/vein/ice/classthree = 10,
)
/datum/biome/snow/lush
@@ -164,15 +167,18 @@
)
feature_spawn_chance = 0.1
feature_spawn_list = list(
- /obj/structure/spawner/ice_moon = 3,
- /obj/structure/spawner/ice_moon/polarbear = 3,
+ /obj/structure/spawner/ice_moon = 2,
+ /obj/structure/spawner/ice_moon/polarbear = 2,
/obj/structure/statue/snow/snowman = 3,
- /obj/structure/statue/snow/snowlegion = 1
+ /obj/structure/statue/snow/snowlegion = 1,
+ /obj/structure/vein/ice = 3,
+ /obj/structure/vein/ice/classtwo = 4,
+ /obj/structure/vein/ice/classthree = 1,
)
mob_spawn_list = list(
/mob/living/simple_animal/hostile/asteroid/wolf/random = 30,
- /obj/structure/spawner/ice_moon = 3,
- /obj/structure/spawner/ice_moon/polarbear = 3,
+ /obj/structure/spawner/ice_moon = 2,
+ /obj/structure/spawner/ice_moon/polarbear = 2,
/mob/living/simple_animal/hostile/asteroid/polarbear/random = 30,
/mob/living/simple_animal/hostile/asteroid/hivelord/legion/snow = 50,
/mob/living/simple_animal/hostile/asteroid/goldgrub = 10,
@@ -209,10 +215,13 @@
feature_spawn_list = list(
/obj/effect/spawner/lootdrop/anomaly/ice = 100,
/obj/effect/spawner/lootdrop/anomaly/big = 1,
- /obj/structure/spawner/ice_moon/demonic_portal/low_threat = 300,
- /obj/structure/spawner/ice_moon/demonic_portal/medium_threat = 500,
- /obj/structure/spawner/ice_moon/demonic_portal/high_threat = 50,
- /obj/structure/spawner/ice_moon/demonic_portal/extreme_threat = 1
+ /obj/structure/spawner/ice_moon/demonic_portal/low_threat = 200,
+ /obj/structure/spawner/ice_moon/demonic_portal/medium_threat = 400,
+ /obj/structure/spawner/ice_moon/demonic_portal/high_threat = 40,
+ /obj/structure/spawner/ice_moon/demonic_portal/extreme_threat = 1,
+ /obj/structure/vein/ice = 300,
+ /obj/structure/vein/ice/classtwo = 500,
+ /obj/structure/vein/ice/classthree = 50,
)
@@ -249,8 +258,8 @@
mob_spawn_chance = 2
mob_spawn_list = list(
/mob/living/simple_animal/hostile/asteroid/wolf/random = 30,
- /obj/structure/spawner/ice_moon = 3,
- /obj/structure/spawner/ice_moon/polarbear = 3,
+ /obj/structure/spawner/ice_moon = 2,
+ /obj/structure/spawner/ice_moon/polarbear = 2,
/mob/living/simple_animal/hostile/asteroid/polarbear/random = 30,
/mob/living/simple_animal/hostile/asteroid/hivelord/legion/snow = 50,
/mob/living/simple_animal/hostile/asteroid/goldgrub = 10,
@@ -260,13 +269,16 @@
)
feature_spawn_chance = 0.2
feature_spawn_list = list(
- /obj/structure/spawner/ice_moon/demonic_portal/low_threat = 30,
- /obj/structure/spawner/ice_moon/demonic_portal/medium_threat = 50,
- /obj/structure/spawner/ice_moon/demonic_portal/high_threat = 6,
- /obj/structure/spawner/ice_moon/demonic_portal/extreme_threat = 2,
- /obj/structure/spawner/ice_moon = 30,
- /obj/structure/spawner/ice_moon/polarbear = 30,
- /obj/effect/spawner/lootdrop/anomaly/ice/cave = 10
+ /obj/structure/spawner/ice_moon/demonic_portal/low_threat = 20,
+ /obj/structure/spawner/ice_moon/demonic_portal/medium_threat = 40,
+ /obj/structure/spawner/ice_moon/demonic_portal/high_threat = 5,
+ /obj/structure/spawner/ice_moon/demonic_portal/extreme_threat = 1,
+ /obj/structure/spawner/ice_moon = 20,
+ /obj/structure/spawner/ice_moon/polarbear = 20,
+ /obj/effect/spawner/lootdrop/anomaly/ice/cave = 10,
+ /obj/structure/vein/ice = 30,
+ /obj/structure/vein/ice/classtwo = 50,
+ /obj/structure/vein/ice/classthree = 6,
)
/datum/biome/cave/snow/thawed
diff --git a/code/game/objects/items/pinpointer.dm b/code/game/objects/items/pinpointer.dm
index 12f91f99fb8c..fced9d335205 100644
--- a/code/game/objects/items/pinpointer.dm
+++ b/code/game/objects/items/pinpointer.dm
@@ -63,7 +63,7 @@
. = ..()
if(!active)
return
- if(!target)
+ if(!target?.loc)
. += "pinon[alert ? "alert" : ""]null[icon_suffix]"
return
var/turf/here = get_turf(src)
diff --git a/code/game/objects/structures/spawner.dm b/code/game/objects/structures/spawner.dm
index 620f1cb6126d..6929bb46cf19 100644
--- a/code/game/objects/structures/spawner.dm
+++ b/code/game/objects/structures/spawner.dm
@@ -41,10 +41,12 @@ GLOBAL_LIST_INIT(astroloot, list(
var/faction = list("hostile")
var/spawn_sound = list('sound/effects/break_stone.ogg')
var/spawner_type = /datum/component/spawner
+ var/spawn_distance_min = 1
+ var/spawn_distance_max = 1
/obj/structure/spawner/Initialize()
. = ..()
- AddComponent(spawner_type, mob_types, spawn_time, faction, spawn_text, max_mobs, spawn_sound)
+ AddComponent(spawner_type, mob_types, spawn_time, faction, spawn_text, max_mobs, spawn_sound, spawn_distance_min, spawn_distance_max)
/obj/structure/spawner/attack_animal(mob/living/simple_animal/M)
if(faction_check(faction, M.faction, FALSE)&&!M.client)
diff --git a/code/game/turfs/closed/minerals.dm b/code/game/turfs/closed/minerals.dm
index 321a5ce25e90..b5eddacd3e41 100644
--- a/code/game/turfs/closed/minerals.dm
+++ b/code/game/turfs/closed/minerals.dm
@@ -155,11 +155,11 @@
return
/turf/closed/mineral/random
- var/list/mineralSpawnChanceList = list(/obj/item/stack/ore/uranium = 5, /obj/item/stack/ore/diamond = 1, /obj/item/stack/ore/gold = 10,
- /obj/item/stack/ore/silver = 12, /obj/item/stack/ore/plasma = 20, /obj/item/stack/ore/iron = 40, /obj/item/stack/ore/titanium = 11,
+ var/list/mineralSpawnChanceList = list(/obj/item/stack/ore/uranium = 3, /obj/item/stack/ore/diamond = 1, /obj/item/stack/ore/gold = 4,
+ /obj/item/stack/ore/silver = 4, /obj/item/stack/ore/plasma = 40, /obj/item/stack/ore/iron = 65, /obj/item/stack/ore/titanium = 5,
/turf/closed/mineral/gibtonite = 4, /obj/item/stack/ore/bluespace_crystal = 1)
//Currently, Adamantine won't spawn as it has no uses. -Durandan
- var/mineralChance = 13
+ var/mineralChance = 5
/turf/closed/mineral/random/Initialize(mapload, inherited_virtual_z)
@@ -189,7 +189,7 @@
Spread_Vein(path)
/turf/closed/mineral/random/high_chance
- mineralChance = 25
+ mineralChance = 13
mineralSpawnChanceList = list(
/obj/item/stack/ore/uranium = 35, /obj/item/stack/ore/diamond = 30, /obj/item/stack/ore/gold = 45, /obj/item/stack/ore/titanium = 45,
/obj/item/stack/ore/silver = 50, /obj/item/stack/ore/plasma = 50, /obj/item/stack/ore/bluespace_crystal = 20)
@@ -211,7 +211,7 @@
initial_gas_mix = "o2=22;n2=82;TEMP=293.15"
/turf/closed/mineral/random/low_chance
- mineralChance = 6
+ mineralChance = 3
mineralSpawnChanceList = list(
/obj/item/stack/ore/uranium = 2, /obj/item/stack/ore/diamond = 1, /obj/item/stack/ore/gold = 4, /obj/item/stack/ore/titanium = 4,
/obj/item/stack/ore/silver = 6, /obj/item/stack/ore/plasma = 15, /obj/item/stack/ore/iron = 40,
@@ -228,7 +228,7 @@
initial_gas_mix = LAVALAND_DEFAULT_ATMOS
defer_change = 1
- mineralChance = 10
+ mineralChance = 5
mineralSpawnChanceList = list(
/obj/item/stack/ore/uranium = 5, /obj/item/stack/ore/diamond = 1, /obj/item/stack/ore/gold = 10, /obj/item/stack/ore/titanium = 11,
/obj/item/stack/ore/silver = 12, /obj/item/stack/ore/plasma = 20, /obj/item/stack/ore/iron = 40,
@@ -254,7 +254,7 @@
baseturfs = /turf/open/floor/plating/asteroid/icerock
initial_gas_mix = ICEMOON_DEFAULT_ATMOS
defer_change = TRUE
- mineralChance = 20 //as most caves is snowy, might as well bump up the chance
+ mineralChance = 10 //as most caves is snowy, might as well bump up the chance
mineralSpawnChanceList = list(
/obj/item/stack/ore/uranium = 5, /obj/item/stack/ore/diamond = 1, /obj/item/stack/ore/gold = 10, /obj/item/stack/ore/titanium = 11,
@@ -281,7 +281,7 @@
/turf/closed/mineral/random/snow/underground
baseturfs = /turf/open/floor/plating/asteroid/snow/icemoon
// abundant ore
- mineralChance = 20
+ mineralChance = 10
mineralSpawnChanceList = list(
/obj/item/stack/ore/uranium = 10, /obj/item/stack/ore/diamond = 4, /obj/item/stack/ore/gold = 20, /obj/item/stack/ore/titanium = 22,
/obj/item/stack/ore/silver = 24, /obj/item/stack/ore/plasma = 20, /obj/item/stack/ore/iron = 20, /obj/item/stack/ore/bananium = 1,
@@ -818,7 +818,7 @@
baseturfs = /turf/open/floor/plating/asteroid/wasteplanet
mineralSpawnChanceList = list(/obj/item/stack/ore/uranium = 30, /obj/item/stack/ore/diamond = 0.5, /obj/item/stack/ore/gold = 5,
/obj/item/stack/ore/silver = 7, /obj/item/stack/ore/plasma = 35, /obj/item/stack/ore/iron = 35, /obj/item/stack/ore/titanium = 10)
- mineralChance = 30
+ mineralChance = 10
/turf/closed/mineral/snowmountain/cavern/shipside
name = "ice cavern rock"
diff --git a/code/modules/cargo/packs/machinery.dm b/code/modules/cargo/packs/machinery.dm
index 510ec7841417..bef22daedb29 100644
--- a/code/modules/cargo/packs/machinery.dm
+++ b/code/modules/cargo/packs/machinery.dm
@@ -178,6 +178,19 @@
)
crate_name = "Shuttle in a Box"
+/datum/supply_pack/machinery/drill_crate
+ name = "Heavy duty laser mining drill"
+ desc = "An experimental laser-based mining drill that Nanotrasen is kindly allowing YOU, the customer, to opt into testing of."
+ cost = 1000 //Only while TMed, jack up the price before merging
+ contains = list(
+ /obj/machinery/drill,
+ /obj/item/pinpointer/mineral,
+ /obj/item/paper/guides/drill
+ )
+ crate_name = "laser mining drill crate"
+ crate_type = /obj/structure/closet/crate/engineering
+
+
/*
Power generation machines
*/
diff --git a/code/modules/mining/drill.dm b/code/modules/mining/drill.dm
new file mode 100644
index 000000000000..281097be7842
--- /dev/null
+++ b/code/modules/mining/drill.dm
@@ -0,0 +1,370 @@
+//For handling the types of randomized malfunctions
+#define MALF_LASER 1
+#define MALF_SENSOR 2
+#define MALF_CAPACITOR 3
+#define MALF_STRUCTURAL 4
+#define MALF_CALIBRATE 5
+
+//For handling the repair of a completely destroyed drill
+#define METAL_ABSENT 0 //Couldn't think of a better word for this but it gets the point across
+#define METAL_PLACED 1
+#define METAL_SECURED 2
+
+/obj/machinery/drill
+ name = "heavy-duty laser mining drill"
+ desc = "A large scale laser drill. It's able to mine vast amounts of minerals from near-surface ore pockets, however the seismic activity tends to anger local fauna."
+ icon = 'icons/obj/machines/drill.dmi'
+ icon_state = "deep_core_drill"
+ max_integrity = 400
+ density = TRUE
+ anchored = FALSE
+ use_power = NO_POWER_USE
+ layer = ABOVE_ALL_MOB_LAYER
+ armor = list("melee" = 50, "bullet" = 30, "laser" = 30, "energy" = 30, "bomb" = 30, "bio" = 0, "rad" = 0, "fire" = 90, "acid" = 90)
+ component_parts = list()
+
+ var/malfunction
+ var/active = FALSE
+ var/obj/structure/vein/mining
+ var/datum/looping_sound/drill/soundloop
+ var/obj/item/stock_parts/cell/cell
+ var/preload_cell_type = /obj/item/stock_parts/cell
+ var/power_cost = 100
+ var/metal_attached = METAL_ABSENT
+ var/missing_part //I hate this but it's better than most the ideas I've had
+ var/current_timerid
+
+/obj/machinery/drill/examine(mob/user)
+ . = ..()
+ if(panel_open && component_parts)
+ . += display_parts(user, TRUE)
+ if(cell.charge < power_cost*5)
+ . += "The low power light is blinking."
+ switch(malfunction)
+ if(MALF_LASER)
+ . += "The [src]'s laser array appears to be broken and needs to be replaced."
+ if(MALF_SENSOR)
+ . += "The [src]'s sensors appear to be broken and need to be replaced."
+ if(MALF_CAPACITOR)
+ . += "The [src]'s capacitor appears to be broken and needs to be replaced."
+ if(MALF_STRUCTURAL)
+ . += "The [src]'s structure looks like it needs to be welded back together."
+ if(MALF_CALIBRATE)
+ . += "The [src]'s gimbal is out of alignment, it needs to be recalibrated with a multitool."
+ switch(metal_attached)
+ if(METAL_PLACED)
+ . += "Replacement plating has been attached to [src], but has not been bolted in place yet."
+ if(METAL_SECURED)
+ . += "Replacement plating has been secured to [src], but still needs to be welded into place."
+ if(machine_stat & BROKEN && !metal_attached)
+ . += "[src]'s structure has been totaled, the plasteel plating needs to be replaced."
+ . += "The manual shutoff switch can be pulled with Alt Click."
+
+/obj/machinery/drill/Initialize()
+ . = ..()
+ component_parts += new /obj/item/stock_parts/capacitor(null)
+ component_parts += new /obj/item/stock_parts/micro_laser(null)
+ component_parts += new /obj/item/stock_parts/scanning_module(null)
+ if(preload_cell_type)
+ if(!ispath(preload_cell_type,/obj/item/stock_parts/cell))
+ log_mapping("[src] at [AREACOORD(src)] had an invalid preload_cell_type: [preload_cell_type].")
+ else
+ cell = new preload_cell_type(src)
+ soundloop = new(list(src), active)
+
+/obj/machinery/drill/process()
+ if(machine_stat & BROKEN || (active && !mining))
+ active = FALSE
+ soundloop.stop()
+ update_overlays()
+ update_icon_state()
+
+/obj/machinery/drill/Destroy()
+ QDEL_NULL(soundloop)
+ QDEL_NULL(cell)
+ return ..()
+
+//Instead of being qdeled the drill requires mildly expensive repairs to use again
+/obj/machinery/drill/deconstruct(disassembled)
+ if(active && mining)
+ say("Drill integrity failure. Engaging emergency shutdown procedure.")
+ //Just to make sure mobs don't spawn infinitely from the vein and as a failure state for players
+ mining.deconstruct()
+ obj_break()
+ update_icon_state()
+ update_overlays()
+
+/obj/machinery/drill/get_cell()
+ return cell
+
+//The RPED sort of trivializes a good deal of the malfunction mechancis, as such it will not be allowed to work
+/obj/machinery/drill/exchange_parts(mob/user, obj/item/storage/part_replacer/W)
+ to_chat(user, "[W] does not seem to work on [src], it might require more delicate part manipulation.")
+ return
+
+/obj/machinery/drill/attackby(obj/item/tool, mob/living/user, params)
+ var/obj/structure/vein/vein = locate(/obj/structure/vein) in src.loc
+ if(machine_stat & BROKEN)
+ if(istype(tool,/obj/item/stack/sheet/plasteel))
+ var/obj/item/stack/sheet/plasteel/plating = tool
+ if(plating.use(10,FALSE,TRUE))
+ metal_attached = METAL_PLACED
+ to_chat(user, "You prepare to attach the plating to [src].")
+ return
+ else
+ to_chat(user, "You don't have enough plasteel to fix the plating.")
+ return
+ if(metal_attached == METAL_SECURED && tool.tool_behaviour == TOOL_WELDER)
+ if(tool.use_tool(src, user, 30, volume=50))
+ to_chat(user, "You weld the new plating onto the [src], successfully repairing it.")
+ metal_attached = METAL_ABSENT
+ obj_integrity = max_integrity
+ set_machine_stat(machine_stat & ~BROKEN)
+ update_icon_state()
+ return
+ if(tool.tool_behaviour == TOOL_WRENCH)
+ if(metal_attached && machine_stat & BROKEN)
+ if(tool.use_tool(src, user, 30, volume=50))
+ to_chat(user, "You bolt the plating the plating in place on [src].")
+ metal_attached = METAL_SECURED
+ return
+ if(!vein && !anchored)
+ to_chat(user, "[src] must be on top of an ore vein.")
+ return
+ if(active)
+ to_chat(user, "[src] can't be unsecured while it's running!")
+ return
+ if(!anchored && tool.use_tool(src, user, 30, volume=50))
+ to_chat(user, "You secure the [src] to the ore vein.")
+ playsound(src, 'sound/items/deconstruct.ogg', 50, TRUE)
+ mining = vein
+ anchored = TRUE
+ update_icon_state()
+ return
+ if(tool.use_tool(src, user, 30, volume=50))
+ to_chat(user, "You unsecure the [src] from the ore vein.")
+ playsound(src, 'sound/items/deconstruct.ogg', 50, TRUE)
+ anchored = FALSE
+
+ if(mining?.spawner_attached && mining?.spawning_started)
+ mining.toggle_spawning()
+ mining = null
+ update_icon_state()
+ return
+ if(default_deconstruction_screwdriver(user,icon_state,icon_state,tool))
+ return TRUE
+ if(panel_open) //All malfunction repair and maintenance actions are handled under here
+ var/list/needed_parts = list(/obj/item/stock_parts/scanning_module,/obj/item/stock_parts/micro_laser,/obj/item/stock_parts/capacitor)
+ if(is_type_in_list(tool,needed_parts))
+ for(var/obj/item/stock_parts/part in component_parts)
+ var/obj/item/stock_parts/new_part = tool
+ if(new_part.part_behaviour == part.part_behaviour)
+ user.transferItemToLoc(tool,src)
+ part.forceMove(user.loc)
+ component_parts += new_part
+ component_parts -= part
+ to_chat(user, "You replace [part] with [new_part].")
+ break
+ else if(istype(new_part,missing_part))
+ user.transferItemToLoc(tool,src)
+ component_parts += new_part
+ malfunction = null
+ missing_part = null
+ obj_integrity = max_integrity
+ to_chat(user, "You replace the broken part with [new_part].")
+ break
+ return
+ if(tool.tool_behaviour == TOOL_MULTITOOL && malfunction == MALF_CALIBRATE)
+ user.visible_message("[user] begins recalibrating [src].", \
+ "You begin recalibrating [src]...")
+ if(tool.use_tool(src, user, 100, volume=50))
+ malfunction = null
+ obj_integrity = max_integrity
+ return
+ if(tool.tool_behaviour == TOOL_WELDER && malfunction == MALF_STRUCTURAL)
+ if(!tool.tool_start_check(user, amount=0))
+ return
+ user.visible_message("[user] begins repairing [src].", \
+ "You begin repairing [src]...", \
+ "You hear welding.")
+ if(tool.use_tool(src, user, 100, volume=50))
+ malfunction = null
+ obj_integrity = max_integrity
+ return
+ if(istype(tool, /obj/item/stock_parts/cell))
+ var/obj/item/stock_parts/cell/battery = tool
+ if(cell)
+ to_chat(user, "[src] already has a cell!")
+ return
+ else //This should literally never be tripped unless someone tries to put a watch battery in it or something, but just in case
+ if(battery.maxcharge < power_cost)
+ to_chat(user, "[src] requires a higher capacity cell.")
+ return
+ if(!user.transferItemToLoc(tool, src))
+ return
+ cell = tool
+ to_chat(user, "You install a cell in [src].")
+ return
+ if(tool.tool_behaviour == TOOL_CROWBAR)
+ cell.update_appearance()
+ cell.forceMove(get_turf(src))
+ cell = null
+ to_chat(user, "You remove the cell from [src].")
+ active = FALSE
+ update_appearance()
+ return
+ return ..()
+
+/obj/machinery/drill/AltClick(mob/user)
+ if(active)
+ to_chat(user, "You begin the manual shutoff process.")
+ if(do_after(user,10))
+ active = FALSE
+ soundloop.stop()
+ deltimer(current_timerid)
+ mining.toggle_spawning()
+ playsound(src, 'sound/machines/switch2.ogg', 50, TRUE)
+ say("Manual shutoff engaged, ceasing mining operations.")
+ update_icon_state()
+ update_overlays()
+ else
+ to_chat(user, "You cancel the manual shutoff process.")
+
+//Can we even turn the damn thing on?
+/obj/machinery/drill/interact(mob/user, special_state)
+ . = ..()
+ if(malfunction)
+ say("Please resolve existing malfunction before continuing mining operations.")
+ return
+ if(!mining)
+ to_chat(user, "[src] isn't secured over an ore vein!")
+ return
+ if(!active)
+ playsound(src, 'sound/machines/click.ogg', 100, TRUE)
+ user.visible_message( \
+ "[user] activates [src].", \
+ "You hit the ignition button to activate [src].", \
+ "You hear a drill churn to life.")
+ start_mining()
+ else
+ to_chat(user, "[src] is currently busy, wait until it's done!")
+
+/obj/machinery/drill/update_icon_state()
+ if(anchored)
+ if(machine_stat & BROKEN)
+ icon_state = "deep_core_drill-deployed_broken"
+ return ..()
+ if(active)
+ icon_state = "deep_core_drill-active"
+ return ..()
+ else
+ icon_state = "deep_core_drill-idle"
+ return ..()
+ else
+ if(machine_stat & BROKEN)
+ icon_state = "deep_core_drill-broken"
+ return ..()
+ icon_state = "deep_core_drill"
+ return ..()
+
+/obj/machinery/drill/update_overlays()
+ . = ..()
+ SSvis_overlays.remove_vis_overlay(src, managed_vis_overlays)
+ //Cool beam of light ignores shadows.
+ if(active && anchored)
+ set_light(3, 1, "99FFFF")
+ SSvis_overlays.add_vis_overlay(src, icon, "mining_beam-particles", layer, plane, dir)
+ SSvis_overlays.add_vis_overlay(src, icon, "mining_beam-particles", layer, EMISSIVE_PLANE, dir)
+ else
+ set_light(0)
+
+//Handles all checks before starting the 30 second (on average) mining tick
+/obj/machinery/drill/proc/start_mining()
+ var/eta
+ var/power_use
+ for(var/obj/item/stock_parts/capacitor/capacitor in component_parts)
+ power_use = power_cost/capacitor.rating
+ if(cell.charge < power_use)
+ say("Error: Internal cell charge depleted")
+ active = FALSE
+ soundloop.stop()
+ update_overlays()
+ return
+ if(obj_integrity <= max_integrity/1.5)
+ malfunction = rand(1,5)
+ malfunction(malfunction)
+ active = FALSE
+ update_icon_state()
+ update_overlays()
+ return
+ if(mining.mining_charges >= 1)
+ var/mine_time
+ active = TRUE
+ soundloop.start()
+ if(!mining.spawner_attached)
+ mining.begin_spawning()
+ else if(!mining.spawning_started)
+ mining.toggle_spawning()
+ for(var/obj/item/stock_parts/micro_laser/laser in component_parts)
+ mine_time = round((300/sqrt(laser.rating))*mining.mine_time_multiplier)
+ eta = mine_time*mining.mining_charges
+ cell.use(power_use)
+ current_timerid = addtimer(CALLBACK(src, PROC_REF(mine)), mine_time, TIMER_STOPPABLE)
+ say("Estimated time until vein depletion: [time2text(eta,"mm:ss")].")
+ update_icon_state()
+ update_overlays()
+
+//Handles the process of withdrawing ore from the vein itself
+/obj/machinery/drill/proc/mine()
+ if(mining.mining_charges)
+ mining.mining_charges--
+ mine_success()
+ if(mining.mining_charges < 1)
+ say("Vein depleted.")
+ active = FALSE
+ soundloop.stop()
+ mining.deconstruct()
+ mining = null
+ update_icon_state()
+ update_overlays()
+ else
+ start_mining()
+ else if(!mining.mining_charges) //Extra check to prevent vein related errors locking us in place
+ say("Error: Vein Depleted")
+ active = FALSE
+ update_icon_state()
+ update_overlays()
+
+//Called when it's time for the drill to rip that sweet ore from the earth
+/obj/machinery/drill/proc/mine_success()
+ var/sensor_rating
+ for(var/obj/item/stock_parts/scanning_module/sensor in component_parts)
+ sensor_rating = round(sqrt(sensor.rating))
+ mining.drop_ore(sensor_rating, src)
+
+//Overly long proc to handle the unique properties for each malfunction type
+/obj/machinery/drill/proc/malfunction(malfunction_type)
+ switch(malfunction_type)
+ if(MALF_LASER)
+ say("Malfunction: Laser array damaged, please replace before continuing mining operations.")
+ for (var/obj/item/stock_parts/micro_laser/laser in component_parts)
+ component_parts.Remove(laser)
+ missing_part = /obj/item/stock_parts/micro_laser
+ if(MALF_SENSOR)
+ say("Malfunction: Ground penetrating scanner damaged, please replace before continuing mining operations.")
+ for (var/obj/item/stock_parts/scanning_module/sensor in component_parts)
+ component_parts.Remove(sensor)
+ missing_part = /obj/item/stock_parts/scanning_module
+ if(MALF_CAPACITOR)
+ say("Malfunction: Energy cell capacitor damaged, please replace before continuing mining operations.")
+ for (var/obj/item/stock_parts/capacitor/capacitor in component_parts)
+ component_parts.Remove(capacitor)
+ missing_part = /obj/item/stock_parts/capacitor
+ if(MALF_STRUCTURAL)
+ say("Malfunction: Drill plating damaged, provide structural repairs before continuing mining operations.")
+ if(MALF_CALIBRATE)
+ say("Malfunction: Drill laser calibrations out of alignment, please recalibrate before continuing.")
+
+/obj/item/paper/guides/drill
+ name = "Laser Mining Drill Operation Manual"
+ default_raw_text = "Laser Mining Drill Operation Manual
Thank you for opting in to the paid testing of Nanotrasen's new, experimental laser drilling device (trademark pending). We are legally obligated to mention that despite this new and wonderful drilling device being less dangerous than past iterations (note the 75% decrease in plasma ignition incidents), the seismic activity created by the drill has been noted to anger most forms of xenofauna. As such our legal team advises only armed mining expeditions make use of this drill.
How to set up your Laser Mining Drill
1. Find a suitable ore vein with the included scanner.
2. Wrench the drill's anchors in place over the vein.
3. Protect the drill from any enraged xenofauna until it has finished drilling.
With all this done, your ore should be well on its way out of the ground and into your pockets! Be warned though, the Laser Mining Drill is prone to numerous malfunctions when exposed to most forms of physical trauma. As such, we advise any teams utilizing this drill to bring with them a set of replacement Nanotrasen brand stock parts and a set of tools to handle repairs. If the drill suffers a total structural failure, then plasteel alloy may be needed to repair said structure."
diff --git a/code/modules/mining/equipment/mineral_scanner.dm b/code/modules/mining/equipment/mineral_scanner.dm
index add30b7d467f..7c62a915f65e 100644
--- a/code/modules/mining/equipment/mineral_scanner.dm
+++ b/code/modules/mining/equipment/mineral_scanner.dm
@@ -1,3 +1,6 @@
+#define SCANMODE_SURFACE 0
+#define SCANMODE_SUBSURFACE 1
+
/**********************Mining Scanners**********************/
/obj/item/mining_scanner
desc = "A scanner that checks surrounding rock for useful minerals; it can also be used to stop gibtonite detonations.\nIt has a speaker that can be toggled with alt+click"
@@ -38,7 +41,7 @@
qdel(src)
/obj/item/t_scanner/adv_mining_scanner
- desc = "A scanner that automatically checks surrounding rock for useful minerals; it can also be used to stop gibtonite detonations. This one has an extended range.\nIt has a speaker that can be toggled with alt+click"
+ desc = "A scanner that automatically checks surrounding rock for useful minerals; it can also be used to stop gibtonite detonations.\nIt has a speaker that can be toggled with alt+click"
name = "advanced automatic mining scanner"
icon = 'icons/obj/device.dmi'
icon_state = "mining0"
@@ -101,3 +104,128 @@
/obj/effect/temp_visual/mining_overlay/Initialize()
. = ..()
animate(src, alpha = 0, time = duration, easing = EASE_IN)
+
+/*
+ Vein Mining Scanner
+*/
+
+/obj/item/pinpointer/mineral //Definitely not the deepcore scanner with the serial number filed off
+ name = "ground penetrating mining scanner"
+ desc = "A handheld dowsing utility for locating material deep beneath the surface and on the surface. Alt-Click to change modes."
+ icon = 'icons/obj/mining.dmi'
+ icon_state = "mining"
+ custom_price = 300
+ custom_premium_price = 300
+ icon_suffix = "_mining"
+ var/scanning_surface = FALSE
+ var/cooldown = 50
+ var/current_cooldown = 0
+ var/range = 4
+ var/scanmode = SCANMODE_SURFACE
+
+/obj/item/pinpointer/mineral/examine(mob/user)
+ . = ..()
+ . += "It is currently set to [scanmode ? "scan underground" : "scan the surface"]."
+
+/obj/item/pinpointer/mineral/AltClick(mob/user) //switching modes
+ ..()
+ if(user.canUseTopic(src, BE_CLOSE))
+ if(scanning_surface||active) //prevents swithcing modes when active
+ to_chat(user, "You have to turn the [src] off first before switching modes!")
+ else
+ scanmode = !scanmode
+ to_chat(user, "You switch the [src] to [scanmode ? "scan underground " : "scan the surface"].")
+
+/obj/item/pinpointer/mineral/attack_self(mob/living/user)
+ switch(scanmode)
+ if(SCANMODE_SUBSURFACE)
+ if(active)
+ toggle_on()
+ user.visible_message("[user] deactivates [user.p_their()] scanner.", "You deactivate your scanner.")
+ return
+
+ var/vein = scan_for_target()
+ if(!vein)
+ user.visible_message("[user]'s scanner fails to detect any material.", "Your scanner fails to detect any material.")
+ return
+
+ toggle_on()
+ user.visible_message("[user] activates [user.p_their()] scanner.", "You activate your scanner.")
+ update_icon()
+
+ if(SCANMODE_SURFACE)
+ scanning_surface = !scanning_surface
+ update_icon()
+ if(scanning_surface)
+ START_PROCESSING(SSobj, src)
+ user.visible_message("[user] activates [user.p_their()] scanner.", "You activate your scanner.")
+ else
+ STOP_PROCESSING(SSobj, src)
+ user.visible_message("[user] deactivates [user.p_their()] scanner.", "You deactivate your scanner.")
+ playsound(src, 'sound/items/screwdriver2.ogg', 50, TRUE)
+
+/obj/item/pinpointer/mineral/process()
+ switch(scanmode)
+ if(SCANMODE_SUBSURFACE)
+ if(active && target && target.loc == null)
+ target = null
+ toggle_on()
+ . = ..() //returns pinpointer code if its scanning for deepcore spots
+
+ if(SCANMODE_SURFACE)
+ if(!scanning_surface)
+ STOP_PROCESSING(SSobj, src)
+ return null
+ scan_minerals()
+
+/obj/item/pinpointer/mineral/proc/scan_minerals() //used by the surface mining mode
+ if(current_cooldown <= world.time)
+ current_cooldown = world.time + cooldown
+ var/turf/t = get_turf(src)
+ mineral_scan_pulse(t, range)
+ playsound(src, 'sound/effects/ping.ogg', 20)
+
+/obj/item/pinpointer/mineral/update_overlays()
+ . = ..()
+ var/mutable_appearance/scan_mode_overlay
+ switch(scanmode)
+ if(SCANMODE_SURFACE)
+ if(scanning_surface)
+ scan_mode_overlay = mutable_appearance(icon, "on_overlay")
+ if(SCANMODE_SUBSURFACE)
+ if(active)
+ scan_mode_overlay = mutable_appearance(icon, "pinpointing_overlay")
+ else
+ scan_mode_overlay = mutable_appearance(icon, "null")
+ . += scan_mode_overlay
+
+/obj/item/pinpointer/mineral/scan_for_target()
+ var/turf/here = get_turf(src)
+ var/located_dist
+ var/obj/structure/located_vein
+ for(var/obj/structure/vein/I in GLOB.ore_veins)
+ if(I.z == 0 || I.virtual_z() != here.virtual_z())
+ continue
+ if(located_vein)
+ var/new_dist = get_dist(here, get_turf(I))
+ if(new_dist < located_dist)
+ located_dist = new_dist
+ located_vein = I
+ else
+ located_dist = get_dist(here, get_turf(I))
+ located_vein = I
+ target = located_vein
+ return located_vein
+
+//For scanning ore veins of their contents
+/obj/item/pinpointer/mineral/afterattack(obj/structure/vein/O, mob/user, proximity)
+ . = ..()
+ if(!proximity || !istype(O,/obj/structure/vein))
+ return
+ playsound(src, 'sound/effects/fastbeep.ogg', 10)
+ if(O.vein_contents.len > 0)
+ to_chat(user, "Class [O.vein_class] ore vein with [O.mining_charges] possible ore lodes found.")
+ for(var/re in O.vein_contents)
+ to_chat(user, "\tExtractable amounts of [re].")
+ else
+ to_chat(user, "No notable mineral deposits found in [O].")
diff --git a/code/modules/mining/ore_veins.dm b/code/modules/mining/ore_veins.dm
new file mode 100644
index 000000000000..3e5e20053a77
--- /dev/null
+++ b/code/modules/mining/ore_veins.dm
@@ -0,0 +1,211 @@
+GLOBAL_LIST_EMPTY(ore_veins)
+
+/obj/structure/vein
+ name = "ore vein"
+ desc = "A mostly subsurface ore deposit."
+ icon = 'icons/obj/lavaland/terrain.dmi'
+ icon_state = "geyser"
+ anchored = TRUE
+ layer = LOW_ITEM_LAYER
+ move_resist = INFINITY
+ resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF
+
+ var/mining_charges = 6
+ //Classification of the quality of possible ores within a vein
+ //Used to determine difficulty & ore amounts
+ //Intended to range from class one to class three
+ var/vein_class = 1
+ //A weighted list of all possible ores that can generate in a vein
+ //The design process is that class 1 veins have a small chance of generating with class 2 ores and so on
+ //As higher class veins will be increasingly harder to mine
+ var/list/ore_list = list(
+ /obj/item/stack/ore/iron = 7,
+ /obj/item/stack/ore/plasma = 3,
+ /obj/item/stack/ore/silver = 2,
+ /obj/item/stack/ore/uranium = 1,
+ /obj/item/stack/ore/titanium = 2,
+ )
+ //The post initialize list of all possible drops from the vein
+ //Meant to be player facing in the form of mining scanners
+ //Contents won't be randomized if the list isn't empty on initialize
+ var/list/vein_contents = list()
+ //Allows subtyped veins to determine how long it takes to mine one mining charge
+ var/mine_time_multiplier = 1
+ //Allows subtyped veins to determine how much loot is dropped per drop_ore call
+ var/drop_rate_amount_min = 15
+ var/drop_rate_amount_max = 20
+ //Mob spawning variables
+ var/spawner_attached = FALSE //Probably a drastically less sloppy way of doing this, but it technically works
+ var/spawning_started = FALSE
+ var/max_mobs = 6
+ var/spawn_time = 150 //15 seconds
+ var/mob_types = list(
+ /mob/living/simple_animal/hostile/asteroid/goliath/beast/tendril = 60,
+ /mob/living/simple_animal/hostile/asteroid/hivelord/legion/tendril = 20,
+ /mob/living/simple_animal/hostile/asteroid/brimdemon = 20,
+ )
+ var/spawn_text = "emerges from"
+ var/faction = list("hostile","mining")
+ var/spawn_sound = list('sound/effects/break_stone.ogg')
+ var/spawner_type = /datum/component/spawner
+ var/spawn_distance_min = 4
+ var/spawn_distance_max = 6
+ var/wave_length = 2 MINUTES
+ var/wave_downtime = 30 SECONDS
+
+
+//Generates amount of ore able to be pulled from the vein (mining_charges) and types of ore within it (vein_contents)
+/obj/structure/vein/Initialize()
+ . = ..()
+ var/ore_type_amount
+ mining_charges = rand(round(mining_charges - 2),mining_charges + 2)
+ if(!LAZYLEN(vein_contents))
+ switch(vein_class)
+ if(1)
+ ore_type_amount = rand(1,3)
+ if(2)
+ ore_type_amount = rand(3,5)
+ if(3)
+ ore_type_amount = rand(4,6)
+ else
+ ore_type_amount = 1
+ for(var/ore_count in 1 to ore_type_amount)
+ var/picked = pickweight(ore_list)
+ vein_contents.Add(picked)
+ ore_list.Remove(picked)
+ GLOB.ore_veins += src
+
+/obj/structure/vein/Destroy()
+ GLOB.ore_veins -= src
+ return ..()
+
+/obj/structure/vein/deconstruct(disassembled)
+ destroy_effect()
+ return..()
+
+/obj/structure/vein/proc/begin_spawning()
+ AddComponent(spawner_type, mob_types, spawn_time, faction, spawn_text, max_mobs, spawn_sound, spawn_distance_min, spawn_distance_max, wave_length, wave_downtime)
+ spawner_attached = TRUE
+ spawning_started = TRUE
+
+//Pulls a random ore from the vein list per vein_class
+/obj/structure/vein/proc/drop_ore(multiplier,obj/machinery/drill/current)
+ var/list/adjacent_turfs = get_adjacent_open_turfs(current)
+ var/drop_location = src.loc //Backup in case we can't find an adjacent turf
+ if(adjacent_turfs.len)
+ drop_location = pick(adjacent_turfs)
+ for(var/vein_content_count in 1 to vein_class)
+ var/picked = pick(vein_contents)
+ new picked(drop_location,round(rand(drop_rate_amount_min,drop_rate_amount_max)*multiplier))
+
+/obj/structure/vein/proc/destroy_effect()
+ playsound(loc,'sound/effects/explosionfar.ogg', 200, TRUE)
+ visible_message("[src] collapses!")
+
+/obj/structure/vein/proc/toggle_spawning()
+ spawning_started = SEND_SIGNAL(src, COMSIG_SPAWNER_TOGGLE_SPAWNING, spawning_started)
+
+//
+// Planetary and Class Subtypes
+// The current set of subtypes are heavily subject to future balancing and reworking as the balance of them is tested more
+//
+
+/obj/structure/vein/classtwo
+ mining_charges = 8
+ vein_class = 2
+ ore_list = list(
+ /obj/item/stack/ore/iron = 8,
+ /obj/item/stack/ore/plasma = 3,
+ /obj/item/stack/ore/silver = 4,
+ /obj/item/stack/ore/uranium = 2,
+ /obj/item/stack/ore/titanium = 5,
+ /obj/item/stack/ore/diamond = 1,
+ /obj/item/stack/ore/gold = 2,
+ /obj/item/stack/ore/bluespace_crystal = 1,
+ )
+ max_mobs = 6
+ spawn_time = 100
+ mob_types = list(
+ /mob/living/simple_animal/hostile/asteroid/goliath/beast/tendril = 60,
+ /mob/living/simple_animal/hostile/asteroid/hivelord/legion/tendril = 30,
+ /mob/living/simple_animal/hostile/asteroid/brimdemon = 20,
+ /mob/living/simple_animal/hostile/asteroid/goliath/beast/ancient = 5,
+ /mob/living/simple_animal/hostile/asteroid/hivelord/legion/dwarf/tendril = 5,
+ )
+
+/obj/structure/vein/classthree
+ mining_charges = 10
+ vein_class = 3
+ ore_list = list(
+ /obj/item/stack/ore/iron = 9,
+ /obj/item/stack/ore/plasma = 3,
+ /obj/item/stack/ore/silver = 5,
+ /obj/item/stack/ore/uranium = 2,
+ /obj/item/stack/ore/titanium = 6,
+ /obj/item/stack/ore/diamond = 4,
+ /obj/item/stack/ore/gold = 5,
+ /obj/item/stack/ore/bluespace_crystal = 3,
+ )
+ max_mobs = 6 //Best not to go past 6 due to balance and lag reasons
+ spawn_time = 80
+ mob_types = list(
+ /mob/living/simple_animal/hostile/asteroid/goliath/beast/tendril = 60,
+ /mob/living/simple_animal/hostile/asteroid/hivelord/legion/tendril = 30,
+ /mob/living/simple_animal/hostile/asteroid/brimdemon = 20,
+ /mob/living/simple_animal/hostile/asteroid/goliath/beast/ancient = 10,
+ /mob/living/simple_animal/hostile/asteroid/hivelord/legion/dwarf/tendril = 10,
+ )
+
+/obj/structure/vein/ice
+ mob_types = list(
+ /mob/living/simple_animal/hostile/asteroid/wolf = 30,
+ /mob/living/simple_animal/hostile/asteroid/polarbear = 30,
+ /mob/living/simple_animal/hostile/asteroid/hivelord/legion/snow/tendril = 20,
+ /mob/living/simple_animal/hostile/asteroid/ice_demon = 10,
+ /mob/living/simple_animal/hostile/asteroid/ice_whelp = 5,
+ /mob/living/simple_animal/hostile/asteroid/lobstrosity = 20,
+ )
+ //Ice planets earn a slightly higher rare ore chance on account of them being notably harder
+ //Alongside being a much more reliable source of plasma
+ ore_list = list(
+ /obj/item/stack/ore/iron = 7,
+ /obj/item/stack/ore/plasma = 7,
+ /obj/item/stack/ore/silver = 3,
+ /obj/item/stack/ore/uranium = 1,
+ /obj/item/stack/ore/titanium = 2,
+ /obj/item/stack/ore/titanium = 2,
+ /obj/item/stack/ore/gold = 1,
+ /obj/item/stack/ore/diamond = 1,
+ )
+
+/obj/structure/vein/ice/classtwo
+ mining_charges = 8
+ vein_class = 2
+ ore_list = list(
+ /obj/item/stack/ore/iron = 8,
+ /obj/item/stack/ore/plasma = 9,
+ /obj/item/stack/ore/silver = 5,
+ /obj/item/stack/ore/uranium = 2,
+ /obj/item/stack/ore/titanium = 6,
+ /obj/item/stack/ore/diamond = 2,
+ /obj/item/stack/ore/gold = 3,
+ /obj/item/stack/ore/bluespace_crystal = 1,
+ )
+ max_mobs = 6
+ spawn_time = 100
+
+/obj/structure/vein/ice/classthree
+ mining_charges = 10
+ vein_class = 3
+ ore_list = list(
+ /obj/item/stack/ore/iron = 8,
+ /obj/item/stack/ore/plasma = 9,
+ /obj/item/stack/ore/silver = 6,
+ /obj/item/stack/ore/uranium = 2,
+ /obj/item/stack/ore/titanium = 6,
+ /obj/item/stack/ore/diamond = 4,
+ /obj/item/stack/ore/gold = 6,
+ /obj/item/stack/ore/bluespace_crystal = 4,
+ )
+ max_mobs = 6
+ spawn_time = 80
diff --git a/code/modules/mob/living/simple_animal/hostile/hostile.dm b/code/modules/mob/living/simple_animal/hostile/hostile.dm
index 6eaa0f8ebd5a..526763e875b7 100644
--- a/code/modules/mob/living/simple_animal/hostile/hostile.dm
+++ b/code/modules/mob/living/simple_animal/hostile/hostile.dm
@@ -149,8 +149,10 @@
. = hearers(vision_range, target_from) - src //Remove self, so we don't suicide
var/static/hostile_machines = typecacheof(list(/obj/machinery/porta_turret, /obj/mecha))
+ var/static/mining_drills = typecacheof(list(/obj/machinery/drill))
. += typecache_filter_list(view(vision_range, targets_from), hostile_machines)
+ . += typecache_filter_list(view(vision_range*2, targets_from), mining_drills)
for(var/HM in typecache_filter_list(range(vision_range, target_from), hostile_machines))
if(can_see(target_from, HM, vision_range))
@@ -246,6 +248,12 @@
return FALSE
return TRUE
+ if(istype(the_target, /obj/machinery/drill))
+ var/obj/machinery/drill/drill = the_target
+ if(drill.active)
+ return TRUE
+ return FALSE
+
if(isobj(the_target))
if(attack_all_objects || is_type_in_typecache(the_target, wanted_objects))
return TRUE
@@ -582,7 +590,7 @@
toggle_ai(AI_ON)
/mob/living/simple_animal/hostile/proc/ListTargetsLazy(virtual_z)//Step 1, find out what we can see
- var/static/hostile_machines = typecacheof(list(/obj/machinery/porta_turret, /obj/mecha)) //WS - add spacepod
+ var/static/hostile_machines = typecacheof(list(/obj/machinery/porta_turret, /obj/mecha, /obj/machinery/drill)) //WS - add spacepod
. = list()
for (var/mob/M as anything in LAZYACCESS(SSmobs.players_by_virtual_z, "[virtual_z]"))
if (get_dist(M, src) < vision_range)
diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/goliath.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/goliath.dm
index c078196749fc..ce3850d22f01 100644
--- a/code/modules/mob/living/simple_animal/hostile/mining_mobs/goliath.dm
+++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/goliath.dm
@@ -281,6 +281,7 @@
cached_tentacle_turfs -= t
/mob/living/simple_animal/hostile/asteroid/goliath/beast/tendril
+ butcher_results = list(/obj/item/reagent_containers/food/snacks/meat/slab/goliath = 2, /obj/item/stack/sheet/bone = 2, /obj/item/stack/sheet/sinew = 2)
fromtendril = TRUE
//tentacles
diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/hivelord.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/hivelord.dm
index 892fba0d61f8..b602e948af98 100644
--- a/code/modules/mob/living/simple_animal/hostile/mining_mobs/hivelord.dm
+++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/hivelord.dm
@@ -175,6 +175,9 @@
/mob/living/simple_animal/hostile/asteroid/hivelord/legion/tendril
fromtendril = TRUE
+/mob/living/simple_animal/hostile/asteroid/hivelord/legion/dwarf/tendril
+ fromtendril = TRUE
+
/mob/living/simple_animal/hostile/asteroid/hivelord/legion/dwarf/death(gibbed)
move_force = MOVE_FORCE_DEFAULT
move_resist = MOVE_RESIST_DEFAULT
diff --git a/code/modules/overmap/missions/drill_mission.dm b/code/modules/overmap/missions/drill_mission.dm
new file mode 100644
index 000000000000..06859e8327d2
--- /dev/null
+++ b/code/modules/overmap/missions/drill_mission.dm
@@ -0,0 +1,93 @@
+/datum/mission/drill
+ name = "Class 1 core sample mission"
+ desc = "We require geological information from one of the neighboring planetoids . \
+ Please anchor the drill in place and defend it until it has gathered enough samples.\
+ Operation of the core sampling drill is extremely dangerous, caution is advised. "
+ value = 2000
+ duration = 80 MINUTES
+ weight = 8
+
+ var/obj/machinery/drill/mission/sampler
+ var/num_wanted = 4
+ var/class_wanted = 1
+
+/datum/mission/drill/New(...)
+ num_wanted = rand(num_wanted-2,num_wanted+2)
+ value += num_wanted*100
+ return ..()
+
+/datum/mission/drill/accept(datum/overmap/ship/controlled/acceptor, turf/accept_loc)
+ . = ..()
+ sampler = spawn_bound(/obj/machinery/drill/mission, accept_loc, VARSET_CALLBACK(src, sampler, null))
+ sampler.mission_class = class_wanted
+ sampler.num_wanted = num_wanted
+
+//Gives players a little extra money for going past the mission goal
+/datum/mission/drill/turn_in()
+ value += (sampler.num_current - num_wanted)*50
+ . = ..()
+
+/datum/mission/drill/can_complete()
+ . = ..()
+ if(!.)
+ return
+ var/obj/docking_port/mobile/scanner_port = SSshuttle.get_containing_shuttle(sampler)
+ return . && (sampler.num_current >= num_wanted) && (scanner_port?.current_ship == servant)
+
+/datum/mission/drill/get_progress_string()
+ if(!sampler)
+ return "0/[num_wanted]"
+ else
+ return "[sampler.num_current]/[num_wanted]"
+
+/datum/mission/drill/Destroy()
+ sampler = null
+ return ..()
+
+/datum/mission/drill/turn_in()
+ recall_bound(sampler)
+ return ..()
+
+/datum/mission/drill/give_up()
+ recall_bound(sampler)
+ return ..()
+
+/datum/mission/drill/classtwo
+ name = "Class 2 core sample mission"
+ value = 3500
+ weight = 6
+ class_wanted = 2
+ num_wanted = 6
+
+/datum/mission/drill/classthree
+ name = "Class 3 core sample mission"
+ value = 5000
+ weight = 4
+ duration = 100 MINUTES
+ class_wanted = 3
+ num_wanted = 8
+
+/*
+ Core sampling drill
+*/
+
+/obj/machinery/drill/mission
+ name = "core sampling research drill"
+ desc = "A specialized laser drill designed to extract geological samples."
+
+ var/num_current = 0
+ var/mission_class
+ var/num_wanted
+
+/obj/machinery/drill/mission/examine()
+ . = ..()
+ . += "The drill contains [num_current] of the [num_wanted] samples needed."
+
+/obj/machinery/drill/mission/start_mining()
+ if(mining.vein_class < mission_class && mining)
+ say("Error: A vein class of [mission_class] or greater is required for operation.")
+ return
+ . = ..()
+
+/obj/machinery/drill/mission/mine_success()
+ num_current++
diff --git a/code/modules/research/stock_parts.dm b/code/modules/research/stock_parts.dm
index ccddbdb3eb22..3bafadbc08bd 100644
--- a/code/modules/research/stock_parts.dm
+++ b/code/modules/research/stock_parts.dm
@@ -111,6 +111,7 @@ If you create T5+ please take a pass at gene_modder.dm [L40]. Max_values MUST fi
icon = 'icons/obj/stock_parts.dmi'
w_class = WEIGHT_CLASS_SMALL
var/rating = 1
+ var/part_behaviour
/obj/item/stock_parts/Initialize()
. = ..()
@@ -126,30 +127,35 @@ If you create T5+ please take a pass at gene_modder.dm [L40]. Max_values MUST fi
name = "capacitor"
desc = "A basic capacitor used in the construction of a variety of devices."
icon_state = "capacitor"
+ part_behaviour = PART_CAPACITOR
custom_materials = list(/datum/material/iron=50, /datum/material/glass=50)
/obj/item/stock_parts/scanning_module
name = "scanning module"
desc = "A compact, high resolution scanning module used in the construction of certain devices."
icon_state = "scan_module"
+ part_behaviour = PART_SCANNER
custom_materials = list(/datum/material/iron=50, /datum/material/glass=20)
/obj/item/stock_parts/manipulator
name = "micro-manipulator"
desc = "A tiny little manipulator used in the construction of certain devices."
icon_state = "micro_mani"
+ part_behaviour = PART_MANIPULATOR
custom_materials = list(/datum/material/iron=30)
/obj/item/stock_parts/micro_laser
name = "micro-laser"
desc = "A tiny laser used in certain devices."
icon_state = "micro_laser"
+ part_behaviour = PART_LASER
custom_materials = list(/datum/material/iron=10, /datum/material/glass=20)
/obj/item/stock_parts/matter_bin
name = "matter bin"
desc = "A container designed to hold compressed matter awaiting reconstruction."
icon_state = "matter_bin"
+ part_behaviour = PART_BIN
custom_materials = list(/datum/material/iron=80)
//Rating 2
diff --git a/shiptest.dme b/shiptest.dme
index 939934ea7b06..40f3f1e277f2 100644
--- a/shiptest.dme
+++ b/shiptest.dme
@@ -134,6 +134,7 @@
#include "code\__DEFINES\stat_tracking.dm"
#include "code\__DEFINES\statpanel.dm"
#include "code\__DEFINES\status_effects.dm"
+#include "code\__DEFINES\stock_parts.dm"
#include "code\__DEFINES\subsystems.dm"
#include "code\__DEFINES\tgs.config.dm"
#include "code\__DEFINES\tgs.dm"
@@ -2358,6 +2359,7 @@
#include "code\modules\mentor\verbs\mentorpm.dm"
#include "code\modules\mentor\verbs\mentorsay.dm"
#include "code\modules\mining\abandoned_crates.dm"
+#include "code\modules\mining\drill.dm"
#include "code\modules\mining\fulton.dm"
#include "code\modules\mining\machine_bluespaceminer.dm"
#include "code\modules\mining\machine_processing.dm"
@@ -2370,6 +2372,7 @@
#include "code\modules\mining\minebot.dm"
#include "code\modules\mining\mint.dm"
#include "code\modules\mining\money_bag.dm"
+#include "code\modules\mining\ore_veins.dm"
#include "code\modules\mining\ores_coins.dm"
#include "code\modules\mining\satchel_ore_boxdm.dm"
#include "code\modules\mining\shelters.dm"
@@ -2849,6 +2852,7 @@
#include "code\modules\overmap\overmap_turf.dm"
#include "code\modules\overmap\view_overmap_verb.dm"
#include "code\modules\overmap\missions\acquire_mission.dm"
+#include "code\modules\overmap\missions\drill_mission.dm"
#include "code\modules\overmap\missions\research_mission.dm"
#include "code\modules\overmap\objects\dynamic_datum.dm"
#include "code\modules\overmap\objects\event_datum.dm"