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