mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-07-12 00:27:31 +01:00
b1ae4ddce9
## About The Pull Request A few tweaks regarding diamonds and bluespace crystals with regards to ore vents. With the quantity of bluespace crystals and diamonds that have been reintroduced to the game, in an attempt to maintain their status as the rarest tier of material to obtain for the station, both materials have been removed from the standard pools of ores that can spawn into boulders. What this **means** is that it can't roll as a primary material for a boulder, providing 25-10 sheets worth of material. **However,** diamonds and BS crystals are now available from all tapped vents as an artifact boulder spawn. These artifact boulders will spawn at a flat 7% chance, previously set as a random range of 0-10% randomly per vent. Because this chance was never communicated to players, I think it's fine at this point to split the difference, lock it in at 7% (Considering that we're adding more things onto the artifact boulder outcomes as is, I've elected to throw it to a slightly higher chance, based purely on vibes). For clarity, these two artifact boulders add a flat 1-5 sheets of their respective material to the boulder on spawn, the same as mining a single tile of their respective ore from a wall. Additionally, the overlays when scanning an ore vent will now show a flashing icon to represent the rare chance that these two materials may spawn. Text has been added to the examine for ore vents to explain this behavior. Boss vents may still spawn diamond and bluespace crystals, but at a lower weighting than all other materials. Due to diamonds and BS crystals being removed from the standard ore vent weightings, their distributions within the ore vent mineral distribution used on roundstart has been shifted, while otherwise keeping a similar distribution of minerals across vents, according to the rough intent of what minerals should show up at what rarities. As previously stated, these two artifact material boulders add their ore to the boulder itself, meaning that you may also want to pick out artifact boulders in order to do limited chemical boosting for up to 50% more materials. ## Why It's Good For The Game Diamonds and bluespace crystals are intended to be rare, valuable, and desirable enough to go out of your way to obtain. Ore vents ideally should have had some sort of built-in scaling that tweaked the amount of that specific material they produced, but I never got around to implementing that and as a result they've been really, REALLY common for the last while. One of the design goals of the new mining PRs that have been released recently as to try and reintroduce some rarity and desirability into these high tier minerals by adjusting their availability while not completely locking them off from the rest of the game. This was a compromise to also still provide some utility to doing ore vents with the reintroduction of ores being more common from wall-mining. Wall diamonds and bluespace crystals will still exist, but this way completing a small collection of ore vents will help to supply the station with materials, where it's the quantity, not the quality of vents that provides the best value, while noting you're still going to be recieving these drops somewhat rarely unless optimizing your factory output. ## Changelog 🆑 balance: Non-boss ore vents may no longer contain bluespace crystals and diamonds. balance: All ore vents now have a flat 7% chance to spawn an artifact boulder. balance: There are now 2 new artifact boulder types, which may contain 1-5 sheets worth of bluespace crystals or diamonds respectively. These boulders may have their rare ore boosted via chemical boosting. balance: ore distribution in vents have been adjusted to accommodate for bluespace and diamonds being removed from vents accordingly. spellcheck: Ore vents now explain the artifact boulder mechanic on examine. /🆑
828 lines
32 KiB
Plaintext
828 lines
32 KiB
Plaintext
#define ARTIFACT_ROLL_CHANCE 7
|
|
#define MINERAL_TYPE_OPTIONS_RANDOM 4
|
|
#define OVERLAY_OFFSET_START 0
|
|
#define OVERLAY_OFFSET_EACH 5
|
|
#define MINERALS_PER_BOULDER 3
|
|
#define MAX_MINERAL_PICK_ATTEMPTS 10
|
|
|
|
/obj/structure/ore_vent
|
|
name = "ore vent"
|
|
desc = "An ore vent, brimming with underground ore. Scan with an advanced mining scanner to start extracting ore from it."
|
|
icon = 'icons/obj/mining_zones/terrain.dmi'
|
|
icon_state = "ore_vent"
|
|
base_icon_state = "ore_vent"
|
|
move_resist = MOVE_FORCE_EXTREMELY_STRONG
|
|
resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF //This thing will take a beating.
|
|
anchored = TRUE
|
|
density = TRUE
|
|
can_buckle = TRUE
|
|
|
|
/// Has this vent been tapped to produce boulders? Cannot be untapped.
|
|
var/tapped = FALSE
|
|
/// Has this vent been scanned by a mining scanner? Cannot be scanned again. Adds ores to the vent's description.
|
|
var/discovered = FALSE
|
|
/// Is this type of vent exempt from the map's vent budget/limit? Think the free iron/glass vent or boss vents. This also causes it to not roll for random mineral breakdown.
|
|
var/unique_vent = FALSE
|
|
/// Does this vent spawn a node drone when tapped? Currently unique to boss vents so try not to VV it.
|
|
var/spawn_drone_on_tap = TRUE
|
|
/// What icon_state do we use when the ore vent has been tapped?
|
|
var/icon_state_tapped = "ore_vent_active"
|
|
/// A weighted list of what minerals are contained in this vent, with weight determining how likely each mineral is to be picked in produced boulders.
|
|
var/list/mineral_breakdown = list()
|
|
/// What size boulders does this vent produce?
|
|
var/boulder_size = BOULDER_SIZE_SMALL
|
|
/// Reference to this ore vent's NODE drone, to track wave success.
|
|
var/mob/living/basic/node_drone/node = null //this path is a placeholder.
|
|
/// String containing the formatted list of ores that this vent can produce, and who first discovered this vent.
|
|
var/ore_string = ""
|
|
/// Associated list of vent size weights to pick from.
|
|
var/list/ore_vent_options = list(
|
|
LARGE_VENT_TYPE = 3,
|
|
MEDIUM_VENT_TYPE = 5,
|
|
SMALL_VENT_TYPE = 7,
|
|
)
|
|
var/wave_timer = WAVE_DURATION_SMALL
|
|
|
|
/// What string do we use to warn the player about the excavation event?
|
|
var/excavation_warning = "Are you ready to excavate this ore vent?"
|
|
/// A list of mobs that can be spawned by this vent during a wave defense event.
|
|
var/list/defending_mobs = list(
|
|
/mob/living/basic/mining/goliath,
|
|
/mob/living/basic/mining/legion/spawner_made,
|
|
/mob/living/basic/mining/watcher,
|
|
/mob/living/basic/mining/lobstrosity/lava,
|
|
/mob/living/basic/mining/brimdemon,
|
|
/mob/living/basic/mining/bileworm,
|
|
)
|
|
/// What items can be used to scan a vent?
|
|
var/static/list/scanning_equipment = list(
|
|
/obj/item/t_scanner/adv_mining_scanner,
|
|
/obj/item/mining_scanner,
|
|
)
|
|
|
|
/// Turf above us that we're currently tracking, stored in case of movement jank
|
|
var/turf/tracked_turf = null
|
|
/// Visual overlay for our vent tap
|
|
var/obj/effect/abstract/vent_visual = null
|
|
/// Underlay visual for when we turn transparent
|
|
var/obj/effect/abstract/top_bit = null
|
|
/// Are we currently hiding our top half because someone is above us?
|
|
var/trimmed = FALSE
|
|
|
|
/// What base icon_state do we use for this vent's boulders?
|
|
var/boulder_icon_state = "boulder"
|
|
/// Percent chance that this vent will produce an artifact boulder.
|
|
var/artifact_chance = ARTIFACT_ROLL_CHANCE
|
|
/// We use a cooldown to prevent the wave defense from being started multiple times.
|
|
COOLDOWN_DECLARE(wave_cooldown)
|
|
/// We use a cooldown to prevent players from tapping boulders rapidly from vents.
|
|
COOLDOWN_DECLARE(manual_vent_cooldown)
|
|
|
|
/obj/structure/ore_vent/Initialize(mapload)
|
|
if(mapload)
|
|
generate_description()
|
|
register_context()
|
|
if(!unique_vent)
|
|
SSore_generation.possible_vents += src
|
|
boulder_icon_state = pick(list(
|
|
"boulder",
|
|
"rock",
|
|
"stone",
|
|
))
|
|
if(tapped)
|
|
SSore_generation.processed_vents += src
|
|
update_appearance(UPDATE_ICON_STATE)
|
|
add_tapped_visual()
|
|
|
|
RegisterSignal(src, COMSIG_SPAWNER_SPAWNED_DEFAULT, PROC_REF(anti_cheese))
|
|
RegisterSignal(src, COMSIG_SPAWNER_SPAWNED, PROC_REF(log_mob_spawned))
|
|
AddElement(/datum/element/give_turf_traits, string_list(list(TRAIT_NO_TERRAFORM)))
|
|
set_turf_tracking()
|
|
return ..()
|
|
|
|
/obj/structure/ore_vent/Destroy()
|
|
SSore_generation.possible_vents -= src
|
|
reset_drone(success = FALSE)
|
|
if(tapped)
|
|
SSore_generation.processed_vents -= src
|
|
return ..()
|
|
|
|
/obj/structure/ore_vent/attackby(obj/item/attacking_item, mob/user, list/modifiers, list/attack_modifiers)
|
|
. = ..()
|
|
if(.)
|
|
return TRUE
|
|
if(!is_type_in_list(attacking_item, scanning_equipment))
|
|
return TRUE
|
|
if(tapped)
|
|
balloon_alert_to_viewers("vent tapped!")
|
|
return TRUE
|
|
scan_and_confirm(user)
|
|
return TRUE
|
|
|
|
/obj/structure/ore_vent/attack_hand(mob/living/user, list/modifiers)
|
|
. = ..()
|
|
if(.)
|
|
return
|
|
if(!HAS_TRAIT(user, TRAIT_BOULDER_BREAKER))
|
|
return
|
|
if(!discovered)
|
|
to_chat(user, span_notice("You can't quite find the weakpoint of [src]... Perhaps it needs to be scanned first?"))
|
|
return
|
|
to_chat(user, span_notice("You start striking [src] with your golem's fist, attempting to dredge up a boulder..."))
|
|
for(var/i in 1 to 3)
|
|
if(do_after(user, boulder_size * 1 SECONDS, src))
|
|
user.apply_damage(20, STAMINA)
|
|
playsound(src, 'sound/items/weapons/genhit.ogg', 50, TRUE)
|
|
produce_boulder(TRUE)
|
|
visible_message(span_notice("You've successfully produced a boulder! Boy are your arms tired."))
|
|
|
|
/obj/structure/ore_vent/attack_basic_mob(mob/user, list/modifiers)
|
|
. = ..()
|
|
if(HAS_TRAIT(user, TRAIT_BOULDER_BREAKER))
|
|
produce_boulder(TRUE)
|
|
return TRUE
|
|
|
|
/obj/structure/ore_vent/is_buckle_possible(mob/living/target, force, check_loc)
|
|
. = ..()
|
|
if(tapped)
|
|
return FALSE
|
|
if(istype(target, /mob/living/basic/node_drone))
|
|
return TRUE
|
|
|
|
/obj/structure/ore_vent/examine(mob/user)
|
|
. = ..()
|
|
if(discovered)
|
|
switch(boulder_size)
|
|
if(BOULDER_SIZE_SMALL)
|
|
. += span_notice("This vent produces [span_bold("small")] boulders containing [ore_string]")
|
|
if(BOULDER_SIZE_MEDIUM)
|
|
. += span_notice("This vent produces [span_bold("medium")] boulders containing [ore_string]")
|
|
if(BOULDER_SIZE_LARGE)
|
|
. += span_notice("This vent produces [span_bold("large")] boulders containing [ore_string]")
|
|
else
|
|
. += span_notice("This vent can be scanned with a [span_bold("Mining Scanner")].")
|
|
if(artifact_chance)
|
|
. += span_notice("This vent has a low chance to produce an [span_bold("artifact boulder.")] These may contain rare minerals or strange artifacts.")
|
|
|
|
/obj/structure/ore_vent/add_context(atom/source, list/context, obj/item/held_item, mob/living/user)
|
|
if(is_type_in_list(held_item, scanning_equipment))
|
|
context[SCREENTIP_CONTEXT_LMB] = "Scan vent"
|
|
return CONTEXTUAL_SCREENTIP_SET
|
|
|
|
/**
|
|
* This proc is called when the ore vent is initialized, in order to determine what minerals boulders it spawns can contain.
|
|
* The materials available are determined by SSore_generation.ore_vent_minerals, which is a list of all minerals that can be contained in ore vents for a given cave generation.
|
|
* As a result, minerals use a weighted list as seen by ore_vent_minerals_lavaland, which is then copied to ore_vent_minerals.
|
|
* Once a material is picked from the weighted list, it's removed from ore_vent_minerals, so that it can't be picked again and provided its own internal weight used when assigning minerals to boulders spawned by this vent.
|
|
* May also be called after the fact, as seen in SSore_generation's initialize, to add more minerals to an existing vent.
|
|
*
|
|
* The above applies only when spawning in at mapload, otherwise we pick randomly from ore_vent_minerals_lavaland.
|
|
*
|
|
* @params new_minerals How many minerals should be added to this vent? Defaults to MINERAL_TYPE_OPTIONS_RANDOM, which is 4.
|
|
* @params map_loading Is this vent being spawned in at mapload? If so, we use the ore_generation subsystem's ore_vent_minerals list to pick minerals. Otherwise, we pick randomly from ore_vent_minerals_lavaland.
|
|
*/
|
|
/obj/structure/ore_vent/proc/generate_mineral_breakdown(new_minerals = MINERAL_TYPE_OPTIONS_RANDOM, map_loading = FALSE)
|
|
if(new_minerals < 1)
|
|
CRASH("generate_mineral_breakdown called with new_minerals < 1.")
|
|
|
|
//should have enough minerals for the vent during round start
|
|
var/list/available_minerals = SSore_generation.ore_vent_minerals
|
|
if(map_loading && available_minerals.len < new_minerals)
|
|
CRASH("No minerals left to pick from! We may have spawned too many ore vents in init, or the map config in seedRuins may not have enough resources for the mineral budget.")
|
|
|
|
var/list/datum/material/picked_minerals = list()
|
|
for(var/_ in 1 to new_minerals)
|
|
var/datum/material/mineral
|
|
|
|
//pick an unique mineral but try only MAX_MINERAL_PICK_ATTEMPTS times before giving up else we could be stuck here forever
|
|
var/attempts = 0
|
|
do
|
|
mineral = length(mineral_breakdown) ? pick_weight(mineral_breakdown) : null
|
|
if(map_loading)
|
|
if(!mineral || !available_minerals.Find(mineral))
|
|
mineral = pick(available_minerals)
|
|
else
|
|
mineral = mineral || pick_weight(GLOB.ore_vent_minerals_lavaland)
|
|
attempts += 1
|
|
while(attempts < MAX_MINERAL_PICK_ATTEMPTS && picked_minerals.Find(mineral))
|
|
|
|
//register the picked mineral, removing it from the round start available minerals if nessassary
|
|
if(map_loading)
|
|
available_minerals -= mineral
|
|
picked_minerals |= mineral
|
|
|
|
//assign random weights to picked minerals
|
|
mineral_breakdown.Cut()
|
|
for(var/datum/material/mineral as anything in picked_minerals)
|
|
mineral_breakdown[mineral] = rand(1, new_minerals)
|
|
|
|
/**
|
|
* Returns the quantity of mineral sheets in each ore vent's boulder contents roll.
|
|
* First roll can produce the most ore, with subsequent rolls scaling lower logarithmically.
|
|
* Inversely scales with ore_floor, so that the first roll is the largest, and subsequent rolls are smaller.
|
|
* (1 -> from 16 to 7 sheets of materials, and 3 -> from 8 to 6 sheets of materials on a small vent)
|
|
* This also means a large boulder can highroll a boulder with a full stack of 50 sheets of material.
|
|
* @params ore_floor The number of minerals already rolled. Used to scale the logarithmic function.
|
|
*/
|
|
/obj/structure/ore_vent/proc/ore_quantity_function(ore_floor, ore_per_size = HALF_SHEET_MATERIAL_AMOUNT)
|
|
return ore_per_size * max(round(boulder_size * (log(rand(1 + ore_floor, 4 + ore_floor)) ** -1)), 1)
|
|
|
|
/**
|
|
* This confirms that the user wants to start the wave defense event, and that they can start it.
|
|
*/
|
|
/obj/structure/ore_vent/proc/pre_wave_defense(mob/user, spawn_drone = TRUE, mech_scan = FALSE)
|
|
if(tgui_alert(user, excavation_warning, "Begin defending ore vent?", list("Yes", "No")) != "Yes")
|
|
return FALSE
|
|
if(!can_interact(user) && !mech_scan)
|
|
return FALSE
|
|
if(!COOLDOWN_FINISHED(src, wave_cooldown) || node)
|
|
return FALSE
|
|
//This is where we start spitting out mobs.
|
|
Shake(duration = 3 SECONDS)
|
|
if(spawn_drone)
|
|
node = new /mob/living/basic/node_drone(loc)
|
|
node.arrive(src)
|
|
RegisterSignal(node, COMSIG_QDELETING, PROC_REF(handle_wave_conclusion))
|
|
RegisterSignal(node, COMSIG_MOVABLE_MOVED, PROC_REF(handle_wave_conclusion))
|
|
addtimer(CALLBACK(node, TYPE_PROC_REF(/atom, update_appearance)), wave_timer * 0.25)
|
|
addtimer(CALLBACK(node, TYPE_PROC_REF(/atom, update_appearance)), wave_timer * 0.5)
|
|
addtimer(CALLBACK(node, TYPE_PROC_REF(/atom, update_appearance)), wave_timer * 0.75)
|
|
add_shared_particles(/particles/smoke/ash)
|
|
for(var/i in 1 to 5) // Clears the surroundings of the ore vent before starting wave defense.
|
|
for(var/turf/rock in oview(i))
|
|
|
|
if(istype(rock, /turf/closed/mineral)) //Solid wall checks: Mine out the wall, but start skipping more as we move farther away.
|
|
if(prob(50 + (i * 8)))
|
|
continue
|
|
var/turf/closed/mineral/drillable = rock
|
|
drillable.gets_drilled(user)
|
|
if(prob(50))
|
|
new /obj/effect/decal/cleanable/rubble(rock) // Only throw rubble when we actually mine something, and not all the time.
|
|
continue //skip the rest of the checks
|
|
|
|
if(istype(rock, /turf/open/misc/asteroid) && prob(35)) // Open rock floors: make rubble decals occasionally.
|
|
new /obj/effect/decal/cleanable/rubble(rock)
|
|
continue
|
|
|
|
if(istype(rock, /turf/open/lava)) // Lava turfs, skip as we get farther away, otherwise produce a boulder and make it a platform, lasting the whole wave.
|
|
if(prob(30 + (i * 8))) // We want to skip these less than the mining walls, since lava is more common to deal with.
|
|
continue
|
|
|
|
var/obj/item/boulder/produced = produce_boulder(FALSE)
|
|
var/obj/structure/lattice/catwalk/boulder/platform = produced.create_platform(rock, null, wave_timer)
|
|
|
|
if(!platform || !QDELETED(produced))
|
|
qdel(produced)
|
|
continue
|
|
platform.alpha = 0
|
|
platform.pixel_z = -16
|
|
animate(platform, alpha = 255, time = 2 SECONDS, pixel_z = 0, easing = QUAD_EASING|EASE_OUT)
|
|
sleep(0.6 SECONDS)
|
|
return TRUE
|
|
|
|
/**
|
|
* Starts the wave defense event, which will spawn a number of lavaland mobs based on the size of the ore vent.
|
|
* Called after the vent has been tapped by a scanning device.
|
|
* Will summon a number of waves of mobs, ending in the vent being tapped after the final wave.
|
|
*/
|
|
/obj/structure/ore_vent/proc/start_wave_defense()
|
|
AddComponent(\
|
|
/datum/component/spawner, \
|
|
spawn_types = defending_mobs, \
|
|
spawn_time = (10 SECONDS + (5 SECONDS * (boulder_size/5))), \
|
|
max_spawned = 10, \
|
|
max_spawn_per_attempt = (1 + (boulder_size/5)), \
|
|
spawn_text = "emerges to assault", \
|
|
spawn_distance = 4, \
|
|
spawn_distance_exclude = 3, \
|
|
)
|
|
COOLDOWN_START(src, wave_cooldown, wave_timer)
|
|
addtimer(CALLBACK(src, PROC_REF(handle_wave_conclusion)), wave_timer)
|
|
update_appearance(UPDATE_ICON_STATE)
|
|
|
|
/**
|
|
* Called when the wave defense event ends, after a variable amount of time in start_wave_defense.
|
|
*
|
|
* If the node drone is still alive, the ore vent is tapped and the ore vent will begin generating boulders.
|
|
* If the node drone is dead, the ore vent is not tapped and the wave defense can be reattempted.
|
|
*
|
|
* Also gives xp and mining points to all nearby miners in equal measure.
|
|
*/
|
|
/obj/structure/ore_vent/proc/handle_wave_conclusion(datum/source)
|
|
SIGNAL_HANDLER
|
|
|
|
SEND_SIGNAL(src, COMSIG_VENT_WAVE_CONCLUDED)
|
|
COOLDOWN_RESET(src, wave_cooldown)
|
|
remove_shared_particles(/particles/smoke/ash)
|
|
|
|
//happens in COMSIG_QDELETING
|
|
if(QDELETED(node))
|
|
initiate_wave_loss(loss_message = "\the [src] creaks and groans as the mining attempt fails, and the vent closes back up.")
|
|
return
|
|
|
|
//happens in COMSIG_MOVABLE_MOVED
|
|
if(get_turf(node) != get_turf(src))
|
|
initiate_wave_loss(loss_message = "The [node] detaches from the [src], and the vent closes back up!")
|
|
return //Start over!
|
|
|
|
initiate_wave_win()
|
|
|
|
/**
|
|
* Handles reseting our ore vent to its original state so we can start over
|
|
*/
|
|
/obj/structure/ore_vent/proc/initiate_wave_loss(loss_message)
|
|
visible_message(span_danger(loss_message))
|
|
update_appearance(UPDATE_ICON_STATE)
|
|
reset_drone(success = FALSE)
|
|
|
|
/**
|
|
* Handles winning the event, gives everyone a payout and start boulder production
|
|
*/
|
|
/obj/structure/ore_vent/proc/initiate_wave_win(forced = FALSE)
|
|
tapped = TRUE //The Node Drone has survived the wave defense, and the ore vent is tapped.
|
|
SSore_generation.processed_vents += src
|
|
if(!forced)
|
|
log_game("Ore vent [key_name_and_tag(src)] was tapped")
|
|
SSblackbox.record_feedback("tally", "ore_vent_completed", 1, type)
|
|
balloon_alert_to_viewers("vent tapped!")
|
|
|
|
update_appearance(UPDATE_ICON_STATE)
|
|
add_tapped_visual()
|
|
qdel(GetComponent(/datum/component/gps))
|
|
reset_drone(success = TRUE)
|
|
|
|
if(forced)
|
|
return
|
|
|
|
for(var/mob/living/miner in range(7, src)) //Give the miners who are near the vent points and xp.
|
|
var/obj/item/card/id/user_id_card = miner.get_idcard(TRUE)
|
|
if(miner.stat <= SOFT_CRIT)
|
|
miner.mind?.adjust_experience(/datum/skill/mining, MINING_SKILL_BOULDER_SIZE_XP * boulder_size)
|
|
if(!user_id_card)
|
|
continue
|
|
var/point_reward_val = (MINER_POINT_MULTIPLIER * (boulder_size + 2)) - MINER_POINT_MULTIPLIER // We remove the base value of discovering the vent
|
|
if(user_id_card.registered_account)
|
|
user_id_card.registered_account.mining_points += point_reward_val
|
|
user_id_card.registered_account.bank_card_talk("You have been awarded [point_reward_val] mining points for your efforts.")
|
|
|
|
/obj/structure/ore_vent/proc/add_tapped_visual()
|
|
if (vent_visual)
|
|
vis_contents |= vent_visual
|
|
return
|
|
|
|
vent_visual = new(src)
|
|
vent_visual.icon = 'icons/obj/mining_zones/terrain.dmi'
|
|
vent_visual.icon_state = "well"
|
|
vent_visual.layer = ABOVE_MOB_LAYER
|
|
vent_visual.vis_flags = VIS_INHERIT_PLANE | VIS_INHERIT_ID
|
|
vis_contents += vent_visual
|
|
|
|
/obj/structure/ore_vent/update_icon_state()
|
|
if (tapped)
|
|
icon_state = "[base_icon_state]_active"
|
|
else
|
|
icon_state = base_icon_state
|
|
if (trimmed)
|
|
icon_state = "[icon_state]_trimmed"
|
|
return ..()
|
|
|
|
/obj/structure/ore_vent/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change)
|
|
. = ..()
|
|
set_turf_tracking()
|
|
|
|
/// Update the turf which we track for mobs entering/exiting
|
|
/obj/structure/ore_vent/proc/set_turf_tracking()
|
|
if (tracked_turf)
|
|
UnregisterSignal(tracked_turf, list(COMSIG_ATOM_ENTERED, COMSIG_ATOM_EXITED))
|
|
|
|
tracked_turf = locate(x, y + 1, z)
|
|
if (!isnull(tracked_turf))
|
|
RegisterSignal(tracked_turf, COMSIG_ATOM_ENTERED, PROC_REF(on_entered))
|
|
RegisterSignal(tracked_turf, COMSIG_ATOM_EXITED, PROC_REF(on_exited))
|
|
|
|
/obj/structure/ore_vent/proc/on_entered(atom/source, atom/movable/entered)
|
|
SIGNAL_HANDLER
|
|
|
|
if (!isliving(entered))
|
|
return
|
|
|
|
if (!trimmed)
|
|
trim_vent()
|
|
|
|
/obj/structure/ore_vent/proc/on_exited(atom/source, atom/movable/exited, direction)
|
|
SIGNAL_HANDLER
|
|
|
|
if (!isliving(exited) || !isnull(locate(/mob/living) in tracked_turf))
|
|
return
|
|
|
|
if (trimmed)
|
|
untrim_vent()
|
|
|
|
/// Turn the top part of the vent transparent and clickthrough
|
|
/obj/structure/ore_vent/proc/trim_vent()
|
|
trimmed = TRUE
|
|
update_appearance(UPDATE_ICON_STATE)
|
|
|
|
if (vent_visual)
|
|
vent_visual.vis_flags &= ~VIS_INHERIT_ID
|
|
vent_visual.mouse_opacity = MOUSE_OPACITY_TRANSPARENT
|
|
|
|
if (!top_bit)
|
|
top_bit = new(src)
|
|
top_bit.icon = 'icons/obj/mining_zones/terrain.dmi'
|
|
// Because its transparent we can't just underlay the full thing
|
|
top_bit.icon_state = "[base_icon_state][tapped ? "_active" : ""]_trimmed_top"
|
|
top_bit.layer = ABOVE_MOB_LAYER
|
|
top_bit.vis_flags = VIS_INHERIT_PLANE | VIS_INHERIT_LAYER | VIS_UNDERLAY
|
|
top_bit.mouse_opacity = MOUSE_OPACITY_TRANSPARENT
|
|
|
|
vis_contents |= top_bit
|
|
animate(src, 0.5 SECONDS, alpha = 128)
|
|
|
|
/// Revert the transparency of the top half of the vent
|
|
/obj/structure/ore_vent/proc/untrim_vent()
|
|
trimmed = FALSE
|
|
update_appearance(UPDATE_ICON_STATE)
|
|
|
|
if (vent_visual)
|
|
vent_visual.mouse_opacity = MOUSE_OPACITY_ICON
|
|
vent_visual.vis_flags |= VIS_INHERIT_ID
|
|
|
|
if (top_bit)
|
|
vis_contents -= top_bit
|
|
|
|
animate(src, 0.5 SECONDS, alpha = 255)
|
|
|
|
/**
|
|
* Sends our node back to base and cleans up after the reference
|
|
*/
|
|
/obj/structure/ore_vent/proc/reset_drone(success)
|
|
if(!QDELETED(node))
|
|
node.pre_escape(success = success)
|
|
UnregisterSignal(node, list(COMSIG_QDELETING, COMSIG_MOVABLE_MOVED))
|
|
node = null
|
|
|
|
/**
|
|
* Called when the ore vent is tapped by a scanning device.
|
|
* Gives a readout of the ores available in the vent that gets added to the description,
|
|
* then asks the user if they want to start wave defense if it's already been discovered.
|
|
* @params user The user who tapped the vent.
|
|
* @params mech_scan If TRUE, will bypass interaction checks to allow mechs to be able to begin the wave defense.
|
|
*/
|
|
/obj/structure/ore_vent/proc/scan_and_confirm(mob/living/user, mech_scan = FALSE)
|
|
if(tapped)
|
|
balloon_alert_to_viewers("vent tapped!")
|
|
return
|
|
if(!COOLDOWN_FINISHED(src, wave_cooldown) || node) //We're already defending the vent, so don't scan it again.
|
|
balloon_alert_to_viewers("protect the node drone!")
|
|
return
|
|
if(!discovered)
|
|
if(DOING_INTERACTION_WITH_TARGET(user, src))
|
|
balloon_alert(user, "already scanning!")
|
|
return
|
|
balloon_alert(user, "scanning...")
|
|
playsound(src, 'sound/items/timer.ogg', 30, TRUE)
|
|
if(!do_after(user, 4 SECONDS, src))
|
|
return
|
|
|
|
discovered = TRUE
|
|
balloon_alert(user, "vent scanned!")
|
|
generate_description(user)
|
|
AddComponent(/datum/component/gps, name)
|
|
var/obj/item/card/id/user_id_card = user.get_idcard(TRUE)
|
|
if(isnull(user_id_card))
|
|
return
|
|
if(user_id_card.registered_account)
|
|
user_id_card.registered_account.mining_points += (MINER_POINT_MULTIPLIER)
|
|
user_id_card.registered_account.bank_card_talk("You've been awarded [MINER_POINT_MULTIPLIER] mining points for discovery of an ore vent.")
|
|
return
|
|
|
|
if(!pre_wave_defense(user, spawn_drone_on_tap, mech_scan))
|
|
return
|
|
start_wave_defense()
|
|
|
|
/**
|
|
* Generates a description of the ore vent to ore_string, based on the minerals contained within it.
|
|
* Ore_string is passed to examine().
|
|
*/
|
|
/obj/structure/ore_vent/proc/generate_description(mob/user)
|
|
ore_string = ""
|
|
var/list/mineral_names = list()
|
|
for(var/datum/material/resource as anything in mineral_breakdown)
|
|
mineral_names += initial(resource.name)
|
|
|
|
ore_string = "[english_list(mineral_names)]."
|
|
if(user)
|
|
ore_string += "\nThis vent was first discovered by [user]."
|
|
/**
|
|
* Adds floating temp_visual overlays to the vent, showcasing what minerals are contained within it.
|
|
* If undiscovered, adds a single overlay with the icon_state "unknown".
|
|
*/
|
|
/obj/structure/ore_vent/proc/add_mineral_overlays()
|
|
if(mineral_breakdown.len && !discovered)
|
|
var/atom/movable/flick_visual/visual = flick_overlay_view(mutable_appearance('icons/effects/vent_overlays.dmi', "unknown"), 4.5 SECONDS)
|
|
animate(visual, alpha = 0, time = 4.5 SECONDS, easing = CIRCULAR_EASING|EASE_IN)
|
|
return
|
|
|
|
for(var/datum/material/selected_mat as anything in mineral_breakdown)
|
|
var/atom/movable/flick_visual/visual = flick_overlay_view(mutable_appearance('icons/effects/vent_overlays.dmi', selected_mat.name), 4.5 SECONDS)
|
|
animate(visual, alpha = 0, time = 4.5 SECONDS, easing = CIRCULAR_EASING|EASE_IN)
|
|
|
|
if(artifact_chance)
|
|
var/atom/movable/flick_visual/rare = flick_overlay_view(mutable_appearance('icons/effects/vent_overlays.dmi', "rare_ore"), 4.5 SECONDS)
|
|
animate(rare, alpha = 0, time = 4.5 SECONDS, easing = CIRCULAR_EASING|EASE_IN)
|
|
|
|
|
|
/**
|
|
* Here is where we handle producing a new boulder, based on the qualities of this ore vent.
|
|
* Returns the boulder produced.
|
|
* @params apply_cooldown Should we apply a cooldown to producing boulders? Default's false, used by manual boulder production (goldgrubs, golems, etc).
|
|
*/
|
|
/obj/structure/ore_vent/proc/produce_boulder(apply_cooldown = FALSE)
|
|
RETURN_TYPE(/obj/item/boulder)
|
|
|
|
//cooldown applies only for manual processing by hand
|
|
if(apply_cooldown && !COOLDOWN_FINISHED(src, manual_vent_cooldown))
|
|
return
|
|
|
|
//produce the boulder
|
|
var/obj/item/boulder/new_rock
|
|
if(prob(artifact_chance))
|
|
var/picked_artifact = pick(typesof(/obj/item/boulder/artifact))
|
|
new_rock = new picked_artifact(loc)
|
|
else
|
|
new_rock = new /obj/item/boulder(loc)
|
|
Shake(duration = 1.5 SECONDS)
|
|
|
|
//decorate the boulder with materials
|
|
var/list/mats_list = list()
|
|
for(var/iteration in 1 to MINERALS_PER_BOULDER)
|
|
var/datum/material/material = pick_weight(mineral_breakdown)
|
|
mats_list[material] += ore_quantity_function(iteration)
|
|
new_rock.set_custom_materials(mats_list)
|
|
|
|
//set size & durability
|
|
new_rock.boulder_size = boulder_size
|
|
new_rock.durability = rand(2, boulder_size) //randomize durability a bit for some flavor.
|
|
new_rock.boulder_string = boulder_icon_state
|
|
|
|
switch(boulder_size)
|
|
if(BOULDER_SIZE_SMALL)
|
|
new_rock.platform_lifespan = PLATFORM_LIFE_SMALL
|
|
if(BOULDER_SIZE_MEDIUM)
|
|
new_rock.platform_lifespan = PLATFORM_LIFE_MEDIUM
|
|
if(BOULDER_SIZE_LARGE)
|
|
new_rock.platform_lifespan = PLATFORM_LIFE_LARGE
|
|
|
|
new_rock.update_appearance(UPDATE_ICON_STATE)
|
|
|
|
//start the cooldown & return the boulder
|
|
if(apply_cooldown)
|
|
COOLDOWN_START(src, manual_vent_cooldown, 10 SECONDS)
|
|
return new_rock
|
|
|
|
/**
|
|
* When the ore vent cannot spawn a mob due to being blocked from all sides, we cause some MILD, MILD explosions.
|
|
* Explosion matches a gibtonite light explosion, as a way to clear nearby solid structures, with a high likelihood of breaking the NODE drone.
|
|
*/
|
|
/obj/structure/ore_vent/proc/anti_cheese()
|
|
explosion(src, heavy_impact_range = 1, light_impact_range = 3, flame_range = 0, flash_range = 0, adminlog = FALSE)
|
|
|
|
/**
|
|
* Handle logging for mobs spawned
|
|
*/
|
|
/obj/structure/ore_vent/proc/log_mob_spawned(datum/source, mob/living/created)
|
|
SIGNAL_HANDLER
|
|
log_game("Ore vent [key_name_and_tag(src)] spawned the following mob: [key_name_and_tag(created)]")
|
|
SSblackbox.record_feedback("tally", "ore_vent_mobs_spawned", 1, created.type)
|
|
RegisterSignal(created, COMSIG_LIVING_DEATH, PROC_REF(log_mob_killed))
|
|
|
|
/**
|
|
* Handle logging for mobs killed
|
|
*/
|
|
/obj/structure/ore_vent/proc/log_mob_killed(datum/source, mob/living/killed)
|
|
SIGNAL_HANDLER
|
|
log_game("Vent-spawned mob [key_name_and_tag(killed)] was killed")
|
|
SSblackbox.record_feedback("tally", "ore_vent_mobs_killed", 1, killed.type)
|
|
|
|
/**
|
|
* The part of initialization and the ore_generation subsystem's setup that assigns the size of an ore_vent and it's boulders.
|
|
* @param random: Used for random sized boulders, and the standard (pre 9-6-2025) behavior.
|
|
* @param force_size: An override we use for map_generation, when we know what size boulder we need from this ore vent already. Use the standard ore_vent size defines.
|
|
*/
|
|
/obj/structure/ore_vent/proc/vent_size_setup(random = FALSE, force_size, map_loading)
|
|
var/string_boulder_size
|
|
|
|
if(force_size)
|
|
string_boulder_size = force_size
|
|
|
|
else if(random)
|
|
string_boulder_size = pick_weight(ore_vent_options)
|
|
|
|
name = "[string_boulder_size] ore vent"
|
|
switch(string_boulder_size)
|
|
if(LARGE_VENT_TYPE)
|
|
boulder_size = BOULDER_SIZE_LARGE
|
|
wave_timer = WAVE_DURATION_LARGE
|
|
if(map_loading)
|
|
GLOB.ore_vent_sizes["large"] += 1
|
|
if(MEDIUM_VENT_TYPE)
|
|
boulder_size = BOULDER_SIZE_MEDIUM
|
|
wave_timer = WAVE_DURATION_MEDIUM
|
|
if(map_loading)
|
|
GLOB.ore_vent_sizes["medium"] += 1
|
|
if(SMALL_VENT_TYPE)
|
|
boulder_size = BOULDER_SIZE_SMALL
|
|
wave_timer = WAVE_DURATION_SMALL
|
|
if(map_loading)
|
|
GLOB.ore_vent_sizes["small"] += 1
|
|
else
|
|
boulder_size = BOULDER_SIZE_SMALL //Might as well set a default value
|
|
wave_timer = WAVE_DURATION_SMALL
|
|
name = initial(name)
|
|
|
|
//comes with the station, and is already tapped.
|
|
/obj/structure/ore_vent/starter_resources
|
|
name = "active ore vent"
|
|
desc = "An ore vent, brimming with underground ore. It's already supplying the station with iron and glass."
|
|
tapped = TRUE
|
|
discovered = TRUE
|
|
unique_vent = TRUE
|
|
boulder_size = BOULDER_SIZE_SMALL
|
|
mineral_breakdown = list(
|
|
/datum/material/iron = 1,
|
|
/datum/material/glass = 1,
|
|
)
|
|
|
|
/obj/structure/ore_vent/starter_resources/Initialize(mapload)
|
|
. = ..()
|
|
generate_description()
|
|
|
|
/obj/structure/ore_vent/random
|
|
// Todo: determine if we need a boulder_size default thats unique from the override performed in vent_size_setup.
|
|
|
|
/obj/structure/ore_vent/random/Initialize(mapload)
|
|
. = ..()
|
|
if(!unique_vent && !mapload)
|
|
generate_mineral_breakdown(map_loading = mapload) //Default to random mineral breakdowns, unless this is a unique vent or we're still setting up default vent distribution.
|
|
generate_description()
|
|
if(!mapload)
|
|
vent_size_setup(random = TRUE) // We only do this here specific to random distribution ore vents, and within mapload we handle this manually within SSore_generation.
|
|
|
|
|
|
/obj/structure/ore_vent/random/icebox //The one that shows up on the top level of icebox
|
|
icon_state = "ore_vent_ice"
|
|
base_icon_state = "ore_vent_ice"
|
|
defending_mobs = list(
|
|
/mob/living/basic/mining/lobstrosity,
|
|
/mob/living/basic/mining/legion/snow/spawner_made,
|
|
/mob/living/basic/mining/wolf,
|
|
/mob/living/simple_animal/hostile/asteroid/polarbear,
|
|
)
|
|
ore_vent_options = list(
|
|
SMALL_VENT_TYPE,
|
|
)
|
|
|
|
/obj/structure/ore_vent/random/icebox/lower
|
|
defending_mobs = list(
|
|
/mob/living/basic/mining/ice_whelp,
|
|
/mob/living/basic/mining/lobstrosity,
|
|
/mob/living/basic/mining/legion/snow/spawner_made,
|
|
/mob/living/basic/mining/ice_demon,
|
|
/mob/living/basic/mining/wolf,
|
|
/mob/living/simple_animal/hostile/asteroid/polarbear,
|
|
)
|
|
ore_vent_options = list(
|
|
SMALL_VENT_TYPE = 3,
|
|
MEDIUM_VENT_TYPE = 5,
|
|
LARGE_VENT_TYPE = 7,
|
|
)
|
|
|
|
/obj/structure/ore_vent/boss
|
|
name = "menacing ore vent"
|
|
desc = "An ore vent, brimming with underground ore. This one has an evil aura about it. Better be careful."
|
|
unique_vent = TRUE
|
|
spawn_drone_on_tap = FALSE
|
|
boulder_size = BOULDER_SIZE_LARGE
|
|
mineral_breakdown = list( // All the riches of the world, eeny meeny boulder room.
|
|
/datum/material/iron = 1,
|
|
/datum/material/glass = 1,
|
|
/datum/material/plasma = 1,
|
|
/datum/material/titanium = 1,
|
|
/datum/material/silver = 1,
|
|
/datum/material/gold = 1,
|
|
/datum/material/diamond = 0.1,
|
|
/datum/material/uranium = 1,
|
|
/datum/material/bluespace = 0.1,
|
|
/datum/material/plastic = 1,
|
|
)
|
|
defending_mobs = list(
|
|
/mob/living/simple_animal/hostile/megafauna/bubblegum,
|
|
/mob/living/simple_animal/hostile/megafauna/dragon,
|
|
/mob/living/simple_animal/hostile/megafauna/colossus,
|
|
)
|
|
excavation_warning = "Something big is nearby. Are you ABSOLUTELY ready to excavate this ore vent? A NODE drone will be deployed after threat is neutralized."
|
|
///What boss do we want to spawn?
|
|
var/summoned_boss = null
|
|
|
|
/obj/structure/ore_vent/boss/Initialize(mapload)
|
|
. = ..()
|
|
summoned_boss = pick(defending_mobs)
|
|
|
|
/obj/structure/ore_vent/boss/examine(mob/user)
|
|
. = ..()
|
|
var/boss_string = ""
|
|
switch(summoned_boss)
|
|
if(/mob/living/simple_animal/hostile/megafauna/bubblegum)
|
|
boss_string = "A giant fleshbound beast"
|
|
if(/mob/living/simple_animal/hostile/megafauna/dragon)
|
|
boss_string = "Sharp teeth and scales"
|
|
if(/mob/living/simple_animal/hostile/megafauna/colossus)
|
|
boss_string = "A giant, armored behemoth"
|
|
if(/mob/living/simple_animal/hostile/megafauna/demonic_frost_miner)
|
|
boss_string = "A bloody drillmark"
|
|
if(/mob/living/simple_animal/hostile/megafauna/wendigo/noportal)
|
|
boss_string = "A chilling skull"
|
|
. += span_notice("[boss_string] is etched onto the side of the vent.")
|
|
|
|
/obj/structure/ore_vent/boss/start_wave_defense()
|
|
if(!COOLDOWN_FINISHED(src, wave_cooldown))
|
|
return
|
|
// Completely override the normal wave defense, and just spawn the boss.
|
|
var/mob/living/simple_animal/hostile/megafauna/boss = new summoned_boss(loc)
|
|
RegisterSignal(boss, COMSIG_LIVING_DEATH, PROC_REF(handle_wave_conclusion))
|
|
SSblackbox.record_feedback("tally", "ore_vent_mobs_spawned", 1, summoned_boss)
|
|
COOLDOWN_START(src, wave_cooldown, INFINITY) //Basically forever
|
|
boss.say(boss.summon_line, language = /datum/language/common, forced = "summon line") //Pull their specific summon line to say. Default is meme text so make sure that they have theirs set already.
|
|
|
|
/obj/structure/ore_vent/boss/handle_wave_conclusion()
|
|
node = new /mob/living/basic/node_drone(loc) //We're spawning the vent after the boss dies, so the player can just focus on the boss.
|
|
SSblackbox.record_feedback("tally", "ore_vent_mobs_killed", 1, summoned_boss)
|
|
COOLDOWN_RESET(src, wave_cooldown)
|
|
return ..()
|
|
|
|
/obj/structure/ore_vent/boss/icebox
|
|
icon_state = "ore_vent_ice"
|
|
base_icon_state = "ore_vent_ice"
|
|
defending_mobs = list(
|
|
/mob/living/simple_animal/hostile/megafauna/demonic_frost_miner,
|
|
/mob/living/simple_animal/hostile/megafauna/wendigo/noportal,
|
|
/mob/living/simple_animal/hostile/megafauna/colossus,
|
|
)
|
|
|
|
/obj/structure/ore_vent/debug
|
|
name = "debug ore vent"
|
|
desc = "How the hell did you get this?."
|
|
tapped = TRUE
|
|
discovered = TRUE
|
|
unique_vent = TRUE
|
|
color = "#ff00f2"
|
|
boulder_size = BOULDER_SIZE_SMALL
|
|
mineral_breakdown = list(
|
|
/datum/material/iron = 1,
|
|
)
|
|
|
|
/obj/structure/ore_vent/debug/attack_hand(mob/living/user, list/modifiers)
|
|
. = ..()
|
|
var/datum/material/choice = tgui_input_list(user, "Choose a material to add/remove.", "New material", subtypesof(/datum/material))
|
|
if(!choice)
|
|
return
|
|
if(mineral_breakdown[choice])
|
|
mineral_breakdown -= choice
|
|
balloon_alert_to_viewers("removed [choice::name]")
|
|
return
|
|
mineral_breakdown += choice
|
|
balloon_alert_to_viewers("added [choice::name]")
|
|
var/value = tgui_input_number(user, "What weight should it have?", "ore pickweight", 1, 100, 1)
|
|
mineral_breakdown[choice] = value
|
|
balloon_alert_to_viewers("weighting of [value] added")
|
|
generate_description()
|
|
|
|
/obj/structure/ore_vent/debug/attack_hand_secondary(mob/user, list/modifiers)
|
|
. = ..()
|
|
var/choice = tgui_input_list(user, "Choose a vent size.", "New size", list(SMALL_VENT_TYPE, MEDIUM_VENT_TYPE, LARGE_VENT_TYPE))
|
|
if(!choice)
|
|
return
|
|
vent_size_setup(random = FALSE, force_size = choice, map_loading = FALSE)
|
|
|
|
/obj/effect/landmark/mining_center
|
|
name = "Mining Epicenter"
|
|
icon_state = "mining_epicenter"
|
|
|
|
/obj/effect/landmark/mining_center/Initialize(mapload)
|
|
..()
|
|
|
|
for(var/obj/mining_mark as anything in GLOB.mining_center)
|
|
if(src.z == mining_mark.z)
|
|
CRASH("\The [src] spawned on Z level [z] already exists! Maps should only have at most one mining epicenter for normal ore generation.")
|
|
|
|
GLOB.mining_center += loc
|
|
return INITIALIZE_HINT_QDEL
|
|
|
|
#undef ARTIFACT_ROLL_CHANCE
|
|
#undef MINERAL_TYPE_OPTIONS_RANDOM
|
|
#undef OVERLAY_OFFSET_START
|
|
#undef OVERLAY_OFFSET_EACH
|
|
#undef MINERALS_PER_BOULDER
|
|
#undef MAX_MINERAL_PICK_ATTEMPTS
|