mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-07-13 00:55:20 +01:00
2e01fb19c4
## About The Pull Request Boulder-specific materials got overriden by the vent ## Changelog 🆑 fix: Fixed diamond and bluespace artifact boulders not containing any diamonds/bluespace crystals /🆑
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 = new_rock.custom_materials?.Copy() || 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/basic/mining/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/basic/mining/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
|