diff --git a/code/game/machinery/computer/arcade/arcade.dm b/code/game/machinery/computer/arcade/arcade.dm index 74ad9b711e3..51b623e91bf 100644 --- a/code/game/machinery/computer/arcade/arcade.dm +++ b/code/game/machinery/computer/arcade/arcade.dm @@ -542,7 +542,7 @@ GLOBAL_LIST_INIT(arcade_prize_pool, list( playsound(loc, 'sound/arcade/win.ogg', 50, TRUE) if(obj_flags & EMAGGED) - new /obj/effect/spawner/newbomb/timer/syndicate(loc) + new /obj/effect/spawner/newbomb/timer(loc) new /obj/item/clothing/head/collectable/petehat(loc) message_admins("[ADMIN_LOOKUPFLW(usr)] has outbombed Cuban Pete and been awarded a bomb.") log_game("[key_name(usr)] has outbombed Cuban Pete and been awarded a bomb.") diff --git a/code/game/objects/effects/spawners/bombspawner.dm b/code/game/objects/effects/spawners/bombspawner.dm index 67b09b786fb..65f32aadcbf 100644 --- a/code/game/objects/effects/spawners/bombspawner.dm +++ b/code/game/objects/effects/spawners/bombspawner.dm @@ -1,67 +1,57 @@ -#define CELSIUS_TO_KELVIN(T_K) ((T_K) + T0C) - -#define OPTIMAL_TEMP_K_PLA_BURN_SCALE(PRESSURE_P,PRESSURE_O,TEMP_O) (((PRESSURE_P) * GLOB.meta_gas_info[/datum/gas/plasma][META_GAS_SPECIFIC_HEAT]) / (((PRESSURE_P) * GLOB.meta_gas_info[/datum/gas/plasma][META_GAS_SPECIFIC_HEAT] + (PRESSURE_O) * GLOB.meta_gas_info[/datum/gas/oxygen][META_GAS_SPECIFIC_HEAT]) / PLASMA_UPPER_TEMPERATURE - (PRESSURE_O) * GLOB.meta_gas_info[/datum/gas/oxygen][META_GAS_SPECIFIC_HEAT] / CELSIUS_TO_KELVIN(TEMP_O))) -#define OPTIMAL_TEMP_K_PLA_BURN_RATIO(PRESSURE_P,PRESSURE_O,TEMP_O) (CELSIUS_TO_KELVIN(TEMP_O) * PLASMA_OXYGEN_FULLBURN * (PRESSURE_P) / (PRESSURE_O)) - +/** + * Spawns a TTV. + * + */ /obj/effect/spawner/newbomb name = "bomb" icon = 'icons/hud/screen_gen.dmi' icon_state = "x" - var/temp_p = 1500 - var/temp_o = 1000 // tank temperatures - var/pressure_p = 10 * ONE_ATMOSPHERE - var/pressure_o = 10 * ONE_ATMOSPHERE //tank pressures + /// The initial temperature of the plasma tank. + var/temp_p = 1413 + /// The initial temperature of the oxygen tank. + var/temp_o = 141.3 + /// The initial pressure of the plasma tank. + var/pressure_p = TANK_LEAK_PRESSURE - 1 + /// The initial pressure of the oxygen tank. + var/pressure_o = TANK_LEAK_PRESSURE - 1 + /// The typepath of the assembly to attach to the TTV. var/assembly_type /obj/effect/spawner/newbomb/Initialize() . = ..() - var/obj/item/transfer_valve/V = new(src.loc) - var/obj/item/tank/internals/plasma/PT = new(V) - var/obj/item/tank/internals/oxygen/OT = new(V) + var/obj/item/transfer_valve/ttv = new(loc) + var/obj/item/tank/internals/plasma/plasma_tank = new(ttv) + var/obj/item/tank/internals/oxygen/oxygen_tank = new(ttv) - PT.air_contents.assert_gas(/datum/gas/plasma) - PT.air_contents.gases[/datum/gas/plasma][MOLES] = pressure_p*PT.volume/(R_IDEAL_GAS_EQUATION*CELSIUS_TO_KELVIN(temp_p)) - PT.air_contents.temperature = CELSIUS_TO_KELVIN(temp_p) + var/datum/gas_mixture/plasma_mix = plasma_tank.air_contents + plasma_mix.assert_gas(/datum/gas/plasma) + plasma_mix.gases[/datum/gas/plasma][MOLES] = pressure_p*plasma_mix.volume/(R_IDEAL_GAS_EQUATION*temp_p) + plasma_mix.temperature = temp_p - OT.air_contents.assert_gas(/datum/gas/oxygen) - OT.air_contents.gases[/datum/gas/oxygen][MOLES] = pressure_o*OT.volume/(R_IDEAL_GAS_EQUATION*CELSIUS_TO_KELVIN(temp_o)) - OT.air_contents.temperature = CELSIUS_TO_KELVIN(temp_o) + var/datum/gas_mixture/oxygen_mix = oxygen_tank.air_contents + oxygen_mix.assert_gas(/datum/gas/oxygen) + oxygen_mix.gases[/datum/gas/oxygen][MOLES] = pressure_o*oxygen_mix.volume/(R_IDEAL_GAS_EQUATION*temp_o) + oxygen_mix.temperature = temp_o - V.tank_one = PT - V.tank_two = OT - PT.master = V - OT.master = V + ttv.tank_one = plasma_tank + ttv.tank_two = oxygen_tank + plasma_tank.master = ttv + oxygen_tank.master = ttv if(assembly_type) - var/obj/item/assembly/A = new assembly_type(V) - V.attached_device = A - A.holder = V + var/obj/item/assembly/detonator = new assembly_type(ttv) + ttv.attached_device = detonator + detonator.holder = ttv - V.update_appearance() + ttv.update_appearance() return INITIALIZE_HINT_QDEL -/obj/effect/spawner/newbomb/timer/syndicate/Initialize() - temp_p = (OPTIMAL_TEMP_K_PLA_BURN_SCALE(pressure_p, pressure_o, temp_o)/2 + OPTIMAL_TEMP_K_PLA_BURN_RATIO(pressure_p, pressure_o, temp_o)/2) - T0C - . = ..() - /obj/effect/spawner/newbomb/timer assembly_type = /obj/item/assembly/timer -/obj/effect/spawner/newbomb/timer/syndicate - pressure_o = TANK_LEAK_PRESSURE - 1 - temp_o = 20 - - pressure_p = TANK_LEAK_PRESSURE - 1 - /obj/effect/spawner/newbomb/proximity assembly_type = /obj/item/assembly/prox_sensor /obj/effect/spawner/newbomb/radio assembly_type = /obj/item/assembly/signaler - - -#undef CELSIUS_TO_KELVIN - -#undef OPTIMAL_TEMP_K_PLA_BURN_SCALE -#undef OPTIMAL_TEMP_K_PLA_BURN_RATIO diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm index 5e0475fdc32..8cecf0c3440 100644 --- a/code/game/objects/items.dm +++ b/code/game/objects/items.dm @@ -232,7 +232,7 @@ GLOBAL_VAR_INIT(embedpocalypse, FALSE) // if true, all items will be able to emb /obj/item/blob_act(obj/structure/blob/B) if(B && B.loc == loc) - qdel(src) + obj_destruction(MELEE) /obj/item/ComponentInitialize() . = ..() diff --git a/code/game/objects/items/tanks/tanks.dm b/code/game/objects/items/tanks/tanks.dm index 9d70490febb..cc645e6e3e3 100644 --- a/code/game/objects/items/tanks/tanks.dm +++ b/code/game/objects/items/tanks/tanks.dm @@ -1,3 +1,12 @@ +/// How much time (in seconds) is assumed to pass while assuming air. Used to scale overpressure/overtemp damage when assuming air. +#define ASSUME_AIR_DT_FACTOR 1 + +/** + * # Gas Tank + * + * Handheld gas canisters + * Can rupture explosively if overpressurized + */ /obj/item/tank name = "tank" icon = 'icons/obj/tank.dmi' @@ -16,10 +25,15 @@ custom_materials = list(/datum/material/iron = 500) actions_types = list(/datum/action/item_action/set_internals) armor = list(MELEE = 0, BULLET = 0, LASER = 0, ENERGY = 0, BOMB = 10, BIO = 0, RAD = 0, FIRE = 80, ACID = 30) + integrity_failure = 0.5 + /// The gases this tank contains. var/datum/gas_mixture/air_contents = null - var/distribute_pressure = ONE_ATMOSPHERE - var/integrity = 3 + /// The volume of this tank. var/volume = 70 + /// Whether the tank is currently leaking. + var/leaking = FALSE + /// The pressure of the gases this tank supplies to internals. + var/distribute_pressure = ONE_ATMOSPHERE /// Icon state when in a tank holder. Null makes it incompatible with tank holder. var/tank_holder_icon_state = "holder_generic" @@ -74,7 +88,7 @@ QDEL_NULL(air_contents) STOP_PROCESSING(SSobj, src) - . = ..() + return ..() /obj/item/tank/ComponentInitialize() . = ..() @@ -111,25 +125,13 @@ . += "It feels [descriptive]." -/obj/item/tank/blob_act(obj/structure/blob/B) - if(B && B.loc == loc) - var/turf/location = get_turf(src) - if(!location) - qdel(src) - - if(air_contents) - location.assume_air(air_contents) - - qdel(src) - /obj/item/tank/deconstruct(disassembled = TRUE) - if(!disassembled) - var/turf/T = get_turf(src) - if(T) - T.assume_air(air_contents) - air_update_turf(FALSE, FALSE) - playsound(src.loc, 'sound/effects/spray.ogg', 10, TRUE, -3) - qdel(src) + var/turf/location = get_turf(src) + if(location) + location.assume_air(air_contents) + location.air_update_turf(FALSE, FALSE) + playsound(location, 'sound/effects/spray.ogg', 10, TRUE, -3) + return ..() /obj/item/tank/suicide_act(mob/user) var/mob/living/carbon/human/H = user @@ -146,9 +148,9 @@ /obj/item/tank/attackby(obj/item/W, mob/user, params) add_fingerprint(user) if(istype(W, /obj/item/assembly_holder)) - bomb_assemble(W,user) - else - . = ..() + bomb_assemble(W, user) + return TRUE + return ..() /obj/item/tank/ui_state(mob/user) return GLOB.hands_state @@ -213,10 +215,15 @@ /obj/item/tank/assume_air(datum/gas_mixture/giver) air_contents.merge(giver) + handle_tolerances(ASSUME_AIR_DT_FACTOR) + return TRUE - check_status() - return 1 - +/** + * Removes some volume of the tanks gases as the tanks distribution pressure. + * + * Arguments: + * - volume_to_return: The amount of volume to remove from the tank. + */ /obj/item/tank/proc/remove_air_volume(volume_to_return) if(!air_contents) return null @@ -235,59 +242,78 @@ return remove_air(moles_needed) -/obj/item/tank/process() - //Allow for reactions - air_contents.react() - check_status() - -/obj/item/tank/proc/check_status() - //Handle exploding, leaking, and rupturing of the tank - +/obj/item/tank/process(delta_time) if(!air_contents) - return 0 + return + + //Allow for reactions + air_contents.react(src) + handle_tolerances(delta_time) + if(QDELETED(src) || !leaking || !air_contents) + return + var/turf/location = get_turf(src) + if(!location) + return + var/datum/gas_mixture/leaked_gas = air_contents.remove_ratio(0.25) + location.assume_air(leaked_gas) + location.air_update_turf(FALSE, FALSE) + +/** + * Handles the minimum and maximum pressure tolerances of the tank. + * + * Arguments: + * - delta_time: How long has passed between ticks. + */ +/obj/item/tank/proc/handle_tolerances(delta_time) + if(!air_contents) + return FALSE var/pressure = air_contents.return_pressure() var/temperature = air_contents.return_temperature() + if(temperature >= TANK_MELT_TEMPERATURE) + var/temperature_damage_ratio = (temperature - TANK_MELT_TEMPERATURE) / temperature + take_damage(max_integrity * temperature_damage_ratio * delta_time, BURN, FIRE, FALSE, NONE) + if(QDELETED(src)) + return TRUE + if(pressure >= TANK_LEAK_PRESSURE) + var/pressure_damage_ratio = (pressure - TANK_LEAK_PRESSURE) / (TANK_RUPTURE_PRESSURE - TANK_LEAK_PRESSURE) + take_damage(max_integrity * pressure_damage_ratio * delta_time, BRUTE, BOMB, FALSE, NONE) + return TRUE + +/// Handles the tank springing a leak. +/obj/item/tank/obj_break(damage_flag) + . = ..() + if(leaking) + return + + leaking = TRUE + if(obj_integrity < 0) // So we don't play the alerts while we are exploding or rupturing. + return + visible_message("[src] springs a leak!") + playsound(src, 'sound/effects/spray.ogg', 10, TRUE, -3) + +/// Handles rupturing and fragmenting +/obj/item/tank/obj_destruction(damage_flag) + if(!air_contents) + return ..() + + var/turf/location = get_turf(src) + if(!location) + return ..() + + /// Handle fragmentation + var/pressure = air_contents.return_pressure() if(pressure > TANK_FRAGMENT_PRESSURE) - if(!istype(src.loc, /obj/item/transfer_valve)) + if(!istype(loc, /obj/item/transfer_valve)) log_bomber(get_mob_by_key(fingerprintslast), "was last key to touch", src, "which ruptured explosively") //Give the gas a chance to build up more pressure through reacting air_contents.react(src) pressure = air_contents.return_pressure() var/range = (pressure-TANK_FRAGMENT_PRESSURE)/TANK_FRAGMENT_SCALE - var/turf/epicenter = get_turf(loc) - - explosion(epicenter, round(range*0.25), round(range*0.5), round(range), round(range*1.5)) - if(istype(src.loc, /obj/item/transfer_valve)) - qdel(src.loc) - else - qdel(src) - - else if(pressure > TANK_RUPTURE_PRESSURE || temperature > TANK_MELT_TEMPERATURE) - if(integrity <= 0) - var/turf/T = get_turf(src) - if(!T) - return - T.assume_air(air_contents) - playsound(src.loc, 'sound/effects/spray.ogg', 10, TRUE, -3) - qdel(src) - else - integrity-- - - else if(pressure > TANK_LEAK_PRESSURE) - if(integrity <= 0) - var/turf/T = get_turf(src) - if(!T) - return - var/datum/gas_mixture/leaked_gas = air_contents.remove_ratio(0.25) - T.assume_air(leaked_gas) - else - integrity-- - - else if(integrity < 3) - integrity++ + explosion(location, round(range*0.25), round(range*0.5), round(range), round(range*1.5)) + return ..() /obj/item/tank/rad_act(strength) . = ..() @@ -312,3 +338,5 @@ if(gas_change) air_contents.garbage_collect() + +#undef ASSUME_AIR_DT_FACTOR diff --git a/code/modules/projectiles/guns/misc/blastcannon.dm b/code/modules/projectiles/guns/misc/blastcannon.dm index 51739ea171c..52a4cb622b7 100644 --- a/code/modules/projectiles/guns/misc/blastcannon.dm +++ b/code/modules/projectiles/guns/misc/blastcannon.dm @@ -69,7 +69,7 @@ if(!istype(bomb_to_attach)) return ..() - if(!bomb_to_attach.tank_one || !bomb_to_attach.tank_two) + if(!bomb_to_attach.ready()) to_chat(user, "What good would an incomplete bomb do?") return FALSE if(!user.transferItemToLoc(bomb_to_attach, src)) @@ -83,7 +83,7 @@ /// Handles the bomb power calculations /obj/item/gun/blastcannon/proc/calculate_bomb() - if(!istype(bomb) || !istype(bomb.tank_one) || !istype(bomb.tank_two)) + if(!istype(bomb) || !bomb.ready()) return 0 var/datum/gas_mixture/temp = new(max(reaction_volume_mod, 0)) @@ -98,7 +98,6 @@ temp.react(src) var/pressure = temp.return_pressure() - qdel(temp) if(pressure < TANK_FRAGMENT_PRESSURE) return 0 return ((pressure - TANK_FRAGMENT_PRESSURE) / TANK_FRAGMENT_SCALE) diff --git a/code/modules/research/anomaly/explosive_compressor.dm b/code/modules/research/anomaly/explosive_compressor.dm index faf01f1e0c1..5b5573f0f41 100644 --- a/code/modules/research/anomaly/explosive_compressor.dm +++ b/code/modules/research/anomaly/explosive_compressor.dm @@ -114,12 +114,8 @@ /obj/machinery/research/explosive_compressor/proc/do_implosion() var/required_radius = get_required_radius(inserted_core.anomaly_type) // By now, we should be sure that we have a core, a TTV, and that the TTV has both tanks in place. - var/datum/gas_mixture/mix1 = inserted_bomb.tank_one.air_contents - var/datum/gas_mixture/mix2 = inserted_bomb.tank_two.air_contents - // Snowflaked tank explosion - var/datum/gas_mixture/mix = new(70) // Standard tank volume, 70L - mix.merge(mix1) - mix.merge(mix2) + var/datum/gas_mixture/mix = new(0) + inserted_bomb.merge_gases(mix) mix.react() if(mix.return_pressure() < TANK_FRAGMENT_PRESSURE) // They failed so miserably we're going to give them their bomb back. @@ -128,18 +124,21 @@ inserted_core.forceMove(drop_location()) inserted_core = null say("Transfer valve resulted in negligible explosive power. Items ejected.") - return + return FALSE mix.react() // build more pressure - var/pressure = mix.return_pressure() - var/range = (pressure - TANK_FRAGMENT_PRESSURE) / TANK_FRAGMENT_SCALE - if(range < required_radius) - inserted_bomb.forceMove(src) - say("Resultant detonation failed to produce enough implosive power to compress [inserted_core]. Core ejected.") - return + + var/range = (mix.return_pressure() - TANK_FRAGMENT_PRESSURE) / TANK_FRAGMENT_SCALE QDEL_NULL(inserted_bomb) // bomb goes poof + if(range < required_radius) + say("Resultant detonation failed to produce enough implosive power to compress [inserted_core]. Core ejected.") + inserted_core.forceMove(drop_location()) + inserted_core = null + return FALSE + inserted_core.create_core(drop_location(), TRUE, TRUE) inserted_core = null say("Success. Resultant detonation has theoretical range of [range]. Required radius was [required_radius]. Core production complete.") + return TRUE #undef MAX_RADIUS_REQUIRED #undef MIN_RADIUS_REQUIRED