Adds Chemical flamethrowers (#25091)

* Initial work

* Good enough to open a PR out of draft

* Make these use the vars on the flamethrower

* Moar fixes

* Undefines define

* Update code/game/objects/items/weapons/chemical_flamethrower.dm

Co-authored-by: GDN <96800819+GDNgit@users.noreply.github.com>

* Update code/game/objects/items/weapons/chemical_flamethrower.dm

Co-authored-by: GDN <96800819+GDNgit@users.noreply.github.com>

* Update code/game/objects/items/weapons/chemical_flamethrower.dm

Co-authored-by: GDN <96800819+GDNgit@users.noreply.github.com>

* Update code/game/objects/items/weapons/chemical_flamethrower.dm

Co-authored-by: GDN <96800819+GDNgit@users.noreply.github.com>

* Update code/game/objects/items/weapons/chemical_flamethrower.dm

Co-authored-by: GDN <96800819+GDNgit@users.noreply.github.com>

* This won't compile

* GDN review

* Fixes CI

* Fixes CI even more

* 5 ammo per tile now

* Heavily nerfs adding multiple fires

* Adds phlogiston as a burnable reagent

* Fixes the fire name

* Placeholder sprite + ammo use fix

* Now in cargo partially

* Apply suggestions from code review

Co-authored-by: Henri215 <77684085+Henri215@users.noreply.github.com>
Signed-off-by: DGamerL <108773801+DGamerL@users.noreply.github.com>

* Update code/game/objects/items/weapons/chemical_flamethrower/chemical_flamethrower.dm

Co-authored-by: GDN <96800819+GDNgit@users.noreply.github.com>
Signed-off-by: DGamerL <108773801+DGamerL@users.noreply.github.com>

* Update code/game/objects/items/weapons/chemical_flamethrower/fire_effect.dm

Co-authored-by: GDN <96800819+GDNgit@users.noreply.github.com>
Signed-off-by: DGamerL <108773801+DGamerL@users.noreply.github.com>

* Update code/game/objects/items/weapons/chemical_flamethrower/fire_effect.dm

Co-authored-by: GDN <96800819+GDNgit@users.noreply.github.com>
Signed-off-by: DGamerL <108773801+DGamerL@users.noreply.github.com>

* Lewc Review

* Adds nuclear variant

* Adds the chem flamethrower to the nukie uplink, alongside ammo

* More fire armor tweaks

* GDN review

* Update code/modules/reagents/chemistry/reagents/pyrotechnic.dm

Co-authored-by: 1080pCat <96908085+1080pCat@users.noreply.github.com>
Signed-off-by: DGamerL <108773801+DGamerL@users.noreply.github.com>

* Update code/game/objects/items/weapons/chemical_flamethrower/chemical_flamethrower.dm

Co-authored-by: 1080pCat <96908085+1080pCat@users.noreply.github.com>
Signed-off-by: DGamerL <108773801+DGamerL@users.noreply.github.com>

* Apply suggestions from code review

Co-authored-by: 1080pCat <96908085+1080pCat@users.noreply.github.com>
Signed-off-by: DGamerL <108773801+DGamerL@users.noreply.github.com>

* Update code/game/objects/items/weapons/chemical_flamethrower/chemical_flamethrower.dm

Co-authored-by: Luc <89928798+lewcc@users.noreply.github.com>
Signed-off-by: DGamerL <108773801+DGamerL@users.noreply.github.com>

* Update code/game/objects/items/weapons/chemical_flamethrower/fire_effect.dm

Co-authored-by: Luc <89928798+lewcc@users.noreply.github.com>
Signed-off-by: DGamerL <108773801+DGamerL@users.noreply.github.com>

* TEMPORARY PUSH

* Adds inhands

* Update code/game/objects/items/weapons/chemical_flamethrower/chemical_flamethrower.dm

Co-authored-by: Luc <89928798+lewcc@users.noreply.github.com>
Signed-off-by: DGamerL <108773801+DGamerL@users.noreply.github.com>

* Update code/game/objects/items/weapons/chemical_flamethrower/chemical_flamethrower.dm

Co-authored-by: Luc <89928798+lewcc@users.noreply.github.com>
Signed-off-by: DGamerL <108773801+DGamerL@users.noreply.github.com>

* Fixes inhands

* Makes air hot again

* Lewc review

* Audible messages and canister refilling

* Update code/game/objects/items/weapons/chemical_flamethrower/chemical_flamethrower.dm

Co-authored-by: Burzah <116982774+Burzah@users.noreply.github.com>
Signed-off-by: DGamerL <108773801+DGamerL@users.noreply.github.com>

---------

Signed-off-by: DGamerL <108773801+DGamerL@users.noreply.github.com>
Co-authored-by: GDN <96800819+GDNgit@users.noreply.github.com>
Co-authored-by: Henri215 <77684085+Henri215@users.noreply.github.com>
Co-authored-by: 1080pCat <96908085+1080pCat@users.noreply.github.com>
Co-authored-by: Luc <89928798+lewcc@users.noreply.github.com>
Co-authored-by: Burzah <116982774+Burzah@users.noreply.github.com>
This commit is contained in:
DGamerL
2024-09-05 23:12:56 +00:00
committed by GitHub
co-authored by GDN Henri215 1080pCat Luc Burzah
parent 6ae5bfef8a
commit e3c73110db
21 changed files with 523 additions and 10 deletions
@@ -0,0 +1,288 @@
/obj/item/chemical_flamethrower
name = "chemical flamethrower"
desc = "I love the smell of napalm in the morning."
icon = 'icons/obj/chemical_flamethrower.dmi'
icon_state = "chem_flame"
lefthand_file = 'icons/mob/inhands/flamethrower_lefthand.dmi'
righthand_file = 'icons/mob/inhands/flamethrower_righthand.dmi'
flags = CONDUCT
force = 5
throwforce = 10
throw_speed = 1
throw_range = 5
w_class = WEIGHT_CLASS_NORMAL
slot_flags = SLOT_FLAG_BACK
materials = list(MAT_METAL = 5000)
resistance_flags = FIRE_PROOF
origin_tech = "combat=1;plasmatech=2;engineering=2"
/// How many canisters fit in our flamethrower?
var/max_canisters = 1
/// All our loaded canisters in a list
var/list/canisters = list()
/// Is this a type of flamethrower that starts loaded?
var/should_start_with_canisters = FALSE
/// The burn temperature of our currently stored chemical in the canister
var/canister_burn_temp = T0C + 300
/// The burn duration of our currently stored chemical in the canister
var/canister_burn_duration = 10 SECONDS
/// How many firestacks will our reagent apply
var/canister_fire_applications = 1
/// Is this a syndicate flamethrower
var/syndicate = FALSE
/obj/item/chemical_flamethrower/Initialize(mapload)
. = ..()
if(should_start_with_canisters && !length(canisters))
canisters += new /obj/item/chemical_canister
update_canister_stats()
update_icon_state()
/obj/item/chemical_flamethrower/Destroy()
QDEL_LIST_CONTENTS(canisters)
return ..()
/obj/item/chemical_flamethrower/update_icon_state()
icon_state = "chem_flame[max_canisters == 2 ? "_2" : ""][syndicate ? "_s" : ""]"
item_state = icon_state
/obj/item/chemical_flamethrower/update_overlays()
. = ..()
var/iterator = 1
for(var/obj/item/chemical_canister as anything in canisters)
. += mutable_appearance('icons/obj/chemical_flamethrower.dmi', "[chemical_canister.icon_state]_[iterator]")
iterator++
/obj/item/chemical_flamethrower/attack_self(mob/user)
. = ..()
if(length(canisters))
unequip_canisters(user)
/obj/item/chemical_flamethrower/proc/unequip_canisters(mob/user)
if(!length(canisters))
return
var/obj/item/chemical_canister/canister_to_remove = canisters[length(canisters)]
canister_to_remove.forceMove(get_turf(src))
user.put_in_hands(canister_to_remove)
canisters -= canister_to_remove
update_icon(UPDATE_OVERLAYS)
/obj/item/chemical_flamethrower/attackby(obj/item/I, mob/user, params)
. = ..()
if(!istype(I, /obj/item/chemical_canister))
to_chat(user, "<span class='notice'>You can't fit [I] in there!</span>")
return
if(length(canisters) >= max_canisters)
to_chat(user, "<span class='notice'>[src] is already full!</span>")
return
if(!user.unEquip(I))
return
to_chat(user, "<span class='notice'>You put [I] into [src].</span>")
canisters += I
I.forceMove(src)
update_canister_stats()
/obj/item/chemical_flamethrower/proc/update_canister_stats()
if(!length(canisters))
canister_burn_temp = null
canister_burn_duration = null
canister_fire_applications = null
return
var/burn_temp
var/burn_duration
var/fire_applications
var/how_many_canisters = length(canisters)
for(var/obj/item/chemical_canister/canister as anything in canisters)
if(!canister.ammo)
continue
burn_temp += canister.chem_burn_temp
burn_duration += canister.chem_burn_duration
fire_applications += canister.fire_applications
canister_burn_temp = round(burn_temp / how_many_canisters, 1)
canister_burn_duration = round(burn_duration / how_many_canisters, 1)
canister_fire_applications = round(fire_applications / how_many_canisters, 1)
update_icon(UPDATE_OVERLAYS)
/obj/item/chemical_flamethrower/afterattack(atom/target, mob/user, flag)
. = ..()
if(flag || !user)
return
if(user.mind?.martial_art?.no_guns)
to_chat(user, "<span class='warning'>[user.mind.martial_art.no_guns_message]</span>")
return
if(HAS_TRAIT(user, TRAIT_CHUNKYFINGERS))
to_chat(user, "<span class='warning'>Your meaty finger is far too large for the trigger guard!</span>")
return
if(user.get_active_hand() == src) // Make sure our user is still holding us
var/turf/target_turf = get_turf(target)
if(target_turf)
var/turf_list = get_line(user, target_turf)
add_attack_logs(user, target, "Chemical Flamethrowered at [target.x], [target.y], [target.z]")
INVOKE_ASYNC(src, PROC_REF(flame_turf), turf_list, user)
/obj/item/chemical_flamethrower/proc/flame_turf(list/turflist = list(), mob/user)
if(!length(turflist))
return
var/turf/previousturf = get_turf(src)
for(var/turf/simulated/T in turflist)
if(iswallturf(T)) // No going through walls
break
if(!use_ammo(3))
to_chat(user, "<span class='warning'>You hear a click!</span>")
playsound(user, 'sound/weapons/empty.ogg', 100, TRUE)
break // Whoops! No ammo!
if(T == previousturf)
continue // So we don't burn the tile we be standin on
var/found_obstruction = FALSE
for(var/obj/thing in T)
if(thing.density && !istype(thing, /obj/structure/table))
found_obstruction = TRUE
break
if(found_obstruction)
break
make_flame(T)
update_canister_stats() // In case we ran out of some fuel this fire
sleep(1)
previousturf = T
/obj/item/chemical_flamethrower/proc/make_flame(turf/spawn_turf)
new /obj/effect/fire(spawn_turf, canister_burn_temp, (canister_burn_duration + rand(1, 3) SECONDS), canister_fire_applications) // For that spicy randomness (and to save your ears from all fires extinguishing at the same time)
/*
* Uses `amount` ammo from the flamethrower.
* Returns `TRUE` if ammo could be consumed, returns `FALSE` if it failed somehow
* It will use up ammo if it failed.
*/
/obj/item/chemical_flamethrower/proc/use_ammo(amount)
var/total_ammo
for(var/obj/item/chemical_canister/canister as anything in canisters)
total_ammo += canister.ammo
if(total_ammo - amount < 0)
return FALSE
var/length = length(canisters)
var/difference = amount
for(var/i in 0 to length)
var/obj/item/chemical_canister/canister = canisters[length - i]
if(canister.ammo - difference <= 0)
difference -= canister.ammo
canister.ammo = 0
else
canister.ammo -= difference
difference = 0
if(!difference)
break
return !difference
/obj/item/chemical_flamethrower/extended
name = "extended capacity chemical flamethrower"
desc = "A flamethrower that accepts two chemical canisters to create lasting fires."
max_canisters = 2
/obj/item/chemical_flamethrower/extended/nuclear
name = "\improper Syndicate extended capacity chemical flamethrower"
desc = "A flamethrower that accepts two chemical canisters to create lasting fires. As black as the ash of your enemies."
syndicate = TRUE
/obj/item/chemical_flamethrower/extended/nuclear/Initialize(mapload)
. = ..()
for(var/i in 1 to max_canisters)
canisters += new /obj/item/chemical_canister/extended/nuclear
update_canister_stats()
/obj/item/chemical_canister
name = "chemical canister"
desc = "A simple canister of fuel. Does not accept any pyrotechnics except for welding fuel."
icon = 'icons/obj/chemical_flamethrower.dmi'
icon_state = "normal"
container_type = OPENCONTAINER
/// How much ammo do we have? Empty at 0.
var/ammo = 100
/// Which reagent IDs do we accept
var/list/accepted_chemicals = list("fuel")
/// The burn temperature of our currently stored chemical
var/chem_burn_temp = T0C + 300
/// The burn duration of our currently stored chemical
var/chem_burn_duration = 10 SECONDS
/// How many firestacks will our reagent apply
var/fire_applications = 1
/// The currently stored reagent ID
var/current_reagent_id
/// How many units of the reagent do we need to have it's effects kick in?
var/required_volume = 10
/// Do we have a locked in reagent type?
var/has_filled_reagent = FALSE
/// Are we silent on the first change of reagents?
var/first_time_silent = FALSE // The reason for this is so we can have canisters that spawn with reagents but don't announce it on `Initialize()`
/obj/item/chemical_canister/Initialize(mapload)
. = ..()
create_reagents(50)
/obj/item/chemical_canister/on_reagent_change()
if(has_filled_reagent && ammo != 0)
audible_message("<span class='notice'>[src]'s speaker beeps: no new chemicals are accepted!</span>")
return
if(!reagents.get_master_reagent_id() || !(reagents.get_master_reagent_id() in accepted_chemicals))
reagents.clear_reagents()
audible_message("<span class='notice'>[src]'s speaker beeps: the most present chemical isn't accepted!</span>")
return
current_reagent_id = reagents.get_master_reagent_id()
reagents.isolate_reagent(current_reagent_id)
var/has_enough_reagents = reagents.total_volume >= required_volume
if(!first_time_silent)
audible_message("<span class='notice'>[src]'s speaker beeps: \
All reagents are removed except for [current_reagent_id]. \
The reservoir has [reagents.total_volume] out of [required_volume] units. \
Reagent effects are [has_enough_reagents ? "in effect" : "not active"].</span>")
first_time_silent = FALSE
if(has_enough_reagents)
var/datum/reagent/reagent_to_burn = reagents.reagent_list[1]
chem_burn_duration = reagent_to_burn.burn_duration
chem_burn_temp = reagent_to_burn.burn_temperature
fire_applications = reagent_to_burn.fire_stack_applications
ammo = initial(ammo)
has_filled_reagent = TRUE
/obj/item/chemical_canister/extended
name = "extended capacity chemical canister"
desc = "An extended version of the original design. Does not accept any pyrotechnics except for welding fuel."
icon_state = "extended"
ammo = 200
required_volume = 20 // Bigger canister? More reagents needed.
/obj/item/chemical_canister/extended/nuclear
icon_state = "pyro"
accepted_chemicals = list("napalm")
first_time_silent = TRUE
/obj/item/chemical_canister/extended/nuclear/Initialize(mapload)
..()
reagents.add_reagent("napalm", 30) // Overload it with napalm!
/obj/item/chemical_canister/pyrotechnics
name = "extended capacity chemical canister"
desc = "A specialized canister designed to accept certain pyrotechnics."
icon_state = "pyro"
ammo = 150
accepted_chemicals = list("phlogiston", "phlogiston_dust", "napalm", "fuel", "thermite", "clf3", "plasma")
@@ -0,0 +1,116 @@
GLOBAL_LIST_EMPTY(flame_effects)
#define MAX_FIRE_EXIST_TIME 10 MINUTES // That's a lot of fuel, but you are not gonna make it last for longer
/obj/effect/fire
name = "fire"
desc = "You don't think you should touch this."
icon = 'icons/effects/chemical_fire.dmi'
icon_state = "fire1"
/// How hot is our fire?
var/temperature
/// How long will our fire last
var/duration = 10 SECONDS
/// How many firestacks does the fire give to mobs
var/application_stacks = 1
/obj/effect/fire/Initialize(mapload, reagent_temperature, reagent_duration, fire_applications)
. = ..()
if(reagent_duration < 0 || reagent_temperature <= 0) // There is no reason for this thing to exist
qdel(src)
return
duration = reagent_duration
temperature = reagent_temperature
application_stacks = max(application_stacks, fire_applications)
for(var/obj/effect/fire/flame in get_turf(src))
if(!istype(flame) || flame == src)
continue
merge_flames(flame)
GLOB.flame_effects += src
START_PROCESSING(SSprocessing, src)
/obj/effect/fire/Destroy()
. = ..()
GLOB.flame_effects -= src
STOP_PROCESSING(SSprocessing, src)
/obj/effect/fire/process()
if(duration <= 0)
fizzle()
return
duration -= 2 SECONDS
for(var/atom/movable/thing_to_burn in get_turf(src))
if(isliving(thing_to_burn))
damage_mob(thing_to_burn)
continue
if(isobj(thing_to_burn))
var/obj/obj_to_burn = thing_to_burn
obj_to_burn.fire_act(null, temperature)
continue
var/turf/location = get_turf(src)
if(!location)
return
var/datum/gas_mixture/air = location.private_unsafe_get_air()
if(!air)
return FALSE
var/datum/milla_safe/fire_heat_air/milla = new()
milla.invoke_async(src, location)
/datum/milla_safe/fire_heat_air
/datum/milla_safe/fire_heat_air/on_run(obj/effect/fire/fire, turf/T)
var/datum/gas_mixture/env = get_turf_air(T)
env.set_temperature(fire.temperature)
/obj/effect/fire/water_act(volume, temperature, source, method)
. = ..()
duration -= 10 SECONDS
if(duration <= 0)
fizzle()
/obj/effect/fire/Crossed(atom/movable/AM, oldloc)
. = ..()
if(isliving(AM))
damage_mob(AM)
to_chat(AM, "<span class='warning'>[src] burns you!</span>")
return
if(isitem(AM))
var/obj/item/item_to_burn = AM
item_to_burn.fire_act(null, temperature)
return
/obj/effect/fire/proc/fizzle()
playsound(src, 'sound/effects/fire_sizzle.ogg', 50, TRUE)
qdel(src)
/obj/effect/fire/proc/merge_flames(obj/effect/fire/merging_flame)
duration = min((duration + (merging_flame.duration / 4)), MAX_FIRE_EXIST_TIME)
temperature = ((merging_flame.temperature + temperature) / 2) // No making a sun by just clicking 10 times on a turf
merging_flame.fizzle()
/obj/effect/fire/proc/damage_mob(mob/living/mob_to_burn)
if(!istype(mob_to_burn))
return
var/fire_damage = temperature / 100
if(ishuman(mob_to_burn))
var/mob/living/carbon/human/human_to_burn = mob_to_burn
var/fire_armour = human_to_burn.get_thermal_protection()
if(fire_armour >= FIRE_IMMUNITY_MAX_TEMP_PROTECT)
return
if(fire_armour == FIRE_SUIT_MAX_TEMP_PROTECT) // Good protection but you won't survive infinitely in it
fire_damage /= 4
mob_to_burn.adjustFireLoss(fire_damage)
mob_to_burn.adjust_fire_stacks(application_stacks)
mob_to_burn.IgniteMob()
#undef MAX_FIRE_EXIST_TIME
@@ -557,6 +557,13 @@
/obj/item/storage/box/syndie_kit/pen_bomb/populate_contents()
new /obj/item/grenade/syndieminibomb/pen(src)
/obj/item/storage/box/syndie_kit/chemical_canister
name = "\improper Chemical flamethrower canisters"
/obj/item/storage/box/syndie_kit/chemical_canister/populate_contents()
new /obj/item/chemical_canister/extended/nuclear(src)
new /obj/item/chemical_canister/extended/nuclear(src)
/obj/item/storage/box/syndie_kit/decoy
name = "\improper Decoy Grenade kit"