diff --git a/code/game/objects/items/tanks/tanks.dm b/code/game/objects/items/tanks/tanks.dm index 6b84407ed30..6c11c435507 100644 --- a/code/game/objects/items/tanks/tanks.dm +++ b/code/game/objects/items/tanks/tanks.dm @@ -1,5 +1,9 @@ /// 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 +/// Multiplies the pressure of assembly bomb explosions before it's put through THE LOGARITHM +#define ASSEMBLY_BOMB_COEFFICIENT 0.5 +/// Base of the logarithmic function used to calculate assembly bomb explosion size +#define ASSEMBLY_BOMB_BASE 2.7 /** * # Gas Tank @@ -48,6 +52,10 @@ var/list/reaction_info /// Mob that is currently breathing from the tank. var/mob/living/carbon/breathing_mob = null + /// Attached assembly, can either detonate the tank or release its contents when receiving a signal + var/obj/item/assembly_holder/tank_assembly + /// Whether or not it will try to explode when it receives a signal + var/bomb_status = FALSE /// Closes the tank if dropped while open. /datum/armor/item_tank @@ -123,8 +131,16 @@ /obj/item/tank/Destroy() STOP_PROCESSING(SSobj, src) air_contents = null + QDEL_NULL(tank_assembly) return ..() +/obj/item/tank/update_overlays() + . = ..() + if(tank_assembly) + . += tank_assembly.icon_state + . += tank_assembly.overlays + . += "bomb_assembly" + /obj/item/tank/examine(mob/user) var/obj/icon = src . = ..() @@ -155,6 +171,9 @@ . += span_notice("It feels [descriptive].") + if(tank_assembly) + . += span_warning("There is some kind of device [EXAMINE_HINT("rigged")] to the tank!") + /obj/item/tank/deconstruct(disassembled = TRUE) var/atom/location = loc if(location) @@ -176,8 +195,30 @@ /obj/item/tank/attackby(obj/item/attacking_item, mob/user, params) add_fingerprint(user) if(istype(attacking_item, /obj/item/assembly_holder)) + if(tank_assembly) + balloon_alert(user, "something already attached!") + return ITEM_INTERACT_BLOCKING bomb_assemble(attacking_item, user) - return TRUE + return ITEM_INTERACT_SUCCESS + return ..() + +/obj/item/tank/wrench_act(mob/living/user, obj/item/tool) + if(tank_assembly) + tool.play_tool_sound(src) + bomb_disassemble(user) + return ITEM_INTERACT_SUCCESS + return ..() + +/obj/item/tank/welder_act(mob/living/user, obj/item/tool) + if(bomb_status) + balloon_alert(user, "already welded!") + return ITEM_INTERACT_BLOCKING + if(tool.use_tool(src, user, 0, volume=40)) + bomb_status = TRUE + balloon_alert(user, "bomb armed") + log_bomber(user, "welded a single tank bomb,", src, "| Temp: [air_contents.temperature] Pressure: [air_contents.return_pressure()]") + add_fingerprint(user) + return ITEM_INTERACT_SUCCESS return ..() /obj/item/tank/ui_state(mob/user) @@ -361,16 +402,87 @@ /obj/item/tank/proc/explosion_information() return list(TANK_RESULTS_REACTION = reaction_info, TANK_RESULTS_MISC = explosion_info) -/obj/item/tank/proc/ignite() //This happens when a bomb is told to explode +/obj/item/tank/on_found(mob/finder) //for mousetraps + . = ..() + if(tank_assembly) + tank_assembly.on_found(finder) + +/obj/item/tank/attack_hand() //also for mousetraps + if(..()) + return + if(tank_assembly) + tank_assembly.attack_hand() + +/obj/item/tank/Move() + . = ..() + if(tank_assembly) + tank_assembly.setDir(dir) + +/obj/item/tank/dropped() + . = ..() + if(tank_assembly) + tank_assembly.dropped() + +/obj/item/tank/IsSpecialAssembly() + return TRUE + +/obj/item/tank/receive_signal() //This is mainly called by the sensor through sense() to the holder, and from the holder to here. + audible_message(span_warning("[icon2html(src, hearers(src))] *beep* *beep* *beep*")) + playsound(src, 'sound/machines/triple_beep.ogg', ASSEMBLY_BEEP_VOLUME, TRUE) + addtimer(CALLBACK(src, PROC_REF(ignite)), 1 SECONDS) + +/// Attaches an assembly holder to the tank to create a bomb. +/obj/item/tank/proc/bomb_assemble(obj/item/assembly_holder/assembly, mob/living/user) + //Check if either part of the assembly has an igniter, but if both parts are igniters, then fuck it + var/igniter_count = 0 + for(var/obj/item/assembly/igniter/attached_assembly in assembly.assemblies) + igniter_count++ + + if(LAZYLEN(assembly.assemblies) == igniter_count) + return + + if((src in user.get_equipped_items(include_pockets = TRUE, include_accessories = TRUE)) && !user.canUnEquip(src)) + balloon_alert(user, "it's stuck!") + return + + if(!user.canUnEquip(assembly)) + balloon_alert(user, "it's stuck!") + return + + if(!user.transferItemToLoc(assembly, src)) + balloon_alert(user, "it's stuck!") + return + + tank_assembly = assembly //Tell the tank about its assembly part + assembly.master = src //Tell the assembly about its new owner + assembly.on_attach() + + balloon_alert(user, "bomb assembled") + update_appearance(UPDATE_OVERLAYS) + +/// Detaches an assembly holder from the tank, disarming the bomb +/obj/item/tank/proc/bomb_disassemble(mob/user) + bomb_status = FALSE + balloon_alert(user, "bomb disarmed") + if(!tank_assembly) + CRASH("bomb_disassemble() called on a tank with no assembly!") + user.put_in_hands(tank_assembly) + tank_assembly.master = null + tank_assembly = null + update_appearance(UPDATE_OVERLAYS) + +/// Ignites the contents of the tank. Called when receiving a signal if the tank is welded and has an igniter attached. +/obj/item/tank/proc/ignite() + if(!bomb_status) // if it isn't welded, release the gases instead + release() + return + + // check to make sure it's not already exploding before exploding it if(igniting) - stack_trace("Attempted to ignite a /obj/item/tank multiple times") - return //no double ignite + CRASH("ignite() called multiple times on [type]") igniting = TRUE - // This is done in return_air call, but even then it actually makes zero sense, this tank is going to be deleted - // before ever getting a chance to process. - //START_PROCESSING(SSobj, src) - var/datum/gas_mixture/our_mix = return_air() + var/datum/gas_mixture/our_mix = return_air() our_mix.assert_gases(/datum/gas/plasma, /datum/gas/oxygen) var/fuel_moles = our_mix.gases[/datum/gas/plasma][MOLES] + our_mix.gases[/datum/gas/oxygen][MOLES]/6 our_mix.garbage_collect() @@ -379,6 +491,14 @@ var/turf/ground_zero = get_turf(loc) + /// Used to determine what the temperature of the hotspot when it isn't able to explode + var/igniter_temperature = 0 + for(var/obj/item/assembly/igniter/firestarter in tank_assembly.assemblies) + igniter_temperature = max(igniter_temperature, firestarter.heat) + + if(!igniter_temperature) + CRASH("[type] called ignite() without any igniters attached") + if(bomb_mixture.temperature > (T0C + 400)) strength = (fuel_moles/15) @@ -392,7 +512,7 @@ explosion(ground_zero, devastation_range = -1, light_impact_range = 1, flash_range = 2, explosion_cause = src) else ground_zero.assume_air(bomb_mixture) - ground_zero.hotspot_expose(1000, 125) + ground_zero.hotspot_expose(igniter_temperature, 125) else if(bomb_mixture.temperature > (T0C + 250)) strength = (fuel_moles/20) @@ -403,7 +523,7 @@ explosion(ground_zero, devastation_range = -1, light_impact_range = 1, flash_range = 2, explosion_cause = src) else ground_zero.assume_air(bomb_mixture) - ground_zero.hotspot_expose(1000, 125) + ground_zero.hotspot_expose(igniter_temperature, 125) else if(bomb_mixture.temperature > (T0C + 100)) strength = (fuel_moles/25) @@ -412,21 +532,24 @@ explosion(ground_zero, devastation_range = -1, light_impact_range = round(strength,1), flash_range = round(strength*3,1), explosion_cause = src) else ground_zero.assume_air(bomb_mixture) - ground_zero.hotspot_expose(1000, 125) + ground_zero.hotspot_expose(igniter_temperature, 125) else ground_zero.assume_air(bomb_mixture) - ground_zero.hotspot_expose(1000, 125) + ground_zero.hotspot_expose(igniter_temperature, 125) - if(master) - qdel(master) qdel(src) -/obj/item/tank/proc/release() //This happens when the bomb is not welded. Tank contents are just spat out. +/// Releases air stored in the tank. Called when signaled without being welded, or when ignited without enough pressure to explode. +/obj/item/tank/proc/release() var/datum/gas_mixture/our_mix = return_air() var/datum/gas_mixture/removed = remove_air(our_mix.total_moles()) var/turf/T = get_turf(src) if(!T) return + log_atmos("[type] released its contents of ", air_contents) T.assume_air(removed) + +#undef ASSEMBLY_BOMB_BASE +#undef ASSEMBLY_BOMB_COEFFICIENT #undef ASSUME_AIR_DT_FACTOR diff --git a/code/modules/assembly/bomb.dm b/code/modules/assembly/bomb.dm deleted file mode 100644 index a40f3e3a659..00000000000 --- a/code/modules/assembly/bomb.dm +++ /dev/null @@ -1,155 +0,0 @@ -/obj/item/onetankbomb - name = "bomb" - icon = 'icons/obj/canisters.dmi' - inhand_icon_state = "assembly" - lefthand_file = 'icons/mob/inhands/items/devices_lefthand.dmi' - righthand_file = 'icons/mob/inhands/items/devices_righthand.dmi' - throwforce = 5 - w_class = WEIGHT_CLASS_NORMAL - throw_speed = 2 - throw_range = 4 - obj_flags = CONDUCTS_ELECTRICITY - var/status = FALSE //0 - not readied //1 - bomb finished with welder - var/obj/item/assembly_holder/bombassembly = null //The first part of the bomb is an assembly holder, holding an igniter+some device - var/obj/item/tank/bombtank = null //the second part of the bomb is a plasma tank - -/obj/item/onetankbomb/Destroy() - bombassembly = null - bombtank = null - return ..() - -/obj/item/onetankbomb/IsSpecialAssembly() - return TRUE - -/obj/item/onetankbomb/examine(mob/user) - return bombtank.examine(user) - -/obj/item/onetankbomb/update_icon(updates) - icon = bombtank?.icon || initial(icon) - return ..() - -/obj/item/onetankbomb/update_icon_state() - icon_state = bombtank?.icon_state || initial(icon_state) - return ..() - -/obj/item/onetankbomb/update_overlays() - . = ..() - if(bombassembly) - . += bombassembly.icon_state - . += bombassembly.overlays - . += "bomb_assembly" - -/obj/item/onetankbomb/wrench_act(mob/living/user, obj/item/I) - ..() - to_chat(user, span_notice("You disassemble [src]!")) - if(bombassembly) - bombassembly.forceMove(drop_location()) - bombassembly.master = null - bombassembly = null - if(bombtank) - bombtank.forceMove(drop_location()) - bombtank.master = null - bombtank = null - qdel(src) - return TRUE - -/obj/item/onetankbomb/welder_act(mob/living/user, obj/item/I) - ..() - . = FALSE - if(status) - to_chat(user, span_warning("[bombtank] already has a pressure hole!")) - return - if(!I.tool_start_check(user, amount=1)) - return - if(I.use_tool(src, user, 0, volume=40)) - status = TRUE - var/datum/gas_mixture/bomb_mix = bombtank.return_air() - log_bomber(user, "welded a single tank bomb,", src, "| Temp: [bomb_mix.temperature]") - to_chat(user, span_notice("A pressure hole has been bored to [bombtank] valve. \The [bombtank] can now be ignited.")) - add_fingerprint(user) - return TRUE - -/obj/item/onetankbomb/attack_self(mob/user) //pressing the bomb accesses its assembly - bombassembly.attack_self(user, TRUE) - add_fingerprint(user) - return - -/obj/item/onetankbomb/receive_signal() //This is mainly called by the sensor through sense() to the holder, and from the holder to here. - audible_message(span_warning("[icon2html(src, hearers(src))] *beep* *beep* *beep*")) - playsound(src, 'sound/machines/triple_beep.ogg', ASSEMBLY_BEEP_VOLUME, TRUE) - sleep(1 SECONDS) - if(QDELETED(src)) - return - if(status) - bombtank.ignite() //if its not a dud, boom (or not boom if you made shitty mix) the ignite proc is below, in this file - else - bombtank.release() - -/obj/item/onetankbomb/on_found(mob/finder) //for mousetraps - if(bombassembly) - bombassembly.on_found(finder) - -/obj/item/onetankbomb/attack_hand(mob/user, list/modifiers) //also for mousetraps - . = ..() - if(.) - return - if(bombassembly) - bombassembly.attack_hand() - -/obj/item/onetankbomb/Move() - . = ..() - if(bombassembly) - bombassembly.setDir(dir) - bombassembly.Move() - -/obj/item/onetankbomb/dropped() - . = ..() - if(bombassembly) - bombassembly.dropped() - - - - -// ---------- Procs below are for tanks that are used exclusively in 1-tank bombs ---------- - -//Bomb assembly proc. This turns assembly+tank into a bomb -/obj/item/tank/proc/bomb_assemble(obj/item/assembly_holder/assembly, mob/living/user) - //Check if either part of the assembly has an igniter, but if both parts are igniters, then fuck it - var/igniter_count = 0 - for(var/obj/item/assembly/attached_assembly as anything in assembly.assemblies) - if(isigniter(attached_assembly)) - igniter_count += 1 - if(LAZYLEN(assembly.assemblies) == igniter_count) - return - - if((src in user.get_equipped_items(include_pockets = TRUE, include_accessories = TRUE)) && !user.canUnEquip(src)) - to_chat(user, span_warning("[src] is stuck to you!")) - return - - if(!user.canUnEquip(assembly)) - to_chat(user, span_warning("[assembly] is stuck to your hand!")) - return - - var/obj/item/onetankbomb/bomb = new - user.transferItemToLoc(src, bomb) - user.transferItemToLoc(assembly, bomb) - - bomb.bombassembly = assembly //Tell the bomb about its assembly part - assembly.master = bomb //Tell the assembly about its new owner - assembly.on_attach() - - bomb.bombtank = src //Same for tank - master = bomb - - forceMove(bomb) - bomb.update_appearance() - - user.put_in_hands(bomb) //Equips the bomb if possible, or puts it on the floor. - to_chat(user, span_notice("You attach [assembly] to [src].")) - return - -/obj/item/onetankbomb/return_analyzable_air() - if(bombtank) - return bombtank.return_analyzable_air() - else - return null diff --git a/code/modules/research/experimentor.dm b/code/modules/research/experimentor.dm index 301f35a4bce..10ed939c55b 100644 --- a/code/modules/research/experimentor.dm +++ b/code/modules/research/experimentor.dm @@ -98,7 +98,6 @@ /obj/item/aicard, /obj/item/storage/backpack/holding, /obj/item/slime_extract, - /obj/item/onetankbomb, /obj/item/transfer_valve)) /obj/machinery/rnd/experimentor/RefreshParts() diff --git a/tgstation.dme b/tgstation.dme index 52426081b34..3a2c7489ab8 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -3316,7 +3316,6 @@ #include "code\modules\art\paintings.dm" #include "code\modules\art\statues.dm" #include "code\modules\assembly\assembly.dm" -#include "code\modules\assembly\bomb.dm" #include "code\modules\assembly\doorcontrol.dm" #include "code\modules\assembly\flash.dm" #include "code\modules\assembly\health.dm"