diff --git a/code/__HELPERS/unsorted.dm b/code/__HELPERS/unsorted.dm
index 7122d32f852..0593e055a9c 100644
--- a/code/__HELPERS/unsorted.dm
+++ b/code/__HELPERS/unsorted.dm
@@ -538,7 +538,7 @@ Turf and target are seperate in case you want to teleport some distance from a t
var/y = min(world.maxy, max(1, A.y + dy))
return locate(x,y,A.z)
-//Makes sure MIDDLE is between LOW and HIGH. If not, it adjusts it. Returns the adjusted value.
+//Makes sure MIDDLE is between LOW and HIGH. If not, it adjusts it. Returns the adjusted value. Lower bound takes priority.
/proc/between(var/low, var/middle, var/high)
return max(min(middle, high), low)
diff --git a/code/game/objects/effects/chem/chemsmoke.dm b/code/game/objects/effects/chem/chemsmoke.dm
index 3fa93e7e467..034ce946864 100644
--- a/code/game/objects/effects/chem/chemsmoke.dm
+++ b/code/game/objects/effects/chem/chemsmoke.dm
@@ -4,14 +4,76 @@
/obj/effect/effect/smoke/chem
icon = 'icons/effects/chemsmoke.dmi'
opacity = 0
+ layer = 6
time_to_live = 300
pass_flags = PASSTABLE | PASSGRILLE | PASSGLASS //PASSGLASS is fine here, it's just so the visual effect can "flow" around glass
+ var/splash_amount = 10 //atoms moving through a smoke cloud get splashed with up to 10 units of reagent
+ var/turf/destination
-/obj/effect/effect/smoke/chem/New()
+/obj/effect/effect/smoke/chem/New(var/newloc, smoke_duration, turf/dest_turf = null, icon/cached_icon = null)
+ time_to_live = smoke_duration
+
..()
+
create_reagents(500)
- return
+
+ if(cached_icon)
+ icon = cached_icon
+
+ set_dir(pick(cardinal))
+ pixel_x = -32 + rand(-8, 8)
+ pixel_y = -32 + rand(-8, 8)
+
+ //switching opacity on after the smoke has spawned, and then turning it off before it is deleted results in cleaner
+ //lighting and view range updates (Is this still true with the new lighting system?)
+ opacity = 1
+ //float over to our destination, if we have one
+ destination = dest_turf
+ if(destination)
+ walk_to(src, destination)
+
+/obj/effect/effect/smoke/chem/Destroy()
+ opacity = 0
+ fadeOut()
+ ..()
+
+/obj/effect/effect/smoke/chem/Move()
+ var/list/oldlocs = view(1, src)
+ . = ..()
+ if(.)
+ for(var/turf/T in view(1, src) - oldlocs)
+ for(var/atom/movable/AM in T)
+ if(!istype(AM, /obj/effect/effect/smoke/chem))
+ reagents.splash(AM, splash_amount, copy = 1)
+ if(loc == destination)
+ bound_width = 96
+ bound_height = 96
+
+/obj/effect/effect/smoke/chem/Crossed(atom/movable/AM)
+ ..()
+ if(!istype(AM, /obj/effect/effect/smoke/chem))
+ reagents.splash(AM, splash_amount, copy = 1)
+
+/obj/effect/effect/smoke/chem/proc/initial_splash()
+ for(var/turf/T in view(1, src))
+ for(var/atom/movable/AM in T)
+ if(!istype(AM, /obj/effect/effect/smoke/chem))
+ reagents.splash(AM, splash_amount, copy = 1)
+
+// Fades out the smoke smoothly using it's alpha variable.
+/obj/effect/effect/smoke/chem/proc/fadeOut(var/frames = 16)
+ if(!alpha) return //already transparent
+
+ frames = max(frames, 1) //We will just assume that by 0 frames, the coder meant "during one frame".
+ var/alpha_step = round(alpha / frames)
+ while(alpha > 0)
+ alpha = max(0, alpha - alpha_step)
+ sleep(world.tick_lag)
+
+/////////////////////////////////////////////
+// Chem Smoke Effect System
+/////////////////////////////////////////////
/datum/effect/effect/system/smoke_spread/chem
smoke_type = /obj/effect/effect/smoke/chem
var/obj/chemholder
@@ -115,13 +177,21 @@
else
I = icon('icons/effects/96x96.dmi', "smoke")
+ //Calculate smoke duration
+ var/smoke_duration = 150
+
+ var/pressure = 0
+ var/datum/gas_mixture/environment = location.return_air()
+ if(environment) pressure = environment.return_pressure()
+ smoke_duration = between(5, smoke_duration*pressure/(ONE_ATMOSPHERE/3), smoke_duration)
+
var/const/arcLength = 2.3559 //distance between each smoke cloud
for(var/i = 0, i < range, i++) //calculate positions for smoke coverage - then spawn smoke
var/radius = i * 1.5
if(!radius)
spawn(0)
- spawnSmoke(location, I, 1)
+ spawnSmoke(location, I, 1, 1)
continue
var/offset = 0
@@ -146,43 +216,26 @@
// Randomizes and spawns the smoke effect.
// Also handles deleting the smoke once the effect is finished.
//------------------------------------------
-/datum/effect/effect/system/smoke_spread/chem/proc/spawnSmoke(var/turf/T, var/icon/I, var/dist = 1, var/obj/effect/effect/smoke/chem/passed_smoke)
+/datum/effect/effect/system/smoke_spread/chem/proc/spawnSmoke(var/turf/T, var/icon/I, var/smoke_duration, var/dist = 1, var/splash_initial=0, var/obj/effect/effect/smoke/chem/passed_smoke)
var/obj/effect/effect/smoke/chem/smoke
if(passed_smoke)
smoke = passed_smoke
else
- smoke = PoolOrNew(/obj/effect/effect/smoke/chem, location)
+ smoke = PoolOrNew(/obj/effect/effect/smoke/chem, list(location, smoke_duration + rand(0, 20), T, I))
if(chemholder.reagents.reagent_list.len)
chemholder.reagents.trans_to_obj(smoke, chemholder.reagents.total_volume / dist, copy = 1) //copy reagents to the smoke so mob/breathe() can handle inhaling the reagents
- smoke.icon = I
- smoke.layer = 6
- smoke.set_dir(pick(cardinal))
- smoke.pixel_x = -32 + rand(-8, 8)
- smoke.pixel_y = -32 + rand(-8, 8)
- walk_to(smoke, T)
- smoke.opacity = 1 //switching opacity on after the smoke has spawned, and then
- sleep(150+rand(0,20)) // turning it off before it is deleted results in cleaner
- smoke.opacity = 0 // lighting and view range updates
- fadeOut(smoke)
- qdel(src)
-/datum/effect/effect/system/smoke_spread/chem/spores/spawnSmoke(var/turf/T, var/icon/I, var/dist = 1)
+ //Kinda ugly, but needed unless the system is reworked
+ if(splash_initial)
+ smoke.initial_splash()
+
+
+/datum/effect/effect/system/smoke_spread/chem/spores/spawnSmoke(var/turf/T, var/smoke_duration, var/icon/I, var/dist = 1)
var/obj/effect/effect/smoke/chem/spores = PoolOrNew(/obj/effect/effect/smoke/chem, location)
spores.name = "cloud of [seed.seed_name] [seed.seed_noun]"
- ..(T, I, dist, spores)
-
-/datum/effect/effect/system/smoke_spread/chem/proc/fadeOut(var/atom/A, var/frames = 16) // Fades out the smoke smoothly using it's alpha variable.
- if(A.alpha == 0) //Handle already transparent case
- return
- if(frames == 0)
- frames = 1 //We will just assume that by 0 frames, the coder meant "during one frame".
- var/step = A.alpha / frames
- for(var/i = 0, i < frames, i++)
- A.alpha -= step
- sleep(world.tick_lag)
- return
+ ..(T, I, smoke_duration, dist, spores)
/datum/effect/effect/system/smoke_spread/chem/proc/smokeFlow() // Smoke pathfinder. Uses a flood fill method based on zones to quickly check what turfs the smoke (airflow) can actually reach.
diff --git a/code/modules/mob/living/carbon/breathe.dm b/code/modules/mob/living/carbon/breathe.dm
index 2746e0c82e9..66cdd2f75e5 100644
--- a/code/modules/mob/living/carbon/breathe.dm
+++ b/code/modules/mob/living/carbon/breathe.dm
@@ -60,13 +60,15 @@
//Handle possble chem smoke effect
/mob/living/carbon/proc/handle_chemical_smoke(var/datum/gas_mixture/environment)
+ if(species && environment.return_pressure() < species.breath_pressure/5)
+ return //pressure is too low to even breathe in.
if(wear_mask && (wear_mask.flags & BLOCK_GAS_SMOKE_EFFECT))
return
for(var/obj/effect/effect/smoke/chem/smoke in view(1, src))
if(smoke.reagents.total_volume)
- smoke.reagents.trans_to_mob(src, 10, CHEM_INGEST, copy = 1)
- //maybe check air pressure here or something to see if breathing in smoke is even possible.
+ smoke.reagents.trans_to_mob(src, 5, CHEM_INGEST, copy = 1)
+ smoke.reagents.trans_to_mob(src, 5, CHEM_BLOOD, copy = 1)
// I dunno, maybe the reagents enter the blood stream through the lungs?
break // If they breathe in the nasty stuff once, no need to continue checking
diff --git a/code/modules/mob/living/carbon/human/life.dm b/code/modules/mob/living/carbon/human/life.dm
index 031a69dddb5..93bb0115d07 100644
--- a/code/modules/mob/living/carbon/human/life.dm
+++ b/code/modules/mob/living/carbon/human/life.dm
@@ -391,7 +391,7 @@
return 0
- var/safe_pressure_min = 16 // Minimum safe partial pressure of breathable gas in kPa
+ var/safe_pressure_min = species.breath_pressure // Minimum safe partial pressure of breathable gas in kPa
// Lung damage increases the minimum safe pressure.
if(species.has_organ["lungs"])
diff --git a/code/modules/mob/living/carbon/human/species/species.dm b/code/modules/mob/living/carbon/human/species/species.dm
index 313fa2e1c77..4169f6177d6 100644
--- a/code/modules/mob/living/carbon/human/species/species.dm
+++ b/code/modules/mob/living/carbon/human/species/species.dm
@@ -60,6 +60,7 @@
// Environment tolerance/life processes vars.
var/reagent_tag //Used for metabolizing reagents.
+ var/breath_pressure = 16 // Minimum partial pressure safe for breathing, kPa
var/breath_type = "oxygen" // Non-oxygen gas breathed, if any.
var/poison_type = "phoron" // Poisonous air.
var/exhale_type = "carbon_dioxide" // Exhaled gas type.
diff --git a/code/modules/reagents/Chemistry-Holder.dm b/code/modules/reagents/Chemistry-Holder.dm
index 8a7e34ccb9b..d077c5f3aa1 100644
--- a/code/modules/reagents/Chemistry-Holder.dm
+++ b/code/modules/reagents/Chemistry-Holder.dm
@@ -311,7 +311,7 @@
trans_to(target, amount, multiplier, copy)
/datum/reagents/proc/trans_id_to(var/atom/target, var/id, var/amount = 1)
- if (!target || !target.reagents)
+ if (!target || !target.reagents || !target.simulated)
return
amount = min(amount, get_reagent_amount(id))
@@ -340,7 +340,7 @@
return
/datum/reagents/proc/touch_mob(var/mob/target)
- if(!target || !istype(target))
+ if(!target || !istype(target) || !target.simulated)
return
for(var/datum/reagent/current in reagent_list)
@@ -349,7 +349,7 @@
update_total()
/datum/reagents/proc/touch_turf(var/turf/target)
- if(!target || !istype(target))
+ if(!target || !istype(target) || !target.simulated)
return
for(var/datum/reagent/current in reagent_list)
@@ -358,7 +358,7 @@
update_total()
/datum/reagents/proc/touch_obj(var/obj/target)
- if(!target || !istype(target))
+ if(!target || !istype(target) || !target.simulated)
return
for(var/datum/reagent/current in reagent_list)
@@ -377,7 +377,7 @@
return trans_to_mob(target, amount, CHEM_TOUCH, perm, copy)
/datum/reagents/proc/trans_to_mob(var/mob/target, var/amount = 1, var/type = CHEM_BLOOD, var/multiplier = 1, var/copy = 0) // Transfer after checking into which holder...
- if(!target || !istype(target))
+ if(!target || !istype(target) || !target.simulated)
return
if(iscarbon(target))
var/mob/living/carbon/C = target
@@ -396,7 +396,7 @@
R.touch_mob(target)
/datum/reagents/proc/trans_to_turf(var/turf/target, var/amount = 1, var/multiplier = 1, var/copy = 0) // Turfs don't have any reagents (at least, for now). Just touch it.
- if(!target)
+ if(!target || !target.simulated)
return
var/datum/reagents/R = new /datum/reagents(amount * multiplier)
@@ -405,7 +405,7 @@
return
/datum/reagents/proc/trans_to_obj(var/turf/target, var/amount = 1, var/multiplier = 1, var/copy = 0) // Objects may or may not; if they do, it's probably a beaker or something and we need to transfer properly; otherwise, just touch.
- if(!target)
+ if(!target || !target.simulated)
return
if(!target.reagents)
diff --git a/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Food-Drinks.dm b/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Food-Drinks.dm
index 0d04d8ad99b..62808ba4546 100644
--- a/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Food-Drinks.dm
+++ b/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Food-Drinks.dm
@@ -219,6 +219,10 @@
description = "This is what makes chilis hot."
reagent_state = LIQUID
color = "#B31008"
+ var/agony_dose = 5
+ var/agony_amount = 2
+ var/discomfort_message = "Your insides feel uncomfortably hot!"
+ var/slime_temp_adj = 10
/datum/reagent/capsaicin/affect_blood(var/mob/living/carbon/M, var/alien, var/removed)
if(alien == IS_DIONA)
@@ -232,92 +236,79 @@
var/mob/living/carbon/human/H = M
if(H.species && (H.species.flags & (NO_PAIN | IS_SYNTHETIC)))
return
- if(dose < 5 && (dose == metabolism || prob(5)))
- M << "Your insides feel uncomfortably hot!"
- if(dose >= 5)
- M.apply_effect(2, AGONY, 0)
+ if(dose < agony_dose)
+ if(prob(5) || dose == metabolism) //dose == metabolism is a very hacky way of forcing the message the first time this procs
+ M << discomfort_message
+ else
+ M.apply_effect(agony_amount, AGONY, 0)
if(prob(5))
- M.visible_message("[M] [pick("dry heaves!","coughs!","splutters!")]", "You feel like your insides are burning!")
+ M.custom_emote(2, "[pick("dry heaves!","coughs!","splutters!")]")
+ M << "You feel like your insides are burning!"
if(istype(M, /mob/living/carbon/slime))
- M.bodytemperature += rand(10, 25)
+ M.bodytemperature += rand(0, 15) + slime_temp_adj
holder.remove_reagent("frostoil", 5)
-/datum/reagent/condensedcapsaicin
+/datum/reagent/capsaicin/condensed
name = "Condensed Capsaicin"
id = "condensedcapsaicin"
description = "A chemical agent used for self-defense and in police work."
reagent_state = LIQUID
touch_met = 50 // Get rid of it quickly
color = "#B31008"
+ agony_dose = 0.5
+ agony_amount = 4
+ discomfort_message = "You feel like your insides are burning!"
+ slime_temp_adj = 15
-/datum/reagent/condensedcapsaicin/affect_blood(var/mob/living/carbon/M, var/alien, var/removed)
- if(alien == IS_DIONA)
- return
- M.adjustToxLoss(0.5 * removed)
-
-/datum/reagent/condensedcapsaicin/affect_touch(var/mob/living/carbon/M, var/alien, var/removed)
+/datum/reagent/capsaicin/condensed/affect_touch(var/mob/living/carbon/M, var/alien, var/removed)
var/eyes_covered = 0
var/mouth_covered = 0
- var/obj/item/safe_thing = null
+ var/no_pain = 0
+ var/obj/item/eye_protection = null
+ var/obj/item/face_protection = null
+
+ var/list/protection
if(istype(M, /mob/living/carbon/human))
var/mob/living/carbon/human/H = M
+ protection = list(H.head, H.glasses, H.wear_mask)
if(H.species && (H.species.flags & NO_PAIN))
- return
- if(H.head)
- if(H.head.flags & MASKCOVERSEYES)
- eyes_covered = 1
- safe_thing = H.head
- if(H.head.flags & MASKCOVERSMOUTH)
- mouth_covered = 1
- safe_thing = H.head
- if(H.wear_mask)
- if(!eyes_covered && H.wear_mask.flags & MASKCOVERSEYES)
- eyes_covered = 1
- safe_thing = H.wear_mask
- if(!mouth_covered && H.wear_mask.flags & MASKCOVERSMOUTH)
- mouth_covered = 1
- safe_thing = H.wear_mask
- if(H.glasses)
- if(!eyes_covered)
- eyes_covered = 1
- if(!safe_thing)
- safe_thing = H.glasses
- if(eyes_covered && mouth_covered)
- M << "Your [safe_thing] protects you from the pepperspray!"
- return
- else if(eyes_covered)
- M << "Your [safe_thing] protect you from most of the pepperspray!"
- M.eye_blurry = max(M.eye_blurry, 15)
- M.eye_blind = max(M.eye_blind, 5)
- M.Stun(5)
- M.Weaken(5)
- return
- else if (mouth_covered) // Mouth cover is better than eye cover
- M << "Your [safe_thing] protects your face from the pepperspray!"
- M.eye_blurry = max(M.eye_blurry, 5)
- return
- else // Oh dear :D
- M << "You're sprayed directly in the eyes with pepperspray!"
- M.eye_blurry = max(M.eye_blurry, 25)
- M.eye_blind = max(M.eye_blind, 10)
- M.Stun(5)
- M.Weaken(5)
- return
-
-/datum/reagent/condensedcapsaicin/affect_ingest(var/mob/living/carbon/M, var/alien, var/removed)
- if(ishuman(M))
- var/mob/living/carbon/human/H = M
- if(H.species && (H.species.flags & (NO_PAIN | IS_SYNTHETIC)))
- return
- if(dose == metabolism)
- M << "You feel like your insides are burning!"
+ no_pain = 1 //TODO: living-level can_feel_pain() proc
else
- M.apply_effect(4, AGONY, 0)
- if(prob(5))
- M.visible_message("[M] [pick("dry heaves!","coughs!","splutters!")]", "You feel like your insides are burning!")
- if(istype(M, /mob/living/carbon/slime))
- M.bodytemperature += rand(15, 30)
- holder.remove_reagent("frostoil", 5)
+ protection = list(M.wear_mask)
+
+ for(var/obj/item/I in protection)
+ if(I)
+ if(I.flags & MASKCOVERSEYES)
+ eyes_covered = 1
+ eye_protection = I.name
+ if(I.flags & MASKCOVERSMOUTH)
+ mouth_covered = 1
+ face_protection = I.name
+
+ var/message = null
+ if(eyes_covered)
+ if(!mouth_covered)
+ message = "Your [eye_protection] protects your eyes from the pepperspray!"
+ else
+ message = "The pepperspray gets in your eyes!"
+ if(mouth_covered)
+ M.eye_blurry = max(M.eye_blurry, 15)
+ M.eye_blind = max(M.eye_blind, 5)
+ else
+ M.eye_blurry = max(M.eye_blurry, 25)
+ M.eye_blind = max(M.eye_blind, 10)
+
+ if(mouth_covered)
+ if(!message)
+ message = "Your [face_protection] protects you from the pepperspray!"
+ else if(!no_pain)
+ message = "Your face and throat burn!"
+ if(prob(25))
+ M.custom_emote(2, "[pick("coughs!","coughs hysterically!","splutters!")]")
+ M.Stun(5)
+ M.Weaken(5)
+
+ M << message
/* Drinks */
diff --git a/html/changelogs/HarpyEagle-pepperspray-fix.yml b/html/changelogs/HarpyEagle-pepperspray-fix.yml
new file mode 100644
index 00000000000..c474310d0f8
--- /dev/null
+++ b/html/changelogs/HarpyEagle-pepperspray-fix.yml
@@ -0,0 +1,20 @@
+################################
+# Valid Prefixes:
+# bugfix
+# wip (For works in progress)
+# tweak
+# soundadd
+# sounddel
+# rscadd (general adding of nice things)
+# rscdel (general deleting of nice things)
+# imageadd
+# imagedel
+# maptweak
+# spellcheck (typo fixes)
+# experiment
+#################################
+
+author: HarpyEagle
+delete-after: True
+changes:
+ - bugfix: "When affected by pepperspray, eye protection now prevents blindness and face protection now prevents stun, instead of face protection doing both."
\ No newline at end of file