Skip to content

Commit

Permalink
Meteor Shields Part 2: The Doohickeying (#2358)
Browse files Browse the repository at this point in the history
* Meteor shield refactor part 2

* Fix .dme ordering

* Meteors now drop debris when zapped!

* More info, yayyy

* multi-z meteor shield proxying

* Reorganize.

* Use `are_zs_connected` helper.

* Oh wow it all works now

* turns out that makes the whole thingy transparent

* rename to HK-MPS (idea/name stolen from SentientVoid)

* Fix double-period.

* log interactions with meteor shields

* Slightly boost the kill range
  • Loading branch information
Absolucy authored Jun 25, 2024
1 parent 914b2e3 commit ec868b2
Show file tree
Hide file tree
Showing 15 changed files with 331 additions and 154 deletions.
3 changes: 3 additions & 0 deletions code/__DEFINES/~monkestation/holomaps.dm
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
#define HOLOMAP_ROCK "#66666644" // Color of mineral walls
#define HOLOMAP_HOLOFIER "#0096bb" // Whole map is multiplied by this to give it a green holoish look

#define HOLOMAP_AREACOLOR_SHIELD_1 rgb(0, 119, 255, 64)
#define HOLOMAP_AREACOLOR_SHIELD_2 rgb(0, 255, 255, 64)

#define HOLOMAP_AREACOLOR_COMMAND "#3434d499"
#define HOLOMAP_AREACOLOR_SECURITY "#AE121299"
#define HOLOMAP_AREACOLOR_MEDICAL "#447bc299"
Expand Down
6 changes: 6 additions & 0 deletions code/__DEFINES/~monkestation/traits.dm
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,11 @@

#define ABDUCTOR_GLAND_VENTCRAWLING_TRAIT "abductor_gland_ventcrawling"
#define TRAIT_BETTER_CYBERCONNECTOR "better_cyberconnector_hacking"

/// Allows the user to instantly reload.
#define TRAIT_INSTANT_RELOAD "instant_reload"


// /turf/open
/// If a trait is considered as having "coverage" by a meteor shield.
#define TRAIT_COVERED_BY_METEOR_SHIELD "covered_by_meteor_shield"
12 changes: 6 additions & 6 deletions code/controllers/subsystem/explosions.dm
Original file line number Diff line number Diff line change
Expand Up @@ -514,7 +514,7 @@ SUBSYSTEM_DEF(explosions)
* - [creaking_sound][/sound]: The sound that plays when the station creaks during the explosion.
* - [hull_creaking_sound][/sound]: The sound that plays when the station creaks after the explosion.
*/
/datum/controller/subsystem/explosions/proc/shake_the_room(turf/epicenter, near_distance, far_distance, quake_factor, echo_factor, creaking, sound/near_sound = sound(get_sfx(SFX_EXPLOSION)), sound/far_sound = sound('sound/effects/explosionfar.ogg'), sound/echo_sound = sound('sound/effects/explosion_distant.ogg'), sound/creaking_sound = sound(get_sfx(SFX_EXPLOSION_CREAKING)), hull_creaking_sound = sound(get_sfx(SFX_HULL_CREAKING)))
/datum/controller/subsystem/explosions/proc/shake_the_room(turf/epicenter, near_distance, far_distance, quake_factor, echo_factor, creaking, sound/near_sound = sound(get_sfx(SFX_EXPLOSION)), sound/far_sound = sound('sound/effects/explosionfar.ogg'), sound/echo_sound = sound('sound/effects/explosion_distant.ogg'), sound/creaking_sound = sound(get_sfx(SFX_EXPLOSION_CREAKING)), hull_creaking_sound = sound(get_sfx(SFX_HULL_CREAKING)), pressure_affected = TRUE) // monkestation edit: add pressure_affected
var/frequency = get_rand_frequency()
var/blast_z = epicenter.z
if(isnull(creaking)) // Autoset creaking.
Expand All @@ -535,18 +535,18 @@ SUBSYSTEM_DEF(explosions)
var/base_shake_amount = sqrt(near_distance / (distance + 1))

if(distance <= round(near_distance + world.view - 2, 1)) // If you are close enough to see the effects of the explosion first-hand (ignoring walls)
listener.playsound_local(epicenter, null, 100, TRUE, frequency, sound_to_use = near_sound)
listener.playsound_local(epicenter, null, 100, TRUE, frequency, pressure_affected = pressure_affected, sound_to_use = near_sound) // monkestation edit: pressure_affected
if(base_shake_amount > 0)
shake_camera(listener, NEAR_SHAKE_DURATION, clamp(base_shake_amount, 0, NEAR_SHAKE_CAP))

else if(distance < far_distance) // You can hear a far explosion if you are outside the blast radius. Small explosions shouldn't be heard throughout the station.
var/far_volume = clamp(far_distance / 2, FAR_LOWER, FAR_UPPER)
if(creaking)
listener.playsound_local(epicenter, null, far_volume, TRUE, frequency, sound_to_use = creaking_sound, distance_multiplier = 0)
listener.playsound_local(epicenter, null, far_volume, TRUE, frequency, pressure_affected = pressure_affected, sound_to_use = creaking_sound, distance_multiplier = 0) // monkestation edit: pressure_affected
else if(prob(FAR_SOUND_PROB)) // Sound variety during meteor storm/tesloose/other bad event
listener.playsound_local(epicenter, null, far_volume, TRUE, frequency, sound_to_use = far_sound, distance_multiplier = 0)
listener.playsound_local(epicenter, null, far_volume, TRUE, frequency, pressure_affected = pressure_affected, sound_to_use = far_sound, distance_multiplier = 0) // monkestation edit: pressure_affected
else
listener.playsound_local(epicenter, null, far_volume, TRUE, frequency, sound_to_use = echo_sound, distance_multiplier = 0)
listener.playsound_local(epicenter, null, far_volume, TRUE, frequency, pressure_affected = pressure_affected, sound_to_use = echo_sound, distance_multiplier = 0) // monkestation edit: pressure_affected

if(base_shake_amount || quake_factor)
base_shake_amount = max(base_shake_amount, quake_factor * 3, 0) // Devastating explosions rock the station and ground
Expand All @@ -559,7 +559,7 @@ SUBSYSTEM_DEF(explosions)
shake_camera(listener, FAR_SHAKE_DURATION, clamp(quake_factor / 4, 0, FAR_SHAKE_CAP))
else
echo_volume = 40
listener.playsound_local(epicenter, null, echo_volume, TRUE, frequency, sound_to_use = echo_sound, distance_multiplier = 0)
listener.playsound_local(epicenter, null, echo_volume, TRUE, frequency, pressure_affected = pressure_affected, sound_to_use = echo_sound, distance_multiplier = 0) // monkestation edit: pressure_affected

if(creaking) // 5 seconds after the bang, the station begins to creak
addtimer(CALLBACK(listener, TYPE_PROC_REF(/mob, playsound_local), epicenter, null, rand(FREQ_LOWER, FREQ_UPPER), TRUE, frequency, null, null, FALSE, hull_creaking_sound, 0), CREAK_DELAY)
Expand Down
2 changes: 1 addition & 1 deletion code/modules/station_goals/meteor_shield.dm
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
// A chain of satellites encircles the station
// Satellites be actived to generate a shield that will block unorganic matter from passing it.
/datum/station_goal/station_shield
name = "Station Shield"
name = "Hard-Kill Meteor Protection System" // monkestation edit
var/coverage_goal = 500
requires_space = TRUE

Expand Down
9 changes: 9 additions & 0 deletions monkestation/code/modules/cargo/packs/engineering.dm
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
/datum/supply_pack/engineering/shield_sat
name = "Hard-Kill Meteor Protection Satellites"
desc = "Contains a 5 pack of HK-MPS capsules, which can be deployed into a full meteor defense satellite."
special = FALSE
access_view = ACCESS_ENGINEERING
contains = list(/obj/item/meteor_shield_capsule = 5)

/datum/supply_pack/engineering/shield_sat_control
name = "Hard-Kill Meteor Protection System Control Board"
desc = "A control system for HK-MPS satellites."
special = FALSE
access_view = ACCESS_ENGINEERING
Binary file modified monkestation/code/modules/holomaps/icons/8x8.dmi
Binary file not shown.
14 changes: 14 additions & 0 deletions monkestation/code/modules/holomaps/machinery.dm
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,20 @@
if(length(fire_alarms))
extra_overlays["Fire Alarms"] = list("icon" = image('monkestation/code/modules/holomaps/icons/8x8.dmi', icon_state = "fire_marker"), "markers" = fire_alarms)

if(length(GLOB.meteor_shielded_turfs))
var/icon/canvas = icon(HOLOMAP_ICON, "blank")
var/z_has_coverage = FALSE
for(var/turf/open/shielded_turf as anything in GLOB.meteor_shielded_turfs)
if(shielded_turf?.z != current_z_level)
continue
var/offset_x = HOLOMAP_CENTER_X + shielded_turf.x
var/offset_y = HOLOMAP_CENTER_Y + shielded_turf.y
var/color = ((offset_x ^ offset_y) % 2 == 0) ? HOLOMAP_AREACOLOR_SHIELD_1 : HOLOMAP_AREACOLOR_SHIELD_2
canvas.DrawBox(color, offset_x, offset_y)
z_has_coverage = TRUE
if(z_has_coverage)
extra_overlays["Meteor Shield"] = list("icon" = image('monkestation/code/modules/holomaps/icons/8x8.dmi', icon_state = "meteor_shield"), "markers" = list(image(canvas)))

/*
var/list/air_alarms = list()
for(var/obj/machinery/airalarm/air_alarm in GLOB.machines)
Expand Down
114 changes: 114 additions & 0 deletions monkestation/code/modules/meteor_shield/meteor_shield.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
GLOBAL_LIST_EMPTY_TYPED(meteor_shield_sats, /obj/machinery/satellite/meteor_shield)
GLOBAL_VAR_INIT(total_meteors_zapped, 0)

/obj/machinery/satellite/meteor_shield
name = "meteor defense satellite"
mode = "HK-MPS"
kill_range = 16
/// Whether the meteor sat checks for line of sight to determine if it can intercept a meteor.
var/check_sight = TRUE
/// The proximity monitor used to detect meteors entering the shield's range.
var/datum/proximity_monitor/advanced/meteor_shield/monitor
/// A counter for how many meteors this specific satellite has zapped.
var/meteors_zapped = 0
/// A list of "proxy" objects used for multi-z coverage.
var/list/obj/effect/abstract/meteor_shield_proxy/proxies = list()

/obj/machinery/satellite/meteor_shield/Initialize(mapload)
. = ..()
AddElement(/datum/element/repackable, /obj/item/flatpacked_machine/generic, 5 SECONDS, TRUE, TRUE)

GLOB.meteor_shield_sats += src
RegisterSignal(src, COMSIG_MOVABLE_SPACEMOVE, PROC_REF(on_space_move)) // so these fuckers don't drift off into space when you're trying to position them
setup_proximity()
setup_proxies()
register_context()

/obj/machinery/satellite/meteor_shield/Destroy()
GLOB.meteor_shield_sats -= src
proxies = null
QDEL_NULL(monitor)
return ..()

/obj/machinery/satellite/meteor_shield/examine(mob/user)
. = ..()
. += span_info("It has stopped <b>[meteors_zapped]</b> meteors so far.")
. += span_info("Overall, all meteor defense satellites have stopped a combined <b>[GLOB.total_meteors_zapped]</b> meteors this shift.")

/obj/machinery/satellite/meteor_shield/proc/on_space_move(datum/source)
SIGNAL_HANDLER
return COMSIG_MOVABLE_STOP_SPACEMOVE

/obj/machinery/satellite/meteor_shield/vv_edit_var(vname, vval)
. = ..()
if(.)
switch(vname)
if(NAMEOF(src, kill_range))
monitor?.set_range(kill_range)
for(var/proxy_z in proxies)
var/obj/effect/abstract/meteor_shield_proxy/proxy = proxies[proxy_z]
proxy.monitor.set_range(kill_range)
if(NAMEOF(src, active))
set_anchored(active)
setup_proximity()

/obj/machinery/satellite/meteor_shield/add_context(atom/source, list/context, obj/item/held_item, mob/user)
context[SCREENTIP_CONTEXT_LMB] = active ? "Deactivate" : "Activate"
return CONTEXTUAL_SCREENTIP_SET

/obj/machinery/satellite/meteor_shield/toggle(mob/user)
. = ..()
if(.)
user.log_message("[active ? "" : "de"]activated [src] at [AREACOORD(src)]", LOG_GAME)
setup_proximity()

/obj/machinery/satellite/meteor_shield/emag_act(mob/user, obj/item/card/emag/emag_card)
. = ..()
user.log_message("emagged [src] at [AREACOORD(src)]", LOG_GAME)
setup_proximity()

/obj/machinery/satellite/meteor_shield/on_changed_z_level(turf/old_turf, turf/new_turf, same_z_layer, notify_contents)
. = ..()
setup_proxies()

/obj/machinery/satellite/meteor_shield/proc/setup_proximity()
if((obj_flags & EMAGGED) || !active)
if(!QDELETED(monitor))
QDEL_NULL(monitor)
else
if(QDELETED(monitor))
monitor = new(src, kill_range)

/obj/machinery/satellite/meteor_shield/proc/setup_proxies()
for(var/stacked_z in SSmapping.get_connected_levels(get_turf(src)))
setup_proxy_for_z(stacked_z)

/obj/machinery/satellite/meteor_shield/proc/setup_proxy_for_z(target_z)
if(target_z == z)
return
// don't setup a proxy if there already is one.
if(!QDELETED(proxies["[target_z]"]))
return
var/turf/our_loc = get_turf(src)
var/turf/target_loc = locate(our_loc.x, our_loc.y, target_z)
if(QDELETED(target_loc))
return
var/obj/effect/abstract/meteor_shield_proxy/new_proxy = new(target_loc, src)
proxies["[target_z]"] = new_proxy

/obj/machinery/satellite/meteor_shield/piercing
check_sight = FALSE

/obj/machinery/satellite/meteor_shield/proc/change_meteor_chance(mod = 1)
var/static/list/meteor_event_typecache
if(!meteor_event_typecache)
meteor_event_typecache = typecacheof(list(
/datum/round_event_control/meteor_wave,
/datum/round_event_control/sandstorm,
/datum/round_event_control/space_dust,
/datum/round_event_control/stray_meteor
))
var/list/all_events = SSevents.control | SSgamemode.control
for(var/datum/round_event_control/event as anything in all_events)
if(is_type_in_typecache(event, meteor_event_typecache))
event.weight *= mod
10 changes: 10 additions & 0 deletions monkestation/code/modules/meteor_shield/meteor_shield_capsule.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/obj/item/meteor_shield_capsule
name = "meteor defense satellite capsule"
desc = "A bluespace capsule which a single unit of meteor defense satellite is compressed within. If you activate this capsule, a meteor shield satellite will pop out. You still need to install these."
icon_state = "capsule"
icon = 'icons/obj/mining.dmi'
w_class = WEIGHT_CLASS_TINY

/obj/item/meteor_shield_capsule/Initialize(mapload)
. = ..()
AddComponent(/datum/component/deployable, 5 SECONDS, /obj/machinery/satellite/meteor_shield, delete_on_use = TRUE)
40 changes: 40 additions & 0 deletions monkestation/code/modules/meteor_shield/meteor_shield_coverage.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#define TRAIT_METEOR_SHIELD_FIELD_MONITORED "meteor_shield_field_monitored"

GLOBAL_LIST_EMPTY_TYPED(meteor_shielded_turfs, /turf/open)

/// Stupid element to handle tracking which turfs are in a meteor sat's range,
/// without messing up in situations like with overlapping ranges.
/datum/element/meteor_shield_coverage
// Detach whenever destroyed, so we can ensure there's no hanging references to the turf in GLOB.meteor_shielded_turfs
element_flags = ELEMENT_DETACH_ON_HOST_DESTROY
/// Signals to attach to all turfs.
var/static/list/attach_signals = list(
SIGNAL_ADDTRAIT(TRAIT_COVERED_BY_METEOR_SHIELD),
SIGNAL_REMOVETRAIT(TRAIT_COVERED_BY_METEOR_SHIELD)
)

/datum/element/meteor_shield_coverage/Attach(turf/open/target)
. = ..()
if(!isgroundlessturf(target))
return ELEMENT_INCOMPATIBLE
// We use a trait to prevent duplicate assignments.
if(!HAS_TRAIT(target, TRAIT_METEOR_SHIELD_FIELD_MONITORED))
ADD_TRAIT(target, TRAIT_METEOR_SHIELD_FIELD_MONITORED, ELEMENT_TRAIT(type))
RegisterSignals(target, attach_signals, PROC_REF(update_global_shield_list))
GLOB.meteor_shielded_turfs += target

/datum/element/meteor_shield_coverage/Detach(turf/open/target)
REMOVE_TRAIT(target, TRAIT_METEOR_SHIELD_FIELD_MONITORED, ELEMENT_TRAIT(type))
UnregisterSignal(target, attach_signals)
GLOB.meteor_shielded_turfs -= target
return ..()

/datum/element/meteor_shield_coverage/proc/update_global_shield_list(turf/open/source)
SIGNAL_HANDLER
if(!isgroundlessturf(source) || !HAS_TRAIT(source, TRAIT_COVERED_BY_METEOR_SHIELD))
source.RemoveElement(/datum/element/meteor_shield_coverage)

/proc/get_meteor_sat_coverage() as num
return length(GLOB.meteor_shielded_turfs)

#undef TRAIT_METEOR_SHIELD_FIELD_MONITORED
33 changes: 33 additions & 0 deletions monkestation/code/modules/meteor_shield/meteor_shield_field.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
GLOBAL_LIST_EMPTY_TYPED(meteor_shield_fields, /datum/proximity_monitor/advanced/meteor_shield)

/// A proximity monitor field that marks openspace turfs within as being covered by a meteor shield.
/datum/proximity_monitor/advanced/meteor_shield
edge_is_a_field = TRUE
var/obj/machinery/satellite/meteor_shield/proxied_host

/datum/proximity_monitor/advanced/meteor_shield/New(atom/_host, range, _ignore_if_not_on_turf, proxied_host)
GLOB.meteor_shield_fields += src
if(proxied_host)
src.proxied_host = proxied_host
return ..()

/datum/proximity_monitor/advanced/meteor_shield/Destroy()
GLOB.meteor_shield_fields -= src
proxied_host = null
return ..()

/datum/proximity_monitor/advanced/meteor_shield/setup_field_turf(turf/open/target)
if(!isgroundlessturf(target))
return
var/obj/machinery/satellite/meteor_shield/host_sat = proxied_host || host
if(host_sat.check_los(get_turf(host_sat), target))
ADD_TRAIT(target, TRAIT_COVERED_BY_METEOR_SHIELD, REF(src))
target.AddElement(/datum/element/meteor_shield_coverage)

/datum/proximity_monitor/advanced/meteor_shield/cleanup_field_turf(turf/target)
REMOVE_TRAIT(target, TRAIT_COVERED_BY_METEOR_SHIELD, REF(src))

/datum/proximity_monitor/advanced/meteor_shield/set_range(range, force_rebuild)
. = ..()
if(.)
recalculate_field(full_recalc = TRUE)
43 changes: 43 additions & 0 deletions monkestation/code/modules/meteor_shield/meteor_shield_proxy.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/obj/effect/abstract/meteor_shield_proxy
invisibility = INVISIBILITY_ABSTRACT
/// The meteor shield sat this is proxying - any HasProximity calls will be forwarded to it.
var/obj/machinery/satellite/meteor_shield/parent
/// Our proximity monitor.
var/datum/proximity_monitor/advanced/meteor_shield/monitor

/obj/effect/abstract/meteor_shield_proxy/Initialize(mapload, obj/machinery/satellite/meteor_shield/parent)
. = ..()
if(QDELETED(parent))
return INITIALIZE_HINT_QDEL
src.parent = parent
src.monitor = new(src, parent.kill_range, TRUE, parent)
RegisterSignal(parent, COMSIG_MOVABLE_MOVED, PROC_REF(on_parent_deleted))
RegisterSignal(parent, COMSIG_MOVABLE_Z_CHANGED, PROC_REF(on_parent_z_changed))
RegisterSignal(parent, COMSIG_QDELETING, PROC_REF(on_parent_moved))

/obj/effect/abstract/meteor_shield_proxy/Destroy(force)
QDEL_NULL(monitor)
if(!QDELETED(parent))
if(parent.proxies["[z]"] == src)
parent.proxies -= "[z]"
UnregisterSignal(parent, list(COMSIG_MOVABLE_MOVED, COMSIG_MOVABLE_Z_CHANGED, COMSIG_QDELETING))
parent = null
return ..()

/obj/effect/abstract/meteor_shield_proxy/HasProximity(obj/effect/meteor/meteor)
parent.HasProximity(meteor)

/obj/effect/abstract/meteor_shield_proxy/proc/on_parent_moved()
SIGNAL_HANDLER
var/turf/parent_loc = get_turf(parent)
var/turf/new_loc = locate(parent_loc.x, parent_loc.y, z)
forceMove(new_loc)

/obj/effect/abstract/meteor_shield_proxy/proc/on_parent_z_changed()
SIGNAL_HANDLER
if(!are_zs_connected(parent, src) || z == parent.z)
qdel(src)

/obj/effect/abstract/meteor_shield_proxy/proc/on_parent_deleted()
SIGNAL_HANDLER
qdel(src)
46 changes: 46 additions & 0 deletions monkestation/code/modules/meteor_shield/meteor_shield_zap.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/obj/machinery/satellite/meteor_shield/HasProximity(obj/effect/meteor/meteor)
if(!active || !istype(meteor) || QDELING(meteor) || (obj_flags & EMAGGED))
return
var/turf/our_turf = get_turf(src)
var/turf/meteor_turf = get_turf(meteor)
if(!check_los(our_turf, meteor_turf))
return
our_turf.Beam(meteor_turf, icon_state = "sat_beam", time = 5)
if(meteor.shield_defense(src))
new /obj/effect/temp_visual/explosion(meteor_turf)
INVOKE_ASYNC(src, PROC_REF(play_zap_sound), meteor_turf)
SSblackbox.record_feedback("tally", "meteors_zapped", 1, "[meteor.type]")
meteors_zapped++
GLOB.total_meteors_zapped++
meteor.make_debris()
qdel(meteor)

/obj/machinery/satellite/meteor_shield/proc/check_los(turf/source, turf/target) as num
// if something goes fucky wucky, let's just assume line-of-sight by default
. = TRUE
if(!check_sight)
return TRUE
for(var/turf/segment as anything in get_line(source, target))
if(QDELETED(segment))
continue
if(isclosedturf(segment) && !istransparentturf(segment))
return FALSE

/obj/machinery/satellite/meteor_shield/proc/play_zap_sound(turf/epicenter)
if(QDELETED(epicenter))
return
var/static/near_distance
if(isnull(near_distance))
var/list/world_view = getviewsize(world.view)
near_distance = max(world_view[1], world_view[2])
SSexplosions.shake_the_room(
epicenter,
near_distance,
far_distance = near_distance * 8,
quake_factor = 0,
echo_factor = 0,
creaking = FALSE,
near_sound = sound('sound/weapons/lasercannonfire.ogg'),
far_sound = sound('sound/weapons/marauder.ogg'),
pressure_affected = FALSE
)
Loading

0 comments on commit ec868b2

Please sign in to comment.