diff --git a/code/__DEFINES/colors.dm b/code/__DEFINES/colors.dm index 5814f8f27a1fb..c428e1e7dd532 100644 --- a/code/__DEFINES/colors.dm +++ b/code/__DEFINES/colors.dm @@ -164,6 +164,8 @@ #define LIGHT_COLOR_LAVA "#C48A18" /// Bright, non-saturated red. Leaning slightly towards pink for visibility. rgb(250, 100, 75) #define LIGHT_COLOR_FLARE "#FA644B" +/// Vivid red. Leans a bit darker to accentuate red colors and leave other channels a bit dry. rgb(200, 25, 25) +#define LIGHT_COLOR_INTENSE_RED "#C81919" /// Weird color, between yellow and green, very slimy. rgb(175, 200, 75) #define LIGHT_COLOR_SLIME_LAMP "#AFC84B" /// Extremely diluted yellow, close to skin color (for some reason). rgb(250, 225, 175) diff --git a/code/datums/components/weatherannouncer.dm b/code/datums/components/weatherannouncer.dm new file mode 100644 index 0000000000000..3821f9a1b5596 --- /dev/null +++ b/code/datums/components/weatherannouncer.dm @@ -0,0 +1,170 @@ +#define WEATHER_ALERT_CLEAR 0 +#define WEATHER_ALERT_INCOMING 1 +#define WEATHER_ALERT_IMMINENT_OR_ACTIVE 2 + +/// Component which makes you yell about what the weather is +/datum/component/weather_announcer + /// Currently displayed warning level + var/warning_level = WEATHER_ALERT_CLEAR + /// Whether the incoming weather is actually going to harm you + var/is_weather_dangerous = TRUE + /// Are we actually turned on right now? + var/enabled = TRUE + /// Overlay added when things are alright + var/state_normal + /// Overlay added when you should start looking for shelter + var/state_warning + /// Overlay added when you are in danger + var/state_danger + +/datum/component/weather_announcer/Initialize( + state_normal, + state_warning, + state_danger, +) + . = ..() + if (!ismovable(parent)) + return COMPONENT_INCOMPATIBLE + + START_PROCESSING(SSprocessing, src) + RegisterSignal(parent, COMSIG_ATOM_UPDATE_OVERLAYS, PROC_REF(on_update_overlays)) + RegisterSignal(parent, COMSIG_MACHINERY_POWER_RESTORED, PROC_REF(on_powered)) + RegisterSignal(parent, COMSIG_MACHINERY_POWER_LOST, PROC_REF(on_power_lost)) + + src.state_normal = state_normal + src.state_warning = state_warning + src.state_danger = state_danger + var/atom/speaker = parent + speaker.update_appearance(UPDATE_ICON) + update_light_color() + +/datum/component/weather_announcer/Destroy(force, silent) + STOP_PROCESSING(SSprocessing, src) + return ..() + +/// Add appropriate overlays +/datum/component/weather_announcer/proc/on_update_overlays(atom/parent_atom, list/overlays) + SIGNAL_HANDLER + if (!enabled || !state_normal || !state_warning || !state_danger) + return + + switch (warning_level) + if(WEATHER_ALERT_CLEAR) + overlays += state_normal + if(WEATHER_ALERT_INCOMING) + overlays += state_warning + if(WEATHER_ALERT_IMMINENT_OR_ACTIVE) + overlays += (is_weather_dangerous) ? state_danger : state_warning + +/// If powered, receive updates +/datum/component/weather_announcer/proc/on_powered() + SIGNAL_HANDLER + enabled = TRUE + var/atom/speaker = parent + speaker.update_appearance(UPDATE_ICON) + +/// If no power, don't receive updates +/datum/component/weather_announcer/proc/on_power_lost() + SIGNAL_HANDLER + enabled = FALSE + var/atom/speaker = parent + speaker.update_appearance(UPDATE_ICON) + +/datum/component/weather_announcer/process(seconds_per_tick) + if (!enabled) + return + + var/previous_level = warning_level + var/previous_danger = is_weather_dangerous + set_current_alert_level() + if(previous_level == warning_level && previous_danger == is_weather_dangerous) + return // No change + var/atom/movable/speaker = parent + speaker.say(get_warning_message()) + speaker.update_appearance(UPDATE_ICON) + update_light_color() + +/datum/component/weather_announcer/proc/update_light_color() + var/atom/movable/light = parent + switch(warning_level) + if(WEATHER_ALERT_CLEAR) + light.set_light_color(LIGHT_COLOR_GREEN) + if(WEATHER_ALERT_INCOMING) + light.set_light_color(LIGHT_COLOR_YELLOW) + if(WEATHER_ALERT_IMMINENT_OR_ACTIVE) + light.set_light_color(LIGHT_COLOR_INTENSE_RED) + light.update_light() + +/// Returns a string we should display to communicate what you should be doing +/datum/component/weather_announcer/proc/get_warning_message() + if (!is_weather_dangerous) + return "No risk expected from incoming weather front." + switch(warning_level) + if(WEATHER_ALERT_CLEAR) + return "All clear, no weather alerts to report." + if(WEATHER_ALERT_INCOMING) + return "Weather front incoming, begin to seek shelter." + if(WEATHER_ALERT_IMMINENT_OR_ACTIVE) + return "Weather front imminent, find shelter immediately." + return "Error in meteorological calculation. Please report this deviation to a trained programmer." + +/datum/component/weather_announcer/proc/time_till_storm() + var/datum/weather_controller/local_weather_controller = SSmapping.get_map_zone_weather_controller(parent) + if(!local_weather_controller.next_weather) + return null + for(var/type_index in local_weather_controller.current_weathers) + var/datum/weather/check_weather = local_weather_controller.current_weathers[type_index] + if(!check_weather.barometer_predictable || check_weather.stage == WIND_DOWN_STAGE || check_weather.stage == END_STAGE) + continue + warning_level = WEATHER_ALERT_IMMINENT_OR_ACTIVE + return 0 + + var/time_until_next = INFINITY + var/next_time = local_weather_controller.next_weather - world.time || INFINITY + if (next_time && next_time < time_until_next) + time_until_next = next_time + return time_until_next + +/// Polls existing weather for what kind of warnings we should be displaying. +/datum/component/weather_announcer/proc/set_current_alert_level() + var/time_until_next = time_till_storm() + if(isnull(time_until_next)) + return // No problems if there are no mining z levels + if(time_until_next >= 2 MINUTES) + warning_level = WEATHER_ALERT_CLEAR + return + + if(time_until_next >= 30 SECONDS) + warning_level = WEATHER_ALERT_INCOMING + return + + // Weather is here, now we need to figure out if it is dangerous + warning_level = WEATHER_ALERT_IMMINENT_OR_ACTIVE + + var/datum/weather_controller/local_weather_controller = SSmapping.get_map_zone_weather_controller(parent) + for(var/type_index in local_weather_controller.current_weathers) + var/datum/weather/check_weather = local_weather_controller.current_weathers[type_index] + if(!check_weather.barometer_predictable || check_weather.stage == WIND_DOWN_STAGE || check_weather.stage == END_STAGE) + continue + is_weather_dangerous = !check_weather.aesthetic + return + +/datum/component/weather_announcer/proc/on_examine(atom/radio, mob/examiner, list/examine_texts) + var/time_until_next = time_till_storm() + if(isnull(time_until_next)) + return + if (time_until_next == 0) + examine_texts += span_warning ("A storm is currently active, please seek shelter.") + else + examine_texts += span_notice("The next storm is inbound in [DisplayTimeText(time_until_next)].") + +/datum/component/weather_announcer/RegisterWithParent() + RegisterSignal(parent, COMSIG_PARENT_EXAMINE, PROC_REF(on_examine)) + +/datum/component/weather_announcer/UnregisterFromParent() + .=..() + UnregisterSignal(parent, COMSIG_PARENT_EXAMINE) + +#undef WEATHER_ALERT_CLEAR +#undef WEATHER_ALERT_INCOMING +#undef WEATHER_ALERT_IMMINENT_OR_ACTIVE diff --git a/code/modules/jobs/job_types/shaft_miner.dm b/code/modules/jobs/job_types/shaft_miner.dm index 6a3f13da7c780..cc5ec142932ee 100644 --- a/code/modules/jobs/job_types/shaft_miner.dm +++ b/code/modules/jobs/job_types/shaft_miner.dm @@ -27,7 +27,8 @@ backpack_contents = list( /obj/item/flashlight/seclite=1,\ /obj/item/kitchen/knife/combat/survival=1,\ - /obj/item/stack/marker_beacon/ten=1) + /obj/item/stack/marker_beacon/ten=1,\ + /obj/item/radio/weather_monitor=1) backpack = /obj/item/storage/backpack/explorer satchel = /obj/item/storage/backpack/satchel/explorer diff --git a/code/modules/mining/equipment/miningradio.dm b/code/modules/mining/equipment/miningradio.dm new file mode 100644 index 0000000000000..a0bef397d8cac --- /dev/null +++ b/code/modules/mining/equipment/miningradio.dm @@ -0,0 +1,23 @@ +/// Portable mining radio purchasable by miners +/obj/item/radio/weather_monitor + icon = 'icons/obj/miningradio.dmi' + name = "mining weather radio" + icon_state = "miningradio" + desc = "A weather radio designed for use in inhospitable environments. Gives audible warnings when storms approach." + luminosity = 1 + light_power = 1 + light_range = 1.6 + +/obj/item/radio/weather_monitor/update_overlays() + . = ..() + . += emissive_appearance(icon, "small_emissive", src, alpha = src.alpha) + +/obj/item/radio/weather_monitor/Initialize(mapload) + . = ..() + AddComponent( \ + /datum/component/weather_announcer, \ + state_normal = "weatherwarning", \ + state_warning = "urgentwarning", \ + state_danger = "direwarning", \ + ) + set_frequency(FREQ_COMMON) diff --git a/code/modules/research/designs/mining_designs.dm b/code/modules/research/designs/mining_designs.dm index cf4ba7b9fa419..2cddc5043c3f9 100644 --- a/code/modules/research/designs/mining_designs.dm +++ b/code/modules/research/designs/mining_designs.dm @@ -120,3 +120,13 @@ build_path = /obj/item/borg/upgrade/modkit/aoe/turfs category = list("Mining Designs", "Cyborg Upgrade Modules") departmental_flags = DEPARTMENTAL_FLAG_CARGO + +/datum/design/weather_monitor + name = "Weather Radio" + desc = "A weather radio designed for use in inhospitable environments. Gives audible warnings when storms approach." + id = "weatherradio" + build_type = PROTOLATHE + materials = list(/datum/material/iron=75, /datum/material/glass=25) + build_path = /obj/item/radio/weather_monitor + category = list("Mining Designs") + departmental_flags = DEPARTMENTAL_FLAG_CARGO diff --git a/code/modules/research/techweb/all_nodes.dm b/code/modules/research/techweb/all_nodes.dm index 4f9d389c5a386..79009ed1fdf63 100644 --- a/code/modules/research/techweb/all_nodes.dm +++ b/code/modules/research/techweb/all_nodes.dm @@ -559,7 +559,7 @@ display_name = "Mining Technology" description = "Better than Efficiency V." prereq_ids = list("engineering", "basic_plasma") - design_ids = list("drill", "superresonator", "triggermod", "damagemod", "cooldownmod", "rangemod", "ore_redemption", "mining_equipment_vendor", "cargoexpress", "plasmacutter", "mecha_kineticgun")//e a r l y g a m e) + design_ids = list("drill", "superresonator", "triggermod", "damagemod", "cooldownmod", "rangemod", "ore_redemption", "mining_equipment_vendor", "cargoexpress", "plasmacutter", "mecha_kineticgun", "weatherradio")//e a r l y g a m e) research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500) export_price = 5000 diff --git a/icons/obj/miningradio.dmi b/icons/obj/miningradio.dmi new file mode 100644 index 0000000000000..08b6052e91a0c Binary files /dev/null and b/icons/obj/miningradio.dmi differ diff --git a/shiptest.dme b/shiptest.dme index 4244191d5dc2b..919c61c53c36f 100644 --- a/shiptest.dme +++ b/shiptest.dme @@ -530,6 +530,7 @@ #include "code\datums\components\udder.dm" #include "code\datums\components\uplink.dm" #include "code\datums\components\wearertargeting.dm" +#include "code\datums\components\weatherannouncer.dm" #include "code\datums\components\wet_floor.dm" #include "code\datums\components\crafting\crafting.dm" #include "code\datums\components\crafting\guncrafting.dm" @@ -2365,6 +2366,7 @@ #include "code\modules\mining\equipment\marker_beacons.dm" #include "code\modules\mining\equipment\mineral_scanner.dm" #include "code\modules\mining\equipment\mining_tools.dm" +#include "code\modules\mining\equipment\miningradio.dm" #include "code\modules\mining\equipment\regenerative_core.dm" #include "code\modules\mining\equipment\resonator.dm" #include "code\modules\mining\equipment\survival_pod.dm"