diff --git a/code/datums/looping_sounds/vents.dm b/code/datums/looping_sounds/vents.dm new file mode 100644 index 00000000000..2d0a3443631 --- /dev/null +++ b/code/datums/looping_sounds/vents.dm @@ -0,0 +1,7 @@ +/datum/looping_sound/vent_pump_overclock + start_sound = 'sound/machines/fan_start.ogg' + start_length = 1.5 SECONDS + end_sound = 'sound/machines/fan_stop.ogg' + end_sound = 1.5 SECONDS + mid_sounds = 'sound/machines/fan_loop.ogg' + mid_length = 2 SECONDS diff --git a/code/game/atom_defense.dm b/code/game/atom_defense.dm index bced6cb3bf1..f5759110a6d 100644 --- a/code/game/atom_defense.dm +++ b/code/game/atom_defense.dm @@ -51,6 +51,13 @@ if(atom_integrity == new_value) return atom_integrity = new_value + on_update_integrity(old_value, new_value) + return new_value + +/// Handle updates to your atom's integrity +/atom/proc/on_update_integrity(old_value, new_value) + SHOULD_NOT_SLEEP(TRUE) + SHOULD_CALL_PARENT(TRUE) SEND_SIGNAL(src, COMSIG_ATOM_INTEGRITY_CHANGED, old_value, new_value) /// This mostly exists to keep atom_integrity private. Might be useful in the future. @@ -58,6 +65,11 @@ SHOULD_BE_PURE(TRUE) return atom_integrity +/// Similar to get_integrity, but returns the percentage as [0-1] instead. +/atom/proc/get_integrity_percentage() + SHOULD_BE_PURE(TRUE) + return round(atom_integrity / max_integrity, 0.01) + ///returns the damage value of the attack after processing the atom's various armor protections /atom/proc/run_atom_armor(damage_amount, damage_type, damage_flag = 0, attack_dir, armour_penetration = 0) if(!uses_integrity) diff --git a/code/modules/atmospherics/machinery/air_alarm/_air_alarm.dm b/code/modules/atmospherics/machinery/air_alarm/_air_alarm.dm index e629c14e0fe..24d1e63e42a 100644 --- a/code/modules/atmospherics/machinery/air_alarm/_air_alarm.dm +++ b/code/modules/atmospherics/machinery/air_alarm/_air_alarm.dm @@ -287,6 +287,8 @@ GLOBAL_LIST_EMPTY_TYPED(air_alarms, /obj/machinery/airalarm) "refID" = REF(vent), "long_name" = sanitize(vent.name), "power" = vent.on, + "overclock" = vent.fan_overclocked, + "integrity" = vent.get_integrity_percentage(), "checks" = vent.pressure_checks, "excheck" = vent.pressure_checks & ATMOS_EXTERNAL_BOUND, "incheck" = vent.pressure_checks & ATMOS_INTERNAL_BOUND, @@ -356,6 +358,14 @@ GLOBAL_LIST_EMPTY_TYPED(air_alarms, /obj/machinery/airalarm) powering.on = !!params["val"] powering.atmos_conditions_changed() powering.update_appearance(UPDATE_ICON) + + if("overclock") + if(isnull(vent)) + return TRUE + vent.toggle_overclock() + vent.update_appearance(UPDATE_ICON) + return TRUE + if ("direction") if (isnull(vent)) return TRUE diff --git a/code/modules/atmospherics/machinery/components/unary_devices/vent_pump.dm b/code/modules/atmospherics/machinery/components/unary_devices/vent_pump.dm index 3422f3e3adf..25713e55e37 100644 --- a/code/modules/atmospherics/machinery/components/unary_devices/vent_pump.dm +++ b/code/modules/atmospherics/machinery/components/unary_devices/vent_pump.dm @@ -15,6 +15,8 @@ shift_underlay_only = FALSE pipe_state = "uvent" vent_movement = VENTCRAWL_ALLOWED | VENTCRAWL_CAN_SEE | VENTCRAWL_ENTRANCE_ALLOWED + // vents are more complex machinery and so are less resistant to damage + max_integrity = 100 ///Direction of pumping the gas (ATMOS_DIRECTION_RELEASING or ATMOS_DIRECTION_SIPHONING) var/pump_direction = ATMOS_DIRECTION_RELEASING @@ -34,6 +36,18 @@ ///area this vent is assigned to var/area/assigned_area + /// Is this vent currently overclocked, removing pressure limits but damaging the fan? + var/fan_overclocked = FALSE + + /// Rate of damage per atmos process to the fan when overclocked. Set to 0 to disable damage. + var/fan_damage_rate = 0.5 + + /// The cached string we show for examine that lets you know how fucked up the fan is. + var/examine_condition + + /// Datum for managing the overclock sound loop + var/datum/looping_sound/vent_pump_overclock/sound_loop + /obj/machinery/atmospherics/components/unary/vent_pump/Initialize(mapload) if(!id_tag) id_tag = assign_random_name() @@ -42,16 +56,44 @@ tool_screentips = string_assoc_nested_list(list( TOOL_MULTITOOL = list( SCREENTIP_CONTEXT_LMB = "Log to link later with air sensor", - ) + ), + TOOL_SCREWDRIVER = list( + SCREENTIP_CONTEXT_LMB = "Repair", + ), )) AddElement(/datum/element/contextual_screentip_tools, tool_screentips) . = ..() + sound_loop = new(src) assign_to_area() +/obj/machinery/atmospherics/components/unary/vent_pump/on_update_integrity(old_value, new_value) + . = ..() + var/condition_string + switch(get_integrity_percentage()) + if(1) + condition_string = "perfect" + if(0.75 to 0.99) + condition_string = "good" + if(0.50 to 0.74) + condition_string = "okay" + if(0.25 to 0.49) + condition_string = "bad" + else + condition_string = "terrible" + examine_condition = "The fan is in [condition_string] condition." + /obj/machinery/atmospherics/components/unary/vent_pump/examine(mob/user) . = ..() . += span_notice("You can link it with an air sensor using a multitool.") + if(fan_overclocked) + . += span_warning("It is currently overclocked causing it to take damage over time.") + + if(get_integrity() > 0) + . += span_notice(examine_condition) + else + . += span_warning("The fan is broken.") + /obj/machinery/atmospherics/components/unary/vent_pump/multitool_act(mob/living/user, obj/item/multitool/multi_tool) if(istype(multi_tool.buffer, /obj/machinery/air_sensor)) var/obj/machinery/air_sensor/sensor = multi_tool.buffer @@ -63,8 +105,33 @@ multi_tool.set_buffer(src) return TOOL_ACT_TOOLTYPE_SUCCESS +/obj/machinery/atmospherics/components/unary/vent_pump/screwdriver_act(mob/living/user, obj/item/tool) + var/time_to_repair = (10 SECONDS) * (1 - get_integrity_percentage()) + if(!time_to_repair) + return FALSE + + balloon_alert(user, "repairing vent...") + if(do_after(user, time_to_repair, src)) + balloon_alert(user, "vent repaired") + repair_damage(max_integrity) + + else + balloon_alert(user, "interrupted!") + return TOOL_ACT_TOOLTYPE_SUCCESS + +/obj/machinery/atmospherics/components/unary/vent_pump/atom_fix() + set_is_operational(TRUE) + update_appearance() + return ..() + +/obj/machinery/atmospherics/components/unary/vent_pump/atom_break(damage_flag) + set_is_operational(FALSE) + update_appearance() + return ..() + /obj/machinery/atmospherics/components/unary/vent_pump/Destroy() disconnect_from_area() + QDEL_NULL(sound_loop) var/area/vent_area = get_area(src) if(vent_area) @@ -107,6 +174,17 @@ . = ..() disconnect_from_area(area_to_unregister) +/obj/machinery/atmospherics/components/unary/vent_pump/update_overlays() + . = ..() + if(!powered()) + return + + if(get_integrity() <= 0) + . += mutable_appearance(icon, "broken") + + else if(fan_overclocked) + . += mutable_appearance(icon, "overclocked") + /obj/machinery/atmospherics/components/unary/vent_pump/update_icon_nopipes() cut_overlays() if(showpipe) @@ -144,6 +222,20 @@ else // pump_direction == SIPHONING icon_state = "vent_in" +/obj/machinery/atmospherics/components/unary/vent_pump/proc/toggle_overclock(from_break = FALSE) + fan_overclocked = !fan_overclocked + + if(from_break) + playsound(src, 'sound/machines/fan_break.ogg', 100) + fan_overclocked = FALSE + + if(fan_overclocked) + sound_loop.start() + else + sound_loop.stop() + + update_appearance() + /obj/machinery/atmospherics/components/unary/vent_pump/process_atmos() if(!is_operational) return @@ -154,6 +246,17 @@ var/turf/open/us = loc if(!istype(us)) return + + var/current_integrity = get_integrity() + if(fan_overclocked) + take_damage(fan_damage_rate, sound_effect=FALSE) + if(current_integrity == 0) + on = FALSE + set_is_operational(FALSE) + toggle_overclock(from_break = TRUE) + return + + var/percent_integrity = get_integrity_percentage() var/datum/gas_mixture/air_contents = airs[1] var/datum/gas_mixture/environment = us.return_air() var/environment_pressure = environment.return_pressure() @@ -168,9 +271,13 @@ if(pressure_delta > 0) if(air_contents.temperature > 0) - if(environment_pressure >= 50 * ONE_ATMOSPHERE) + if(!fan_overclocked && (environment_pressure >= 50 * ONE_ATMOSPHERE)) return FALSE + var/transfer_moles = (pressure_delta * environment.volume) / (air_contents.temperature * R_IDEAL_GAS_EQUATION) + if(!fan_overclocked && (percent_integrity < 1)) + transfer_moles *= percent_integrity + var/datum/gas_mixture/removed = air_contents.remove(transfer_moles) if(!removed || !removed.total_moles()) @@ -187,9 +294,12 @@ pressure_delta = min(pressure_delta, (internal_pressure_bound - air_contents.return_pressure())) if(pressure_delta > 0 && environment.temperature > 0) - if(air_contents.return_pressure() >= 50 * ONE_ATMOSPHERE) + if(!fan_overclocked && (air_contents.return_pressure() >= 50 * ONE_ATMOSPHERE)) return FALSE + var/transfer_moles = (pressure_delta * air_contents.volume) / (environment.temperature * R_IDEAL_GAS_EQUATION) + if(!fan_overclocked && (percent_integrity < 1)) + transfer_moles *= percent_integrity var/datum/gas_mixture/removed = loc.remove_air(transfer_moles) diff --git a/icons/obj/machines/atmospherics/unary_devices.dmi b/icons/obj/machines/atmospherics/unary_devices.dmi index 3350500fde9..6a929f211b8 100644 Binary files a/icons/obj/machines/atmospherics/unary_devices.dmi and b/icons/obj/machines/atmospherics/unary_devices.dmi differ diff --git a/sound/machines/fan_break.ogg b/sound/machines/fan_break.ogg new file mode 100644 index 00000000000..ca0549333ad Binary files /dev/null and b/sound/machines/fan_break.ogg differ diff --git a/sound/machines/fan_loop.ogg b/sound/machines/fan_loop.ogg new file mode 100644 index 00000000000..9c7820548f6 Binary files /dev/null and b/sound/machines/fan_loop.ogg differ diff --git a/sound/machines/fan_start.ogg b/sound/machines/fan_start.ogg new file mode 100644 index 00000000000..a0d11c3e969 Binary files /dev/null and b/sound/machines/fan_start.ogg differ diff --git a/sound/machines/fan_stop.ogg b/sound/machines/fan_stop.ogg new file mode 100644 index 00000000000..84d39c3ee5a Binary files /dev/null and b/sound/machines/fan_stop.ogg differ diff --git a/tgstation.dme b/tgstation.dme index 93735c4b914..bb2da82fed3 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -1459,6 +1459,7 @@ #include "code\datums\looping_sounds\item_sounds.dm" #include "code\datums\looping_sounds\machinery_sounds.dm" #include "code\datums\looping_sounds\music.dm" +#include "code\datums\looping_sounds\vents.dm" #include "code\datums\looping_sounds\weather.dm" #include "code\datums\mapgen\_MapGenerator.dm" #include "code\datums\mapgen\CaveGenerator.dm" diff --git a/tgui/packages/tgui/interfaces/common/AtmosControls.tsx b/tgui/packages/tgui/interfaces/common/AtmosControls.tsx index 099de593558..e67f7ba385e 100644 --- a/tgui/packages/tgui/interfaces/common/AtmosControls.tsx +++ b/tgui/packages/tgui/interfaces/common/AtmosControls.tsx @@ -8,6 +8,8 @@ export type VentProps = { refID: string; long_name: string; power: BooleanLike; + overclock: BooleanLike; + integrity: number; checks: number; excheck: BooleanLike; incheck: BooleanLike; @@ -37,6 +39,8 @@ export const Vent = (props: VentProps, context) => { refID, long_name, power, + overclock, + integrity, checks, excheck, incheck, @@ -50,19 +54,41 @@ export const Vent = (props: VentProps, context) => {
- act('power', { - ref: refID, - val: Number(!power), - }) - } - /> + <> +